Le novità di Rails 2.0

Venerdì 7 dicembre è stata rilasciata,
dopo oltre un anno di sviluppo, la release 2.0 (subito seguita dalla 2.0.1 e
ora dalla 2.0.2)
di Ruby on Rails, l’ormai celebre
framework per lo sviluppo di applicazioni Web reso noto da progetti come Basecamp e Twitter, e adottato anche per il remake di A List Apart.

L’ultima major release di Rails non introduce novità
rivoluzionarie, piuttosto porta il framework a maturazione, grazie alla
profonda pulizia del codice svolta dagli sviluppatori e alla rimozione di
funzionalità considerate non vitali che sono ora disponibili sotto forma di
plugin.

Ecco alcune delle novità.

ActionPack

ActionPack include le librerie ActionController e ActionView. Per
queste due librerie è stata data molta attenzione al miglioramento delle
performance e del supporto per applicazioni RESTful.

Asset Caching e Asset Server

Durante lo sviluppo di un’applicazione Web, può essere comodo avere più
file CSS e JS per ogni blocco separato di funzionalità (un file CSS per i
form, uno per gli header, uno per il reset delle impostazioni di default,
ecc), tuttavia questa pratica influenza negativamente le performance
dell’applicazione quando la si sposta in production, infatti, è necessario che
il client si connetta al server per scaricare ognuno dei singoli file e spesso
il tempo di setup della connessione è maggiore di quello necessario per
scaricare il file stesso. La soluzione ottimale sarebbe mantenere i file
separati in development e aggregarli in production, ed è
proprio ciò che è possibile fare con la nuova funzionalità di asset
caching
.

Per sfruttarla è sufficiente aggiungere l’opzione :cache a
stylesheet_link_tag e javascript_include_tag:

# Produrrà il file all.js in production
javascript_include_tag 'reset', 'header', 'form', 'application',
                       :cache => true
# Produrrà il file all.css in production
stylesheet_link_tag 'reset', 'header', 'form', 'application',
                    :cache => true

è ovviamente possibile aggregare i file in più gruppi diversi in caso non
vogliate avere un unico file all.*. Per farlo passate una stringa
come valore di :cache, invece di true.

Un altro problema legato all’asset (immagini, CSS, JS, ecc) in
cui ci si imbatte spesso è il limite, raccomandato dalla RFC2616, di effettuare al
massimo due connessioni contemporanee ad ogni hostname. Se il
limite può essere utile per chi fosse sprovvisto di una connessione a banda
larga, diventa utile per quelli che invece hanno a disposizione connessioni
sempre più veloci.

La soluzione sarebbe semplice: usare più server per servire i vari file
statici. Tuttavia, non tutti possono avere più server solo per questo scopo,
ma non temete, Rails 2 arriva in nostro soccorso con uno stratagemma davvero
ingegnoso. Aggiungendo la riga di codice seguente nel vostro file di
configurazione environments/production.rb direte a Rails di
utilizzare più asset server per servire i file:

config.action_controller.asset_host = "http://asset%d.vostrosito.it"

Rails non farà altro che sostituire %d con un numero da 0 a
3. Il calcolo del numero da usare non è casuale, ma prevede il calcolo
dell’hashing numerico del nome del file per essere sicuri che ogni
file venga servito sempre dallo stesso server.

L’ultimo passo è creare i record DNS appropriati, assicurandosi che tutti
puntino all’IP della vostra applicazione. Così facendo ingannerete i
browser nel pensare che i server siano 5, mentre in realtà le richieste vanno
tutte allo stesso server fisico.

In passato le opzioni per salvare i dati delle sessioni utente erano:

  • Memcached: potente ma complesso come uso e setup
  • file system: veloce, ma non scalabile oltre qualche centinaio di sessioni
  • database: meno veloce, ma indipendente dal numero di sessioni totale

Nessuna delle precedenti opzioni si è dimostrata ottimale, così gli
sviluppatori di Rails hanno deciso di introdurre un nuovo metodo di
salvataggio delle sessioni di default: i dati vengono salvati in un cookie che
quindi rimane sul client dell’utente. Unico limite: i dati non possono
superare i 4KB — cosa comunque sconsigliata, visto che la sessione dovrebbe
includere solo l’id dell’utente e il flash.

Per garantire che i dati non vengano manomessi dall’utente, Rails include
un digest dei dati stessi
concatenati ad una chiave segreta.

Multiview

Per meglio supportare il metodo respond_to, è stato separato
il formato della view dall’engine usato per il rendering:
action.format.engine. Ad esempio, index.rhtml
diventerà index.html.erb, e index.rxml sarà
index.xml.builder, o addirittura index.csv.erb per
supportare il formato CSV. Le uniche view il cui nome non è stato
cambiato sono i template RJS che rimangono .rjs.

Route

Il sistema di routing presenta 3 grosse novità.

La prima, e quella che più probabilmente romperà il supporto con
la vostra applicazione 1.2, è l’aggiunta automatica di un prefisso ai nomi
delle route. Come prefisso viene usata la catena dei
parent di una risorsa. Ad esempio:

map.resources :posts do |posts|
  posts.resources :comments
end

Definirà i seguenti metodi:

post_comments_path/url
post_comment_path/url
new_post_comment_path/url
edit_post_comment_path/url

La seconda novità è la possibilità di specificare un namespace per
un gruppo di risorse:

map.namespace :admin do |admin|
  admin.resources :posts
end

La dichiarazione dell’esempio precedente farà riferimento ad un controller
chiamato Admin::PostsController e definirà i seguenti metodi:

admin_posts_path/_url
admin_post_path/_url
new_admin_post_path/_url
edit_admin_post_path_url

Infine, sono state aggiunte le relazioni has_one e
has_many per i casi più semplici di risorse innestate:

# Invece di questo...
map.resources :posts do |posts|
  posts.resources :comments
end
# Questo...
map.resources :comments
map.resources :posts, :has_many => :comments

Un’altra novità è la nuova logica con cui funzionano i vari
link_to, redirect_to, form_for, ecc,
infatti nella maggior parte dei casi non sarà più necessario usare i metodi
definiti delle route, ma basterà passare gli oggetti stessi come
argomenti:

link_to(h(@post.title), @post)
redirect_to(@post)
form_for(@post)

Autenticazione HTTP

Migliorato il supporto per l’autenticazione HTTP Basic (purtroppo,
l’autenticazione Digest non è ancora supportata) con l’aggiunta del metodo
authenticate_or_request_with_http_basic:

class PostsController < ApplicationController
  USER_NAME, PASSWORD = "user", "secret"
  before_filter :authenticate, :except => [ :index ]

  def index
    render :text => "Tutti possono vedermi!" 
  end

  def edit
    render :text => "Solo chi ha la password può vedermi" 
  end

private
  def authenticate
    authenticate_or_request_with_http_basic do |user_name, password|
      user_name == USER_NAME && password == PASSWORD
    end
  end
end

Sicurezza

Uno degli attacchi più comuni degli ultimi anni è il cosiddetto CSRF che
consiste nell’ingannare l’utente facendogli cliccare un link, all’apparenza
innocuo, che in realtà esegue un qualche tipo di operazione su un sito a cui
l’utente in questione ha accesso. Includendo il metodo
protect_from_forgery in cima al vostro
ApplicationController eviterete questo tipo di attacchi: Rails
includerà in tutti i form un codice univoco per ogni utente che poi utilizzerà
per autenticare tutte le richieste POST,
PUT e DELETE.

Anche la protezione contro attacchi XSS è stata
migliorata grazie al passaggio da un approccio blacklist a uno
whitelist per la funzione sanitize. Tale funzione
risulta molto utile per quelle occasioni in cui volete permettere ai vostri
utenti di inserire codice HTML assicurandovi però che non inseriscano codice
maligno.

Gestione delle eccezioni

Con Rails 2 è stato introdotto un nuovo filtro per intercettare i vari tipi
di eccezione singolarmente e gestirle in modo appropriato. Ecco un semplice
esempio:

class PostsController < ApplicationController
  rescue_from   ActiveRecord::RecordNotFound, :with => :not_found

private
  def not_found
    ...
  end
end

ActiveRecord

ActiveRecord è la componente più grande di Rails ed in questa release ha
ricevuto sia nuove funzionalità che miglioramenti soprattutto per quanto
riguarda le performance.

Query caching

In passato, per evitare di dovere eseguire query ripetitive, mi sono
trovato spesso a dovere ricorrere al cosiddetto memoizing:

@posts ||= Post.find(:all)

Rails 2 introduce una sorta di memoization a livello di query: in
pratica, viene usata la query stessa come chiave in un hash, evitando di
dovere eseguire query identiche se un risultato è già presente
nell’hash.

Bisogna tenere presente due cose: poiché Rails è single-threaded
la cache è svuotata ad ogni richiesta, perché sarebbe impossibile mantenere la
cache sincronizzata tra più istanze di un’applicazione. Inoltre, la cache è
anche svuotata ogni volta che è eseguita una query INSERT,
UPDATE o DELETE, anche se la modifica non
influerenzerebbe i dati salvati in cache.

In caso dobbiate avere la certezza che una determinata query venga
effettivamente eseguita potete usare un blocco uncached:

uncached do
  @posts = Post.find(:all)
end

è sicuramente una soluzione semplicistica, ma assolutamente trasparente,
quindi non potrà che portare benefici alle vostre applicazioni.

Validation

Le validation vedono due novità: una è l’aggiunta dell’opzione :allow_blank, simile alla allow_nil, ma molto più utile nelle situazioni di tutti giorni — campi in form lasciati vuoti.
L’altra novità è l’estensione di validates_numericality_of che comprende ora molte opzioni interessanti:

validates_numericality_of :tre,
                          :equal_to => 3
validates_numericality_of :numero_di_mani,
                          :less_than => 3
validates_numericality_of :numero_di_mani,
                          :less_than_or_equal_to => 2
validates_numericality_of :eta,
                          :greater_than => 17
validates_numericality_of :eta,
                          :greater_than_or_equal_to => 18
validates_numericality_of :numero_dispari,
                          :odd => true
validates_numericality_of :numero_pari,
                          :even => true

è possibile anche combinare più condizioni, ammesso che queste non siano in
conflitto tra di loro.

Sexy migration

La sintassi delle migration è stata resa più concisa grazie
all’introduzione delle cosiddette sexy migration le quali
permettono di passare da questo:

create_table :post do |t|
  t.column :title, :string, :null => false
  t.column :body, :text, :null => false
  t.column :user_id, :integer, :default => 0
  t.column :published, :integer, :default => 0
  t.column :created_at, :timestamp
  t.column :updated_at, :timestamp
end

A questo:

create_table :post do |t|
  t.string  :title,
            :null => false
  t.text    :body,
            :null => false
  t.integer :user_id, :published,
            :default => 0
  t.timestamps
end

Foxy fixture

Infine, è anche stata resa più semplice la gestione delle fixture
che, come ben saprete se siete adepti del TDD, diventano
praticamente ingestibili dopo un po’ di tempo per la scomodità di dovere
mantenere la referenzialità delle foreign key tra più fixture
differenti. Rails 2 introduce infatti la possibilità di fare riferimento
al nome della fixture invece che al suo id, occupandosi della
generazione automatizzata degli id.

Ecco come è ora possibile definire le fixture:

# posts.yml
firstpost:
  title: First Post
  body: ...
# comments.yml
acomment:
  text: ...
  post: firstpost

Anche i timestamp come, ad esempio, created_at e
updated_at, vengono popolati automaticamente con
Time.now.

Funzionalità rimosse

Alcune funzionalità considerate non essenziali per il core di
ActiveRecord sono state rimosse e dei plugin da installare quando necessario
sono stati creati. Tra queste la pagination, considerata
comunque non ottimale — personalmente mi trovo molto bene con WillPaginate — e i vari
acts_as_*. Potete dare un’occhiata nell’SVN.

Sono anche stati rimossi gli adapter per i database meno
utilizzati, riducendo la rosa di default ai tre database più utilizzati:
MySQL, Sqlite e PostgreSQL. È comunque possibile installare gli altri
adapter che sono stati resi disponibili sotto forma di gem.

Debugging

Finalmente Rails 2 include il supporto per il debugging vero e proprio.

Per debuggare la vostra applicazione installate la gem ruby-debug,
posizionate una chiamata a debugger nel punto del codice che vi
interessa ed eseguite script/server con l’opzione
—debugger o -u. Appena l’applicazione
raggiungerà la chiamata a debugger, interromperà l’esecuzione e
aprirà una shell IRB.

Per maggiori informazioni su come usare il debugger potete fare
riferimento al sito
ufficiale
.

Riordinate la configurazione

Spesso il file environment.rb e i vari file in
environments vengono usati per mettere qualunque tipo di
dichiarazione che non si sa bene dove mettere. Di conseguenza si finisce con
file disordinati e poco organici. Fortunatamente è ora possibile
aggiungere tutti i file di configurazione necessari in
config/initializers, mantenendo così la configurazione di parti
distinte della nostra applicazione in file separati.

I file in config/initializers vengono caricati in ordine
alfabetico, dopo che l’intero environment di Rails e tutti i plugin
sono stati caricati. Potete usare il file
config/preinitializer.rb qualora dovesse servirvi eseguire del
codice prima che l’environment sia stato caricato, ma comunque
dopo l’inizializzazione delle classi base di Rails.

Caricamento ordinato dei plugin

Potete definire l’ordine in cui caricare i plugin aggiungendo quanto segue
al vostro environment.rb:

config.plugins = [:exception_notification, :acts_as_fulltextable, :all]

Notate il :all che specifica di caricare tutti gli altri
plugin, che altrimenti non verrebbero considerati.

Rake

Sono stati aggiunti anche molti task Rake che vi renderanno la vita molto
più semplice. Tra questi ce ne sono molti per gestire i database:
db:create, db:drop, db:reset e
db:rollback per annullare l’ultima migration.

Per avere una breve panoramica su tutti i vari task vi consiglio di
eseguire rake -T dalla root di un progetto Rails 2.

Installazione

Potete installare la gem come al solito:

gem install rails -y

Oppure eseguire il freeze all’interno della vostra applicazione:

rake rails:freeze:edge

Per aggiornare la vostra applicazione 1.2, vi consiglio di dare un’occhiata
ai log e scovare tutti i deprecation warning per sapere cosa
modificare prima di passare a Rails 2. Potete anche leggere queste note
sull’aggiornamento
.

Comments

  1. ok non novità rivoluzionari, ma comunque molto corpose, per quel che ho potuto vedere in questi mesi in cui ho seguito lo sviluppo

  2. Decisamente corpose, sono d’accordo.

  3. la cosa che mi rende felice è che siano state adottate nel core alcune cose sviluppate esternamente ma che sono decisamente meglio di quel che c’era prima, come le nuove migration.

    Peccato che non abbiano ancora fixato i bug che danno fastidio a me negli integration test 🙂

Policy per i commenti: Apprezzo moltissimo i vostri commenti, critiche incluse. Per evitare spam e troll, e far rimanere il discorso civile, i commenti sono moderati e prontamente approvati poco dopo il loro invio.