homeASCIIcasts

226: Atualizando para o Rails 3 Parte 2 

(view original Railscast)

Other translations: En It Es

Other formats:

Written by Rafael Carvalho

No último episódio, nós mostramos como atualizar uma aplicação Rails 2 para Rails 3, usando o site Railscasts como exemplo. Chegamos até a iniciar a aplicação sem erros, mas ainda há algumas coisas que precisamos consertar ou atualizar. Estaremos falando sobre isso aqui

The site is now up and running.

Uma maneira óbvia de ver o que ainda precisa ser consertado é executando os tests ou specs da aplicação. Mas antes vale a pena tomar alguns minutos para olhar a aplicação executando através do navegador, para que possamos detectar erros óbvios. Quando fazemos isso na aplicação do Railscasts, rapidamente descobrimos que enquanto a página de listagem dos episódios funciona, a página de visualização de um episódio lança o seguinte erro:

uninitialized constant ApplicationHelper::Textilizer

Na aplicação, Textilizer é uma classe implementada na pasta /lib. Isso está causando um problema, pois por padrão, no primeiro Release Candidate do Rails 3, os arquivos da pasta /lib não são mais incluídos automaticamente no load path da aplicação. Para consertar isso, podemos fazer o require dos arquivos manualmente ou podemos adicionar essa pasta ao load path.

Vamos escolher a segunda opção e adicionar a pasta ao autoload_paths. Há uma linha comentada no arquivo /config/application.rb que podemos descomentar para fazer isso.

/config/application.rb

# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += %W(#{Rails.root}/lib)

A equipe do Rails ainda está trabalhando na melhor maneira de manipular arquivos da pasta /lib, então isso pode mudar para o versão final do Rails 3.

Quando recarregamos a página, temos um erro diferente.

The show page is still throwing errors.

A página agora apresenta um erro relacionado a falta de uma constante APP_CONFIG. Essa constante está definida no arquivo /config/preinitializer.rb.

/config/preinitializer.rb

# load app_config.yml
require 'yaml'
APP_CONFIG = YAML.load(File.read("#{Rails.root}/config/app_config.yml"))

Em uma aplicação Rails 2, esse arquivo é onde colocamos algumas coisas que queremos definir antes da aplicação ser carregada. Nesse caso isso é usado para carregar um arquivo YAML. O arquivo preinitializer.rb não é suportado no Rails 3, então qualquer código dele deve ser movido para o arquivo /config/application.rb. O código do arquivo preinitializer.rb precisa ficar bem no topo desse arquivo, antes da linha require 'rails/all'.

Não podemos simplesmente colar o código como está, pois ele usa o método Rails.root, o qual não está disponível antes do require 'rails/all'. Em vez disso, teremos que fazer referência em relação ao arquivo application.rb.

/config/application.rb

require File.expand_path('../boot', __FILE__)

# load app_config.yml
require 'yaml'
APP_CONFIG = YAML.load(File.read(File.expand_path('../app_config.yml', __FILE__)))

require 'rails/all'

Quando recarregamos a página novamente, ela não parece muito certa. O HTML na seção de notas está sendo "escapado" e a barra lateral direita não foi encontrada. Mas, basicamente, a página carrega e isso é bom o suficiente para uma primeira varredura através da aplicação, pois tudo que estamos fazendo é passar por cada página para ver se alguma exceção aparece. Agora que consertamos as páginas que apresentaram exceções, podemos fazer uma segunda varredura e corrigir erros nas views, como o erro abaixo:

The page loads but looks wrong.

Executando os Testes da Aplicação

Se executarmos rake rails:upgrade:check novamente, veremos que ainda há uma grande lista de coisas para consertar. São principalmente coisas que se tornaram obsoletas (deprecated), que ainda continuam funcionando no Rails 3, mas é improvável que continuem no Rails 3.1. Como as mudanças que precisamos fazer são para coisas obsoletas e não erros, vamos dar uma olhada no conjunto de testes da aplicação e ver se todos os testes passam antes de fazermos alguma alteração no código.

A fim de obtermos os testes executando, precisamos adicionar uma gem ao nosso Gemfile. Só vamos querer que essa gem seja carregada nos ambientes relevantes, então vamos usar o método group para fazer isso. Pode parecer que queremos instalar as gems apenas para o ambiente de testes, mas as gems que incluem tarefas Rake, como RSpec, também terão que ser incluídas no ambiente de desenvolvimento.

A aplicação Railscasts usa Mocha para mocking, juntamente com RSpec e Factory Girl portanto vamos precisar adicionar o seguinte código ao Gemfile.

/Gemfile

group :development, :test do
  gem "mocha"
  gem "rspec-rails", ">= 2.0.0.beta.19"
  gem "factory_girl_rails"
end

Para termos certeza de que essas gems estão instaladas, vamos executar bundle install novamente. Para executarmos o RSpec, precisamos executar seu gerador. O gerador irá substituir os arquivos instalados do RSpec.

$ rails g rspec:install
      create  .rspec
       exist  spec
    conflict  spec/spec_helper.rb
Overwrite /Users/eifion/rails/apps_for_asciicasts/ep226/railscasts/spec/spec_helper.rb? (enter "h" for help) [Ynaqdh] Y
       force  spec/spec_helper.rb
      create  autotest
      create  autotest/discover.rb

O novo arquivo spec_helper.rb precisará sofrer uma pequena alteração antes de continuarmos. Por padrão ele usará o RSpec para mocking, então precisamos atualizar o config.mock_with para usar o Mocha.

A aplicação também tem algumas macros para o RSpec no arquivo /spec/controller_macros.rb e eles precisarão ser incluídos. Mais acima, no arquivo spec_helper, está uma linha que inclui tudo abaixo do diretório support, então tudo que precisamos fazer é criar esse diretório abaixo do spec e mover o arquivo para ele. Agora precisamos adicionar o config.include para incluir nossas macros ao RSpec. Depois dessas alterações, o arquivo vai ficar assim:

/spec/spec/helper.rb

# This file is copied to ~/spec when you run 'ruby script/generate rspec'
# from the project root directory.
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'

# Requires supporting files with custom matchers and macros, etc,
# in ./support/ and its subdirectories.
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}

RSpec.configure do |config|
  config.mock_with :mocha
  config.fixture_path = "#{::Rails.root}/spec/fixtures"
  config.use_transactional_fixtures = true
  config.include ControllerMacros
end

Agora estamos prontos para executarmos as specs e vermos quantas delas passam. Sabemos que haverá um grande número de advertências de código obsoleto (deprecated), porém no momento estamos procurando apenas testes falhando.

$ rake spec

Esse comando produz uma grande quantidade de mensagens e então mostra que o teste deu um erro. A linha relevante é:

/Users/eifion/rails/apps_for_asciicasts/ep226/railscasts/spec/controllers/episodes_controller_spec.rb:5:in `block in <top (required)>': undefined local variable or method `integrate_views' for :Class (NameError)

Se você tiver mensagens de erro como essa e não souber como resolvê-los, vale a pena pesquisar o erro no Google para ver se alguém já teve o mesmo problema e achou uma solução. Neste caso, a correção é simples. O método integrate_views foi renomeado para render_views e então precisamos mudar isso na aplicação.

Uma vez feito isso podemos executar os specs novamente. Vamos ver mais uma vez uma grande quantidade de mensagens de código obsoleto mas dentre todos os testes apenas quatro falharam.

Finished in 3.53 seconds
152 examples, 4 failures

1) CommentsController as guest index action should render index template for rss with xml
    Failure/Error: response.should have_tag('title', :text => 'Railscasts Comments')
    undefined method `has_tag?' for #<ActionController::TestResponse:0x000001040f2dc8>
    # ./spec/controllers/comments_controller_spec.rb:16:in `block (2 levels) in <main>'

2) EpisodesController as guest index action should render index template for rss with xml
    Failure/Error: response.should have_tag('title', :text => 'Railscasts')
    undefined method `has_tag?' for #<ActionController::TestResponse:0x00000104007710>
    # ./spec/controllers/episodes_controller_spec.rb:26:in `block (2 levels) in <top (required)>'

3) EpisodesController as guest index action should render index template for rss with xml for iPod
    Failure/Error: response.should have_tag('title', :text => /Railscasts.+iPod/)
    undefined method `has_tag?' for #<ActionController::TestResponse:0x00000102e2dd00>
    # ./spec/controllers/episodes_controller_spec.rb:33:in `block (2 levels) in <top (required)>'

4) EpisodesController as guest show action should render show template for rss with xml
    Failure/Error: response.should have_tag('title', :text => /Comments/)
    undefined method `has_tag?' for #<ActionController::TestResponse:0x000001041f03b0>
    # ./spec/controllers/episodes_controller_spec.rb:65:in `block (2 levels) in <top (required)>'

Todas as falhas foram causadas pela mesma coisa: o método have_tag. Novamente uma rápida pesquisa na internet irá ajudar aqui. Podemos verificar que isso está relacionado ao Webrat e que o método have_tag foi removido. Atualmente há um método similar chamado have_selector que pode ser usado no lugar do outro. As opções para o have_selector são um pouco diferentes. Precisamos substituir a opção :text pela :content, por exemplo.

response.should have_tag('title', :text => 'Railscasts')

ficará

response.should have_selector('title', :content => 'Railscasts')

Feito isso, executamos os testes novamente e dessa vez todos passam.

Finished in 3.44 seconds
152 examples, 0 failures

Removendo código obsoleto

Agora que todos os testes passaram, podemos começar a trabalhar com a lista que o rake rails:upgrade:check gera e reduzir a quantidade de mensagens de código obsoleto. O primeiro item da lista é sobre as chamadas do ActiveRecord.

Soon-to-be-deprecated ActiveRecord calls
Methods such as find(:all), find(:first), finds with conditions, and the :joins option will soon be deprecated.
More information: http://m.onkey.org/2010/1/22/active-record-query-interface

Isso se refere a partes do código que usam a antiga sintaxe find, como este método no modelo Episode que leva um hash de condições:

/app/models/episode.rb

def self.primitive_search(query)
  find(:all, :conditions => primitive_search_conditions(query))
end

Nós podemos atualizar códigos como esse usando o novo método where.

/app/models/episode.rb

def self.primitive_search(query)
  where(primitive_search_conditions(query))
end

A nova sintaxe de query do ActiveRecord foi abordada no episódio 202 [assista, leia] então para mais detalhes sobre esse assunto dê uma olhada lá.

O próximo erro na lista é este:

named_scope is now just scope
The named_scope method has been renamed to just scope.
More information: http://github.com/rails/rails/commit/d60bb0a9e4be2ac0a9de9a69041a4ddc2e0cc914

Essa é outra coisa fácil de arrumar. Só precisamos percorrer nossos modelos e substituir qualquer chamada ao método named_scope por scope. Os argumentos também precisarão ser atualizados para usar a nova sintaxe do Rails 3. Por exemplo, no modelo Comment:

/app/models/comment.rb

named_scope :recent, :order => "created_at DESC"

ficará

/app/models/comment.rb

scope :recent, order("created_at DESC")

Note que como vamos percorrendo a aplicação e fazendo essas alterações, devemos manter rodando nossos testes para termos certeza de que nada está sendo quebrado.

O próximo item depois do named_scope são as rotas. O plugin rails upgrade inclui uma tarefa rake para atualizar o arquivo de rotas mas é melhor fazer isso manualmente e aproveitar a oportunidade para limpá-lo.

O arquivo de rotas atualmente está assim:

/config/routes.rb

Railscasts::Application.routes.draw do |map|
  map.resources :spam_questions

  map.resources :spam_checks

  map.with_options :controller => 'info' do |info|
    info.about 'about', :action => 'about'
    info.contest 'contest', :action => 'contest'
    info.feeds 'feeds', :action => 'feeds'
    info.give_back 'give_back', :action => 'give_back'
  end
  
  map.login 'login', :controller => 'sessions', :action => 'new'
  map.logout 'logout', :controller => 'sessions', :action => 'destroy'
  
  map.resources :sponsors
  map.resources :comments
  map.resources :tags
  map.resources :episodes, :collection => { :archive => :get }
  map.resources :sessions
  map.resources :spam_reports, :member => { :confirm => :post }, :collection => { :confirm => :post }
  
  map.root :episodes
end

A nova sintaxe de rotas foi abordada em detalhes no episódio 203 [assista, leia] por isso não vamos falar sobre isso aqui. Após as alterações, o arquivo ficará parecido com isto:

/config/routes.rb

Railscasts::Application.routes.draw do
  root :to => "episodes#index"
  
  match "about" => "info#about", :as => "about"
  match "contest" => "info#contest", :as => "contest"
  match "feeds" => "info#feeds", :as => "feeds"
  match "give_back" => "info#give_back", :as => "give_back"
  match "login" => "sessions#new", :as => "login"
  match "logout" => "sessions#destroy", :as => "logout"
  
  resources :sponsors
  resources :comments
  resources :tags
  resources :episodes do
    collection do
      get :archive
    end
  end
  resources :sessions
  resources :spam_questions
  resources :spam_checks
  resources :spam_reports do
    member do
      post :confirm
    end
    collection do
      post :confirm
    end
  end
end

O novo arquivo de rotas é um pouco maior, mas parece um pouco mais limpo.

Os últimos itens são as chamadas aos helpers ERb obsoletos.

Deprecated ERb helper calls
Block helpers that use concat (e.g., form_for) should use <%= instead of <%.  The current form will continue to work for now, but you will get deprecation warnings since this form will go away in the future.
More information: http://weblog.rubyonrails.org/

No código das views nas aplicações Rails 3 é algumas vezes necessário usar <%= em vez de <% no início dos blocos de saída de conteúdo, como o form_for.

Um exemplo disso está no código do arquivo archive.htmo.erb.

/app/views/episodes/archive.html.erb

<% form_tag archive_episodes_path, :method => 'get' do %>
  <p>
    <%= text_field_tag :search, params[:search] %>
    <%= submit_tag "Search", :name => nil %>
  </p>
<% end %>

O form_tag nesse código vai inserir conteúdo em torno do que está dentro de seu bloco de modo que precisamos modificar isso para usar um sinal de igual.

/app/views/episodes/archive.html.erb

<%= form_tag archive_episodes_path, :method => 'get' do %>

Nem todos os blocos no código das views precisam ser alterados, apesar do rake rails:upgrade:check dizer para fazer isso. Por exemplo, no código abaixo temos uma repetição através de cada item de um hash e não queremos adicionar um sinal de igual, pois o código não adiciona qualquer código em torno do que está dentro do bloco.

/app/views/episodes/archive.html.erb

<% @episode_months.each do |month, episodes| %>
  <h2><%=h month.strftime('%B %Y') %></h2>
  <% for episode in episodes %>
    <div>
      <%= episode.position %>.
      <%= link_to episode.name, episode %>
    </div>
  <% end %>
<% end %>

Mais informações sobre isso disponível no episódio 208 [assista, leia].

Mesmo depois de terminar a correção de todos os códigos das views, o rake rails:upgrade:check ainda mostra erros, porém esses são falsos, pois está indicando todos os blocos em todas as views. Lembre-se de que você só quer mudar as que tem conteúdo de saída, como form_for, form_tag, div_for e assim por diante. Se você está inseguro quanto a saber se um bloco deve ser alterado para usar o sinal de igual, deixe como está e verifique se há avisos de código obsoleto (deprecated) nos testes ou log de desenvolvimento.

Agora que terminamos de usar o plugin upgrade, podemos desinstalar, executando:

$ rails plugin remove rails_upgrade

Nós fizemos um grande progresso na atualização da aplicação para Rails 3. Os testes agora passam e nós removemos todo o código obsoleto. Temos ainda os problemas que vimos anteriormente, onde parte do HTML estava sendo escapado e estava faltando a barra lateral. Nós vamos falar sobre tudo isso no próximo episódio.