#226 Upgrading to Rails 3 Part 2
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
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.
# 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.
A página agora apresenta um erro relacionado a falta de uma constante APP_CONFIG
. Essa constante está definida no arquivo /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
.
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:
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
.
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 <span class="passed">create</span> .rspec <span class="info">exist</span> spec <span class="failed">conflict</span> spec/spec_helper.rb Overwrite /Users/eifion/rails/apps_for_asciicasts/ep226/railscasts/spec/spec_helper.rb? (enter "h" for help) [Ynaqdh] Y <span class="forced">force</span> spec/spec_helper.rb <span class="passed">create</span> autotest <span class="passed">create</span> 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:
# 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 <span class="failed">152 examples, 4 failures</span> 1) CommentsController as guest index action should render index template for rss with xml Failure/Error: response.should have_tag('title', :text => 'Railscasts Comments') <span class="failed">undefined method `has_tag?' for #<ActionController::TestResponse:0x000001040f2dc8></span> # ./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') <span class="failed">undefined method `has_tag?' for #<ActionController::TestResponse:0x00000104007710></span> # ./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/) <span class="failed">undefined method `has_tag?' for #<ActionController::TestResponse:0x00000102e2dd00></span> # ./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/) <span class="failed">undefined method `has_tag?' for #<ActionController::TestResponse:0x000001041f03b0></span> # ./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 <span class="passed">152 examples, 0 failures</span>
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.
<span class="failed">Soon-to-be-deprecated ActiveRecord calls</span> Methods such as find(:all), find(:first), finds with conditions, and the :joins option will soon be deprecated. More information: <span class="url">http://m.onkey.org/2010/1/22/active-record-query-interface</span>
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:
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
.
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:
<span class="failed">named_scope is now just scope</span> The named_scope method has been renamed to just scope. More information: <span class="url">http://github.com/rails/rails/commit/d60bb0a9e4be2ac0a9de9a69041a4ddc2e0cc914</span>
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
:
named_scope :recent, :order => "created_at DESC"
ficará
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:
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:
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.
<span class="failed">Deprecated ERb helper calls</span> 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: <span class="url">http://weblog.rubyonrails.org/</span>
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
.
<% 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.
<%= 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.
<% @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.