#225 Upgrading to Rails 3 Part 1
La prima release candidate di Rails 3.0 è appena stata rilasciata. E’ dunque un buon momento per provare questa nuova versione, se non lo avete già fatto per conto vostro, e per vedere i cambiamenti che dovranno essere fatti alle attuali applicazioni scritte in Rails 2 per far sì che continuino a funzionare bene anche col nuovo framework. In questo episodio vi mostreremo come fare l’aggiornamento di un’applicazione Rails 2.3 a Rails 3.0.
Ci sono già un po’ di episodi su Rails 3.0 che potete consultare a cui faremo riferimento anche durante l’aggiornamento. L’applicazione che aggiorneremo sarà il sito railscasts.com, il cui codice sorgente è disponibile su Github.
Anche se non siete ancora pronti ad usare Rails 3 in produzione, vale ugualmente la pena di provare a fare l’aggiornamento di applicazioni preesistenti scritte per Rails 2, proprio per vedere che genere di problemi si possono incontrare in questa attività. Se la vostra applicazione utilizza Git come SCM, è semplice creare un nuovo branch per provare la cosa, per cui, dopo aver scaricato il codice, creiamo un nuovo branch chiamato rails3
:
$ git checkout -b rails3 Switched to a new branch 'rails3'
Prima di cominciare
Prima di aggiornare un’applicazione a Rails 3, dovremmo fare l’aggiornamento della stessa all’ultima versione di Rails 2.3 che, nel momento in cui stiamo scrivendo, è Rails 2.3.8. Analogamente, tutti i gem usati dall’applicazione dovrebbero essere aggiornati all’ultima versione. In questo modo si è garantiti che non vi siano problemi di compatibilità con le ultime versioni.
Una volta aggiornato Rails e i gem, dovremmo lanciare i nostri test. Avere una buona suite di test automatici, garantisce che tutto funzioni ancora correttamente come dovrebbe:
$ rake spec (in /Users/eifion/rails/apps_for_asciicasts/ep225/railscasts) DEPRECATION WARNING: Rake tasks in vendor/plugins/hoptoad_notifier/tasks ........................................................................................................................................................ 152 examples, 0 failures
Tutti i test sono passati, per cui siamo pronti a partire con l’upgrade.
Aggiornamento di Ruby e installazione di Rails 3
Per questo aggiornamento vogliamo usare l’ultima versione disponibile di Ruby ed avere una lavagna pulita per il nostro ambiente Rails 3, per cui useremo rvm per installare l’ultima versione di Ruby 1.9.2 ed installeremo in seguito Rails 3.0 su quest’ultima. Ci sono aggiornamenti frequenti a rvm, per cui assicuriamoci innanzitutto di avere l’ultima versione di questo strumento, lanciando rvm update prima ancora di installare Ruby:
$ rvm update rvm 0.1.28 by Wayne E. Seguin (wayneeseguin@gmail.com) [http://rvm.beginrescueend.com/] info: fetching rrvm-1.0.4.tar.gz info: Extracting rvm-1.0.4.tar.gz ... info: Installing rvm-1.0.4.tar.gz...
Una volta che rvm si è aggiornato, dobbiamo ricaricarlo. Lo possiamo fare, lanciando:
$ rvm reload
Ora possiamo usare in nuovo rvm per installare Ruby 1.9.2:
$ rvm install 1.9.2
Questo comando causa lo scaricamento, la compilazione e l’installazione dell’ultima versione di Ruby 1.9.2, al momento la p0. Al termine (ci può mettere diversi minuti), possiamo passare alla nuova versione e controllare che funzioni:
$ rvm 1.9.2 $ ruby -v ruby 1.9.2dev (2010-08-18 revision 29036) [i686-linux]
Ora che sappiamo che tutto è andato per il verso giusto, e che stiamo eseguendo sulla versione corretta di Ruby, possiamo installare il gem di Rails 3.0 release candidate.
gem install rails --pre
Si noti che dal momento che stiamo usando una versione di Ruby installata mediante rvm, non dobbiamo usare sudo per installare i gem.
Iniziamo l’aggiornamento
Ora abbiamo una installazione pulita di Ruby e di Rails 3.0, per cui siamo pronti a cominciare l’aggiornamento della nostra applicazione. Per aiutarci nel compito, esiste un plugin rails_upgrade ufficiale che può essere utilizzato per vedere quali siano i cambiamenti necessari. Per installarlo, dobbiamo per prima cosa ritornare indietro al nostro ambiente Rails 2. Quest’ultimo è già installato sulla nostra versione di sistema di Ruby, per cui per prima cosa dobbiamo ritornare a tale versione, lanciando:
$ rvm system
Ora possiamo installare il plugin:
$ script/plugin install git://github.com/rails/rails_upgrade.git
Questo comando mostrerà un po’ di utile documentazione dopo essersi installato. Uno dei comandi chiave che aggiunge è rake rails:upgrade:check
. Quando lanciamo questo comando dalla directory radice della nostra applicazione, esso mostra un elenco di tutte le cose che necessitano di un aggiornamento nella nostra applicazione. L’output è troppo lungo per essere mostrato qui, ma a titolo di esempio vi mostreremo la prima parte:
$ rake rails:upgrade:check (in /Users/asalicetti/rails/apps_for_asciicasts/ep225/railscasts) Old router API The router API has totally changed. More information: http://yehudakatz.com/2009/12/26/the-rails-3-router-rack-it-up/ The culprits: - config/routes.rb
Prima di iniziare l’aggiornamento vero e proprio, lanciamo rake rails:upgrade:backup
. Questo comando eseguirà un backup di alcuni file che probabilmente verranno sovrascritti durante l’aggiornamento.
$ rake rails:upgrade:backup (in /Users/asalicetti/rails/apps_for_asciicasts/ep225/railscasts) DEPRECATION WARNING: Rake tasks in vendor/plugins/hoptoad_notifier/tasks are deprecated. Use lib/tasks instead. (called from /Library/Ruby/Gems/1.8/gems/rails-2.3.8/lib/tasks/rails.rb:10) * backing up .gitignore to .gitignore.rails2 * backing up app/controllers/application_controller.rb to app/controllers/application_controller.rb.rails2 * backing up app/helpers/application_helper.rb to app/helpers/application_helper.rb.rails2 * backing up config/routes.rb to config/routes.rb.rails2 * backing up config/environment.rb to config/environment.rb.rails2 * backing up config/environments/development.rb to config/environments/development.rb.rails2 * backing up config/environments/production.rb to config/environments/production.rb.rails2 * backing up config/database.yml to config/database.yml.rails2 This is a list of the files analyzed and backed up (if they existed); you will probably not want the generator to replace them since you probably modified them (but now they're safe if you accidentally do!). - .gitignore - app/controllers/application_controller.rb - app/helpers/application_helper.rb - config/routes.rb - config/environment.rb - config/environments/development.rb - config/environments/production.rb - config/environments/staging.rb - config/database.yml - config.ru - doc/README_FOR_APP - test/test_helper.rb
Iniziamo ora l’aggiornamento, creando una nuova applicazione Rails 3 nella directory della nostra vecchia applicazione. Per fare ciò, ritorniamo nell’ambiente in cui gira Ruby 1.9.2:
$ rvm 1.9.2
Ora possiamo creare la nuova applicazione Rails 3 con:
$ rails new .
Dal momento che stiamo creando una nuova applicazione nella stessa cartella in cui ne esiste già un’altra, ci verrà chiesto se vogliamo sovrascrivere alcuni dei file già esistenti. Se non siamo sicuri sul fatto che un file possa essere sovrascritto o meno, possiamo utilizzare l’opzione d
per vedere le differenze fra il file esistente e quello che andrebbe a sostituirlo.
Noi comunque abbiamo già fatto il backup di tutti i file più importanti della nostra applicazione, per cui possiamo scegliere di sovrascrivere tutti i file in conflitto, tranne i seguenti:
/README
/app/views/layouts/application.html.erb
/public/javascripts/application.js
Gli altri file o non sono stati cambiati rispetto al default, o sono stati messi nel backup, permettendoci di fare un rollback man mano che procediamo.
La nostra applicazione è ora stata aggiornata a Rails 3.0, ma ovviamente ora dobbiamo analizzare i file che sono stati salvati prima dell’aggiornamento e recuperare tutto il codice che era presente in essi in quanto specifico dell’applicazione. Ciascun file salvato si trova nella propria cartella originale con l’estensione .rails2
, per cui dobbiamo semplicemente aprire ogni file con tale estensione e confrontarlo con la nuova versione dello stesso, copiando il codice da recuperare.
Partiamo dal file di route. La sintassi di routing è cambiata, ma la vecchia sintassi funziona ancora in Rails 3.0, sebbene sia stata deprecata. Per ora, possiamo semplicemente incollare gli instradamenti dal file di backup al nuovo file di route: li testeremo più avanti.
Poi diamo un’occhiata al file /config/environment.rb
. Sistemare quest’ultimo richiederà un po’ più di lavoro, visto che i cambiamenti in questa parte sono notevoli in Rails 3. Il file environment di default in Rails 3 è decisamente snello, dal momento che la maggior parte delle opzioni di configurazione sono state spostate nel file /config/application.rb
.
Ci sono tre sezioni nel file environment.rb
di backup che dobbiamo considerare. La prima è il codice di configurazione del time zone:
config.time_zone = 'Rome'
Questa linea di codice può essere spostata così com’è nel file /config/application.rb
.
La seconda parte riguarda il codice di sessione:
config.action_controller.session = { :session_key => APP_CONFIG['session_key'], :secret => APP_CONFIG['session_secret'] }
In Rails 3 questo codice appartiene ad un file di inizializzazione e, guardando dentro a quest’ultimo, vedremo il codice per configurare i dati di sessione dell’applicazione:
# Be sure to restart your server when you modify this file. Railscasts::Application.config.session_store :cookie_store, :key => '_railscasts_session' # Use the database for sessions instead of the cookie-based default, # which shouldn't be used to store highly confidential information # (create the session table with "rake db:sessions:create") # Railscasts::Application.config.session_store :active_record_store
Per ora possiamo lasciare questo codice così com’è. Potrebbe aver bisogno di modifiche più avanti, ma per ora queste non si rendono necessarie al fine di fare semplicemente andare di nuovo il sito con Rails 3.
Infine, in fondo al file envronment.rb
di backup, si trova il codice che configura i gem dell’applicazione:
config.gem "RedCloth", :lib => 'redcloth', :version => ">= 4.0" config.gem "coderay" config.gem 'acts-as-list' config.gem 'will_paginate' config.gem 'thinking-sphinx', :lib => 'thinking_sphinx' config.gem 'whenever', :lib => false
Questo codice può essere copiato nel Gemfile
della nostra applicazione, il quale si trova nella cartella radice dell’applicazione. La sintassi è leggermente cambiata, per cui il codice dovrà essere cambiato per riflettere tali modifiche; alla fine dovrebbe apparire analogo a questo:
gem "RedCloth", ">=4.0", :require => 'redcloth' gem "coderay" gem 'acts-as-list' gem 'will_paginate' gem 'thinking-sphinx', :require => 'thinking_sphinx' gem 'whenever', :require => false
Oltre a cambiare config.gem
in gem
su ogni riga, ci sono un paio di altre modifiche da fare. I numeri di versione sono ora forniti semplicemente come secondo argomento e l’opzione :lib
ora è diventata :require
.
Una volta sistemato il tutto, dobbiamo lanciare bundle install
per assicurarci che tutti i gem richiesti siano installati. Per ulteriori informazioni su bundler e sull’installazione dei gem, potete guardare o leggere l’episodio 201. Non vedremo come spostare il codice dai restanti file di backup ai corrispettivi nuovi, ma il tutto dovrebbe essere abbastanza semplice.
Coi gem installati proviamo a far partire il server per vedere se l’applicazione parte:
$ rails s /Users/asalicetti/.rvm/gems/ruby-1.8.7-p299/gems/will_paginate-2.3.14/lib/will_paginate.rb:39:in `enable_activerecord': undefined method `returning' for WillPaginate:Module (NoMethodError) # omissis i restanti errori …
Non funziona e il motivo è che c’è un errore nel codice del gem will_paginate
, dove a quanto pare abbiamo un metodo returning
non definito. Questo metodo era in effetti reso disponibile dal framework in Rails 2, ma è stato rimosso nella versione 3.
In casi del genere, vale la pena consultare il sito web RubyGems per vedere se è disponibile una nuova versione prerelease del gem, che sia compatibile con Rails 3.0. Nel caso del gem will_paginate
c’è e la possiamo usare aggiungendo un numero di versione per tale gem nel nostro Gemfile
:
gem 'will_paginate', '>=3.0.pre'
Se lanciamo nuovamente bundle install
, verrà installata la versione prerelease del gem in questione e saremo in grado di usarla nella nostra applicazione. Ora riproviamo a far partire il server:
$ rails s /Users/asalicetti/.rvm/gems/ruby-1.8.7-p299/gems/after_commit-1.0.7/lib/after_commit/active_record.rb:15:in `include_after_commit_extensions': undefined method `subclasses_of' for Object:Class (NoMethodError) from /Users/asalicetti/.rvm/gems/ruby-1.8.7-p299/gems/after_commit-1.0.7/lib/after_commit.rb:81 from /Users/asalicetti/.rvm/gems/ruby-1.8.7-p299/gems/thinking-sphinx-1.3.18/lib/thinking_sphinx.rb:2:in `require' from /Users/asalicetti/.rvm/gems/ruby-1.8.7-p299/gems/thinking-sphinx-1.3.18/lib/thinking_sphinx.rb:2
Questo errore sembra causato dal gem Thinking Sphinx. Di nuovo, essendoci una versione di prerelease disponibile, facciamo un’altra modifica al Gemfile
, simile a quella di prima e poi lanciamo di nuovo bundle install:
gem 'thinking-sphinx', '>=2.0.0.rc1', :require => 'thinking_sphinx'
Riproviamo il lancio del server:
$ rails s DEPRECATION WARNING: railtie_name is deprecated and has no effect. (called from <class:Railtie> at /Users/asalicetti/.rvm/gems/ruby-1.9.2-rc2/gems/will_paginate-3.0.pre/lib/will_paginate/railtie.rb:6) => Booting WEBrick => Rails 3.0.0 application starting in development on http://0.0.0.0:3000 => Call with -d to detach => Ctrl-C to shutdown server Exiting /Users/asalicetti/rails/apps_for_asciicasts/ep225/railscasts/config/routes.rb:2:in `block in <top (required)>': undefined local variable or method `map' for #<ActionDispatch::Routing::Mapper:0x00000101dffde8> (NameError)
Questa volta siamo riusciti ad andare un po’ oltre. Il server prova a partire, ma poi si lamenta per una variabile o un metodo non definito, chiamato map nei route. Dal momento che abbiamo incollato il vecchio codice di routing così com’era nel nuovo file, ci siamo portati dietro effettivamente una chiamata ad una variabile map
, ma tale variabile non viene effettivamente passata al blocco, dal momento che non è obbligatoria in Rails 3. Possiamo ancora continuare a usare la vecchia sintassi di routing, tuttavia, passandogli la variabile richiesta:
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
Quando proviamo a far partire ora il server, alla quarta volta, siamo fortunati e tutto parte senza errori:
$ rails s DEPRECATION WARNING: railtie_name is deprecated and has no effect. (called from <class:Railtie> at /Users/asalicetti/.rvm/gems/ruby-1.9.2-p0/gems/will_paginate-3.0.pre/lib/will_paginate/railtie.rb:6) => Booting WEBrick => Rails 3.0.0 application starting in development on http://0.0.0.0:3000 => Call with -d to detach => Ctrl-C to shutdown server [2010-08-22 19:30:22] INFO WEBrick 1.3.1 [2010-08-22 19:30:22] INFO ruby 1.9.2 (2010-08-18) [i686-linux] [2010-08-22 19:30:22] INFO WEBrick::HTTPServer#start: pid=9933 port=3000
Se visitiamo la home page in un browser ora, vedremo la home page di default, dal momento che quest’ultima è stata ricreata nella cartella /public
quando abbiamo creato l’applicazione Rails 3. Possiamo vedere da questa pagina che viene utilizzato l’ambiente corretto per la nostra applicazione:
Anche se ora la nostra applicazione parte, ci sono ancora molte cose da sistemare come risulta evidente rilanciando rails:upgrade:check. Dobbiamo anche rilanciare nuovamente le specs per vedere se passano. Ci occuperemo di tutte queste cose nel prossimo episodio.