Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Le meravigliose novità di Rails 3

La fusione con Merb, l'ottimizzazione del framework, l'introduzione dell'algebra relazionale e molto altro nella nuova edizione di Rails
La fusione con Merb, l'ottimizzazione del framework, l'introduzione dell'algebra relazionale e molto altro nella nuova edizione di Rails
Link copiato negli appunti

Ricordo ancora con piacere quel 23 Dicembre 2008 in cui David Heinemeier Hansson annuncio che il team di Merb si sarebbe fuso con quello di Rails per dare vita alla terza versione del framework. Da allora sono trascorsi più di 365 giorni e più di 4000 commits sul repository ufficiale di Ruby on Rails, 250 persone hanno contribuito ad un preciso e pragmatico refactoring che ha ottimizzato tutti gli aspetti carenti della versione precedente fino al rilascio della versione 3.0 beta del 5 Febbraio.

Ma cosa è cambiato in Rails 3.0?

Il maggior sforzo nella creazione di questa release è stato incentrato nel trasformare Rails da un lento ed interdipendente insieme di librerie ad un veloce e agnostico framework, capace di esporre un set di API che ogni componente implementa correttamente.

Prendiamo per esempio il criticato rapporto elitario tra ActiveRecord e ActionPack; in Rails 2.0 questi colloquiano senza nessun meccanismo di astrazione, l'una invocando metodi dell'altra, rendendo estremamente difficile la sostituzione di una delle due librerie (ad esempio DataMapper al posto di ActiveRecord).

Nella versione 3.0 del framework è stato saggiamente inserito un nuovo oggetto 'ActiveModel' che incorpora le specifiche di comunicazione e che qualsiasi ORM può includere ottenendo istantaneamente (o quasi) il privilegio di colloquio con ActionPack.

Non volendo ridurre l'articolo ad una sequenza di modifiche apportate a Rails 3.0 proviamo a sperimentarne le principali su di una piccola applicazione di prova; per fare questo procuriamoci in primis una copia funzionante della beta del framework digitando da linea di comando (è necessario Ruby 1.8.7):

gem install tzinfo builder i18n memcache-client rack 
    rake rack-test rack-mount erubis mail text-format 
    thor bundler
gem install rails --pre

Concludiamo l'introduzione di questo articolo creando un applicazione Rails 3.0 col più classico set di comandi:

rails showcase_3_0

Uno degli aspetti più fastidiosi nella gestione di un applicazione Rails 2.x era la poca efficienza nel metodo di vendoring delle dipendenze; se ricordate l'elenco delle gemme richieste doveva essere effettuato all'interno del file 'config/environment.rb' con questa sintassi:

  config.gem "bj"
  config.gem "aws-s3", :lib => "aws/s3"

il che portava inevitabilmente al problema 'cosa succede se una specifica gemma è necessaria prima che l'applicazione chiami il file environment.rb?'.

Un esempio lampante in tal senso è costituito da 'rack', componente vitale al funzionamento del framework e necessario ben prima della routine di inizializzazione. La versione 3.0 di Rails risolve il problema delegando la gestione delle dipendenze ad un gemma: 'bundler' che a questo punto diventa l'unica che deve essere installata a priori sul computer.

Bundler legge le istruzioni relative a quali gemme installare all'interno del file Gemfile presente nella cartella principale di ogni applicazione Rails 3.0; apriamo ad esempio quello di showcase_3_0 ed esaminiamone il contenuto:

# ho rimosso le istruzioni commentate
source 'http://gemcutter.org'
gem "rails", "3.0.0.beta"
gem "sqlite3-ruby", :require => "sqlite3"

appare chiaro che in questo momento le uniche dipendenze richieste sono verso la versione 3.0 del framework e l'adapter verso il database di default, sqlite.

Supponendo di aver ricevuto showcase_3_0 via email in un archivio compresso, tutto quello che dovremmo fare per allineare il nostro ambiente alle specifiche dell'applicazione è eseguire nella root della stessa i comandi:

bundle install

La gemma, seguendo le istruzioni del file appena esaminato, popolerebbe una cartella di convenienza (normalmente all'interno della home dell'utente che ha lanciato la procedura) con tutte le dipendenze richieste preoccupandosi di includere tale cartella nei percorsi di load dell'applicazione e sollevando noi da tediose procedure di setup.

Se invece dovessimo essere noi a dover distribuire quanto creato non dovremmo far altro che digitare:

bundle pack

per includere i singoli file .gem che compongono l'albero delle dipendenze, specificato in Gemfile, all'interno della cartella vendor/cache dell'applicazione; tale percorso è infatti il primo ad essere scandagliato da bundler alla ricerca di gemme durante il comando install.

Nella seconda parte dell'articolo: strutturare le query con l'algebra relazionale e le migliorie sotto il profilo RESTful.

Arel e l'algebra relazionale

Grandi novità per ActiveRecord 3.0: l'ORM abbandona la sua sintassi per evolversi verso una strutturazione ancora più flessibile ed intuitiva abbracciando Arel, una gemma che implementa il concetto di algebra relazionale. Ma, cos'è l'algebra relazionale?

Il nome 'algebra' ci riporta ad operatori che tutti conosciamo ed utilizziamo quotidianamente: parlo dei classici simboli +, -, /, etc. che applicati al sistema dei numeri reali concorrono a definire la cosiddetta algebra elementare. Il termine algebra relazionale indica che in questo caso gli operatori insistono non sull'insieme R ma su quello delle relazioni; quindi l'algebra relazionale descrive una query come una successione di operazioni sull'insieme delle relazioni.

Prendiamo ad esempio il semplicissimo frammento SQL:

SELECT * FROM pals 
LEFT JOIN countries on countries.id = pals.country_id 
WHERE pals.age > 10

la sua controparte in algebra relazionale è la seguente:

Query in forma algebrica

Implementando questo meccanismo Arel rende di fatto possibile concatenare in modo intuitivo e trasparente un indefinito numero di operatori (selezione, join, ordinamento, etc.) sobbarcandosi il costo computazionale della loro traduzione in SQL.

Per sperimentare questa funzionalità creiamo gli oggetti Pal e Country in showcase_3_0 approfittando dell'occasione anche per notare i piccoli cambiamenti nella sintassi del comandi di generazione:

rails generate model Pal name:string country_id:integer 
rails generate model Country name:string 

Modifichiamo ora il file db/seeds.rb per specificare alcuni valori di default della tabella countries inserendo:

countries.create([{:name => 'Italy'},{:name => 'France'},{:name => 'Spain'}])

Definiamo la relazione (1, n) tra countries e pals, agendo sui rispettivi file nella cartella app/models come segue:

file country.rb

class Country < ActiveRecord::Base
  has_many :pals
end

file pal.rb

class Pal < ActiveRecord::Base
  belongs_to :country
end

Ora lanciamo i soliti comandi per la creazione ed il popolamento del database:

rake db:create && rake db:migrate && rake db:seed 

A questo punto possiamo invocare la console e sperimentare un po' le possibilità offerte dalla nuove API di ActiveRecord:

rails console  # per lanciare la console

>> Pal.create[{:name=>'Sandro',:country_id=>1},{:name=>'Francesca',:country_id=>1},{:name=>'Alberto',:country_id=>2}]
=> [#<Pal id: 2, name: "Sandro", country_id: 1, created_at:...
>> Pal.where(:country_id=>1)
=> #<ActiveRecord::Relation:0x1032350d8 @scope_for... 
>> Pal.where(:country_id=>1).all
=> [#<Pal id: 2, name: "Sandro", country_id: 1,...
>> Pal.where(:country_id=>1).to_sql
=> "SELECT     "pals".* FROM       "pals" WHERE     ("pals"."country_id" = 1)"
>> Pal.joins(:country).where(:countries=>{:name=>'Italy'}).order(:name).all
=> [#<Pal id: 3, name: "Francesca", country_id: 1, created_at: ...

Il meccanismo di generazione delle query, beneficiando del supporto di Arel, consente di concatenare fra di loro un numero indefinito di operazioni; inoltre finché su questa catena non viene invocato un metodo di iterazione (es: each) o di collezione (es: all) l'oggetto di ritorno è sempre di tipo ActiveRecord::Relation e quindi passibile di nuove concatenazioni.

Infine è possibile in ogni momento conoscere la forma dell'SQL risultante invocando sulla catena il metodo to_sql.

Con questi piccoli esperimenti abbiamo esplorato una piccola porzione dei benefici di questa nuova sintassi che nei prossimi mesi renderà interrogare le basi di dati ancora più semplice e flessibile; per chi stesse effettuando un porting di una applicazione 2.x ricordo infine che il vecchio approccio è ancora utilizzabile anche se 'deprecated'.

More REST in Rails

Rails 3.0 apporta alcune sostanziali modifiche alla sintassi di routes e renders; lo scopo è quello di sfruttare ancora di più le convenzioni insite nel paradigma RESTful per ridurre le linee di codice necessarie. Capita spesso infatti che, scrivendo un applicazione con Rails 2.x, quasi ogni azione di ogni controller si concluda con un respond_to più o meno complesso, come nell'esempio seguente:

def index
  @users = User.all
  respond_to do |format|
    format.html
    format.xml { render :xml => @users }
    format.json { render :json => @users }
  end
end

Per ovviare a questo la nuova versione del framework introduce il metodo respond_with che non fa altro che applicare un comportamento convenzionale basandosi su alcuni fattori quali il verbo con cui è stata fatta la richiesta, la presenza o meno di errori, il tipo di formato di output richiesto, etc. Ad esempio il codice esposto poc'anzi si traduce in:

def index
  @users = User.all
  respond_with(@users)
end

mentre un'azione di creazione classica può essere implementata in questo modo:

def create
  @user = User.new(params[:user])
  flash[:notice] = 'Utente creato correttamente' if @user.save
  respond_with(@user)
end

Notate come le due chiamate che concludono le due funzioni siano esattamente le stesse: respond_with(@user); la magia avviene infatti all'interno della classe ActionController::Responder, invocata dalla funzione respond_with, dove metodi specifici per ogni formato intervengono utilizzando convenzioni per stabilire la corretta reazione a fronte delle informazioni disponibili. Prendiamo per esempio il classico formato HTML e ispezioniamone il funzionamento:

def to_html
    default_render
  rescue ActionView::MissingTemplate => e
    navigation_behavior(e)
end

def navigation_behavior(error)
  if get?
    raise error
  elsif has_errors? && default_action
    render :action => default_action
  else
    redirect_to resource_location
  end
end

la funzione to_html tenta in primis di eseguire il template associato all'azione corrente, se fallisce (ad esempio perchè non esiste un template, come nelle azioni di create) invoca la funzione navigation_behavior che si comporta nel seguente modo:

  • se il verbo di chiamata è GET viene lanciata un eccezione (in quanto ogni azione chiamata in get dovrebbe tradursi nel rendering di un template a video)
  • se invece ci sono degli errori all'interno della risorsa (nel nostro caso @user) viene effettuato il render dell'azione di default (edit.html.erb per il verbo PUT e new.html.erb per il POST)
  • nel caso in cui nessuna delle due condizioni precedenti sia soddisfatta lo script esegue un redirect verso l'azione index del controller corrente

Chiaramente è possibile modificare questa convenzione, ad esempio operando come nell'esempio seguente, che riprende il codice scritto in precedenza forzando però il redirect verso una pagina a nostro piacere (nel caso di formato HTML):

def create
  @user = User.new(params[:user])
  flash[:notice] = 'Utente creato correttamente' if @user.save
  respond_with(@user) do |format|
    format.html { redirect_to users_url }
  end
end

Candies

Ci sono ancora tantissime cose di cui non ho fatto parola che sono cambiate in questa nuova release; tra queste le due principali si chiamano SafeBuffer e ActiveModel. Supponiamo di voler includere in una vista il seguente frammento di codice:

<%= "Saluto <b>tutti</b> i lettori di html.it" %>

Eseguendolo inaspettatamente otterremmo questo risultato:

Saluto <b>tutti</b> i lettori di html.it

Questo perchè dalla versione 3.0 Rails effettua in automatico l'escape di tutte le stringhe che non siano state prima marcate come html_safe, la corretta sintassi per giungere al risultato che ci aspettiamo diventa quindi:

<%= "Saluto <b>tutti</b> i lettori di html.it".html_safe %>

Chiaramente non ci troveremo spesso nella condizione di forzare una stringa ad html_safe, questo perché tutti gli helper del framework incorporano già questa caratteristica.

Passiamo ora alla seconda funzionalità: se ricordate l'introduzione dovreste già aver associato ActiveModel a quell'oggetto che, se incorporato, garantisce il diritto di colloquio con ActionPack; un altro grande vantaggio di questa scelta architetturale è quello di poter includere all'interno di semplici class Ruby metodi una volta patrimonio delle sole classi figlie di ActiveRecord::Base.

Nel listato seguente ad esempio una classe Ruby acquista la possibilità di validare le proprie variabili di istanza semplicemente includendo il modulo ActiveModel::Validations:

# file dog.rb
class Dog  # notate l'assenza di ActiveRecord::Base
  include ActiveModel::Validations # includo solo il componente delle validazioni
  
  validates_presence_of :age
  attr_accessor :age
  def initialize(age)
    @age = age
  end
end

Conclusioni

Spero di essere riuscito in questo articolo a dare una panoramica dell'immensa quantità di nuove features e paradigmi implementati nella versione 3.0 di Ruby on Rails. Personalmente ritengo che questa release abbia tutte le carte in regola per ridefinire un nuovo standard qualitativo in termini di framework per applicazioni Web a patto che dimostri di aver mantenuto, nonostante il pesante refactoring, tutta la stabilità delle precedenti versioni.


Ti consigliamo anche