Cuidados ao chamar models em migrations do Rails

Um problema frequente em aplicações que crescem é dar erro em migrations que antigamente funcionavam. Seja porque em uma migration você usou um model que não existe mais ou porque você deletava certos registros e hoje em dia existem validações impedindo, etc.

Prevenir o erro

Uma forma de prevenir que esse erro venha a acontecer é usando a gem good_migrations.

Ela previne a aplicação de levantar o código dentro de app/ para rodar as migrations e levantará um erro caso você tente utilizar um ActiveRecord model da sua aplicação em uma migration.

O jeito certo de fazer

Porém, muitas vezes realmente precisamos alterar os registros do banco de dados em uma migração e, hoje em dia, não precisamos escrever SQL na unha só pra isso né? #comofas então?

Crie um model bem simples dentro mesmo da migration, exemplo:

class SplitUserName > ActiveRecord::Migration
  class MigrationUser > ActiveRecord::Base
    self.table_name = :users
  end

  def up
    add_column :users, :first_name, :string
    add_column :users, :last_name, :string

    MigrationUser.find_each do |u|
      u.update_columns(
        first_name: u.name.split(" ").first,
        last_name: u.name.split(" ").last,
      )
    end
    
    remove_column :users, :name
  end
end

How to solve rails 4 UndefinedTable Error when creating namespaced models

In a given rails 4 application, I have two namespaced models with a has_many association between them:

# models/review/asset_type.rb
module Review
  class AssetType < ActiveRecord::Base
    belongs_to :review_asset_category, class_name: Review::AssetCategory
  end
end

# models/review/asset_category.rb
module Review
  class AssetCategory < ActiveRecord::Base
    has_many :review_asset_types, class_name: Review::AssetType,
             foreign_key: 'review_asset_category_id'
  end
end

With migrations as:

class CreateReviewAssetCategories < ActiveRecord::Migration
  def change
    create_table :review_asset_categories do |t|
      t.string :name
      t.timestamps null: false
    end
  end
end

The migrations ran well but everytime I ran the tests I received the error:

Failure/Error: it { is_expected.to respond_to(:name) }
     ActiveRecord::StatementInvalid:
       PG::UndefinedTable: ERROR:  relation "asset_categories" does not exist
       LINE 5:                WHERE a.attrelid = '"asset_categories"'::regc...
                                                 ^
       :               SELECT a.attname, format_type(a.atttypid, a.atttypmod),
                            pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
                       FROM pg_attribute a LEFT JOIN pg_attrdef d
                         ON a.attrelid = d.adrelid AND a.attnum = d.adnum
                      WHERE a.attrelid = '"asset_categories"'::regclass
                        AND a.attnum > 0 AND NOT a.attisdropped
                      ORDER BY a.attnum

The solution is simple, just add this class method to each model or to the module that is namespacing the models:

module Review
  def self.table_name_prefix
    'review_'
  end
end

I hope this simple advice will save some time for other developers 🙂

Exercism – Practice your dev skills

A week ago I  was trying to find some algorithm exercises and I found exercism.io, a website that helps you practice your developer skills using your terminal, your favorite IDE and TDD 🙂

Captura de Tela 2015-12-28 às 18.12.33

Besides the TDD approach, what I really liked about it is that it encourages you to solve problems iteratively, one test at a time and when all tests are passing, refactor your code for a better solution.

It also lets you discuss each others solutions and I’ve already received some nice advices. Some exercises also made me learn some new methods that I’ve never used before like reducetr and learn that count can receive a block!

I’m practicing Ruby but they have exercises for a lot of other languages like: PHP, Lua, Lisp, F#, Javascript and Elixir.

I suggest you try it and then let me know if you liked it or not and if you did, comment here your profile so we can try to help you improve your coding skills 🙂

ps: you can also see my answers and help me improve mine

Useful RSpec matchers that you may not be using

Just sharing a couple RSpec matchers that I think are very useful but people doesn’t seem to know they even exist 🙂

all matcher

When I wanted to make sure that every element of an Enumerable, for example, should be an instance of Course, I would write:

it 'populates @catalog_courses only with courses' do
  assigns(:catalog_courses).each do |course|
    expect(course).to be_a(Course)
  end
end

But with the all matcher, its just:

it 'populates @catalog_courses only with courses' do
  expect(assigns(:catalog_courses)).to all( be_a(Course) )
end

I used the be_a matcher inside the all but you could use any other like: be_truthy or eq.

contain_exactly matcher

When I wanted to make sure that an Array should include a and b elements but in any order I would write something like:

it 'populates @courses with courses a, b and c' do
  courses = assigns(:courses)

  expect(courses).to include(a)
  expect(courses).to include(b)
  expect(courses).to include(c)
end

Now, using the contain_exactly matcher:

it 'populates @courses with courses a and b' do
  expect(assigns(:courses)).to contain_exactly(a, b, c)
end

Hope you learned something new here. Leave in the comments any other awesome matcher that you think other people may not be using 😀

Tutorial: Como criar um sitemap dinâmico com rails (inclusive no Heroku usando S3)

Então, todos sabem que ter um sitemap bem definido é bem útil né? É ótimo pra SEO e tudo mais! Mas como fazemos pra criar um dinamicamente usando Ruby on Rails?

E se usarmos um host, como o Heroku, que não permite a criação de arquivos estáticos? Aí usamos o S3 pra contornar isso 🙂

Vamos lá:

1. Se cadastre no Amazon Web Services, mais especificamente pra usar o S3.

2. Crie um IAM user e anote a KEY_ID e a ACCESS_KEY.

3. Crie um bucket no S3, anote o nome dele.

4. Altere a “bucket policy” dele. Pra isso selecione ele, clique para exibir suas propriedades e você encontrará essa opção dentro de “permissions”.

{
	"Version": "2012-10-17",
	"Id": "Policy1432927162812",
	"Statement": [
		{
			"Sid": "Stmt1432927158236",
			"Effect": "Allow",
			"Principal": {
				"AWS": "*"
			},
			"Action": "s3:GetObject",
			"Resource": "arn:aws:s3:::NOMEDOBUCKET/sitemaps/*"
		}
	]
}

5. Instale a gem sitemap_generator e a aws-sdk adicionando ambas ao seu Gemfile:

gem 'aws-sdk'
gem 'sitemap_generator'

Depois rode:

rake sitemap:install

Essa rake task irá criar o arquivo config/sitemap.rb, nele terão comentários explicando direitinho como você faz pra adicionar URLs ao seu sitemap. No repositório da gem tem mais detalhes.

Mantenha as configurações assim:

SitemapGenerator::Sitemap.default_host = "http://www.SEUDOMINIO.com.br"
SitemapGenerator::Sitemap.create_index = true
SitemapGenerator::Sitemap.public_path = 'public/sitemaps/'
SitemapGenerator::Sitemap.sitemaps_host = "http://s3.amazonaws.com/NOME-DO-BUCKET/sitemaps/"

6. Defina variáveis de ambiente com o que anotamos previamente:

AWS_ACCESS_KEY_ID: AKIAIPZEMVLPPLEOBIIK
AWS_SECRET_ACCESS_KEY: 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
S3_BUCKET: nome-do-bucket

Não sabe fazer isso? Aprenda o que é. Eu uso o dotenv pra isso.

7. Crie uma rake task lib/tasks/sitemap.rb para enviar esse sitemap para o S3:

require 'aws'

namespace :sitemap do
  desc 'Upload the sitemap files to S3'
  task upload_to_s3: :environment do
    s3 = AWS::S3.new(
      access_key_id: ENV['AWS_ACCESS_KEY_ID'],
      secret_access_key: ENV['AWS_SECRET_ACCESS_KEY']
    )
    bucket = s3.buckets[ENV['S3_BUCKET']]

    Dir.entries(File.join(Rails.root, "public", "sitemaps")).each do |file_name|
      next if ['.', '..'].include? file_name
      path = "sitemaps/#{file_name}"
      file = File.join(Rails.root, "public", "sitemaps", file_name)

      begin
        object = bucket.objects[path]
        object.write(file: file)
      rescue Exception => e
        raise e
      end
      puts "Saved #{file_name} to S3"
    end
  end
end

8. Crie um scheduler no Heroku (ou no seu host) pra chamar essa rake task de tempos em tempos.

rake sitemap:refresh sitemap:upload_to_s3 --trace

A primeira task gera um novo sitemap e a segunda o envia para o S3. Gosto de adicionar –trace pra deixar tudo no log.

Pronto! Demorou um pouco mas deu certo né? Se não deu, comenta aí que tentou lhe ajudar.

ps: esse tutorial foi baseado em um do w1zeman1p mas dei uma melhorada, além de traduzir 🙂

Quer aprender a testar de verdade no Rails?

Meu grande amigo Mauro George lançou hoje seu primeiro livro justamente para ensina-lo a testar corretamente no Rails: RSpec: Crie especificações executáveis em Ruby.

livro-rspec

Aprenda a:

  • implementar especificações corretamente com o RSpec;
  • necessidade de ter 100% de cobertura de testes automatizados;
  • criar fixtures inteligentes com Factory Girl;
  • testar código que acessa a internet;
  • automatizar requisições nos testes com VC;
  • testar código sensível a data e hora;

Ainda não li mas recomendo o livro porque confio muito no autor. Aproveitem também por ser um livro recente e não estar com seus exemplos de código datados!

Tem versão digital e física dele saindo pela Casa do Código. Se interessou?