41
Introduzione e installazione 1. 1. Introduzione a Ruby on Rails Che cosa è Ruby on Rails: l'ambiente completo per lo sviluppo di applicazioni web dinamiche 2. 2. Installazione su Linux Come installare Ruby on Rails su un sistema operativo Linux senza l'utilizzo di package manager 3. 3. Installazione su Windows Come installare Ruby on Rails su un sistema operativo Windows senza l'utilizzo di pacchetti 4. 4. Installazione rapida su Windows Come installare Ruby on Rails su un sistema operativo Windows utilizzando pacchetti unici La struttura di Rails 1. 5. Struttura di un'applicazione Rails - I Descrizione dettagliata della struttura delle directory di un'applicazione Rails: A-L 2. 6. Struttura di un'applicazione Rails - II Descrizione dettagliata della struttura delle directory di un'applicazione Rails: P-V 3. 7. Architettura di Rails - Il modello Uno sguardo all'architettura MVC. La gestione del modello di Ruby on Rails: la libreria ActiveRecord 4. 8. Architettura di Rails - Controller e Viste Uno sguardo all'architettura MVC. La libreria ActionController per il controller e ActionView per le viste 5. 9. Oltre il modello MVC I pacchetti ActionWebService e ActionMailer per la gestione dei Web service e dei servizi di spedizione e-mail 6. 10. Le convenzioni e la struttura del database I primi passi per la costruzione di un'applicazione: stabilire gli oggetti del modello, la nomenclatura, le tabelle del database Un Forum in Rails: primi passi 1. 11. La creazione dell'applicazione Come creare l'applicazione e stabilire le relazione degli oggetti 2. 12. La costruzione dell'applicazione Creazione dei controller utili alla visualizzare della homepage del forum, dell'indice dei messaggi e del loro contenuto 3. 13. Popolare i file HTML Come integrare codice Ruby all'interno dei file .rhtml che contengono il codice HTML di visualizzazione Un Forum in Rails: il layout e i form 1. 14. I Layout in Ruby on Rails

Ruby on Rails

Embed Size (px)

DESCRIPTION

fonte html.it

Citation preview

Page 1: Ruby on Rails

Introduzione e installazione1. 1. Introduzione a Ruby on Rails

Che cosa è Ruby on Rails: l'ambiente completo per lo sviluppo di applicazioni web dinamiche

2. 2. Installazione su Linux Come installare Ruby on Rails su un sistema operativo Linux senza l'utilizzo di package manager

3. 3. Installazione su Windows Come installare Ruby on Rails su un sistema operativo Windows senza l'utilizzo di pacchetti

4. 4. Installazione rapida su Windows Come installare Ruby on Rails su un sistema operativo Windows utilizzando pacchetti unici

La struttura di Rails1. 5. Struttura di un'applicazione Rails - I

Descrizione dettagliata della struttura delle directory di un'applicazione Rails: A-L 2. 6. Struttura di un'applicazione Rails - II

Descrizione dettagliata della struttura delle directory di un'applicazione Rails: P-V 3. 7. Architettura di Rails - Il modello

Uno sguardo all'architettura MVC. La gestione del modello di Ruby on Rails: la libreria ActiveRecord

4. 8. Architettura di Rails - Controller e Viste Uno sguardo all'architettura MVC. La libreria ActionController per il controller e ActionView per le viste

5. 9. Oltre il modello MVC I pacchetti ActionWebService e ActionMailer per la gestione dei Web service e dei servizi di spedizione e-mail

6. 10. Le convenzioni e la struttura del database I primi passi per la costruzione di un'applicazione: stabilire gli oggetti del modello, la nomenclatura, le tabelle del database

Un Forum in Rails: primi passi1. 11. La creazione dell'applicazione

Come creare l'applicazione e stabilire le relazione degli oggetti 2. 12. La costruzione dell'applicazione

Creazione dei controller utili alla visualizzare della homepage del forum, dell'indice dei messaggi e del loro contenuto

3. 13. Popolare i file HTML Come integrare codice Ruby all'interno dei file .rhtml che contengono il codice HTML di visualizzazione

Un Forum in Rails: il layout e i form1. 14. I Layout in Ruby on Rails

Page 2: Ruby on Rails

I layout di Ruby: file che servono per raccogliere codice condiviso fra le diverse pagine di un'applicazione

2. 15. I Partial di Ruby on Rails I partial di Ruby: porzioni di codice che consentono di includere codice in viste differenti

3. 16. I form in Ruby - I Come si creano moduli di interazione con l'utente in Ruby on Rails: la creazione del form e dell'azione

4. 17. I form in Ruby - II Come si creano moduli di interazione con l'utente in Ruby on Rails: gli helper della libreria ActionPack

Un Forum in Rails: utenti e sessioni1. 18. Gestire gli utenti

Creare i moduli di login e gli strumenti per la gestione delle registrazioni degli utenti 2. 19. Validazione dell'input

Come validare i dati inviati dagli utenti alle applicazioni scritte in Ruby on Rails 3. 20. Filtri e sessioni

Le sessioni in Ruby on Rails: come mantenere persistenti i dati di collegamento 4. 21. L'uso dell'oggetto "flash"

L'oggetto flash: come visualizzare elementi solo in alcune transizioni da pagina a pagina

Un Forum in Rails: template XML e il caching1. 22. I template Rxml - I

Come creare e pubblicare file XML e RSS in Ruby utilizzando i template Rxml 2. 23. I template Rxml - II

Come arricchire il modello o eliminare il layout dell'applicazione dai file XML 3. 24. Caching con Rails - I

Come gestire il caching delle pagine con Ruby on Rails: page caching 4. 25. Caching con Rails - II

Come gestire il caching delle pagine con Ruby on Rails: fragment caching e action caching

Conclusioni1. 26. Il resto di Ruby on Rails

Gli argomenti da approfondire che non sono trattati in questa guida e i limiti di Ruby on Rails

Page 3: Ruby on Rails

Introduzione a Ruby on RailsRuby on Rails, o più semplicemente Rails, è un ambiente completo per lo sviluppo web, che contiene al suo interno tutti gli elementi necessari alla realizzazione di siti complessi permettendo di gestire facilmente la creazione di pagine (X)HTML, di accedere semplicemente a database, e di integrare le funzionalità che caratterizzano le applicazioni web moderne, come le funzionalità AJAX ed i Web service.

Rails è un framework di nuova generazione che negli ultimi due anni ha creato un vero e proprio terremoto nella comunità degli sviluppatori, diventando spesso motivo di dibattito e ispirando la nascita di progetti analoghi realizzati con tecnologie differenti, come Cake per PHP, Trails per Java, Turbogears e Subway per Python e molti altri. Rails, in altre parole, ha introdotto un fattore di novità rilevante nell'ambito della programmazione Web.

Andando a guardare Rails nel dettaglio si scopre che esso usa tecniche di programmazione già sperimentate e non rivoluzionarie. La potenza di Rails è il racchiudere questi meccanismi all'interno di un modello di sviluppo nuovo promettendo di ridurre drasticamente i tempi di sviluppo, abolendo i file di configurazione, automatizzando tutto ciò che è possibile, usando dei Domain Specific Language, che spiegheremo più avanti, per esprimere con la massima concisione possibile i concetti.

L'autore originale tiene a ribadire che Rails non è stato sviluppato da subito come una piattaforma indipendente, ma che è il risultato dell'estrazione di funzionalità già provate in un'applicazione funzionante, e che ogni feature è mirata alla soluzione di problemi reali e non è frutto di ragionamenti astratti. L'opinione condivisa è che sia proprio questo a renderlo così efficace.

Indubbiamente parte del successo di Rails è dovuto al linguaggio con cui è scritto, ovvero Ruby, un linguaggio completamente ad oggetti di estrema espressività e potenza, che riesce a fondere in una sintassi semplice e chiara funzionalità ereditate da Perl, Python, Lisp e Smalltalk.

Per questo molti dei progetti mirati a riscrivere Rails in un altro linguaggio hanno poco senso, visto che è Ruby a determinare gran parte del feeling di questo ambiente.

Installazione su LinuxRuby e Rails funzionano su praticamente tutte le piattaforme Unix-like, tra cui Linux, Freebsd, Solaris, Mac OSX e altri. Sebbene in molti casi sia possibile ottenere un'installazione di RubyOnRails direttamente tramite il package manager della propria distribuzione (tipo Apt di Debian), di seguito descriveremo il metodo generale per ottenere un'ambiente di sviluppo funzionante su questo tipo di sistemi.

Ottenere RubyLa prima cosa da fare è procurarsi una versione funzionante di Ruby generalmente disponibile tramite qualsiasi sistema di pacchettizzazione. Il vantaggio di usare una versione di Ruby fornita dalla propria distribuzione o vendor sta nella possibilità di ottenere aggiornamenti in linea con il resto del sistema. Lo svantaggio è che spesso la versione pacchettizzata non è l'ultima: a volte si desidera avere una versione più aggiornata del pacchetto o delle funzionalità che di default non sono incluse (ad esempio Debian/Ubuntu divide Ruby in molti pacchetti, e OSX non ha tutte le librerie funzionanti). In particolare va notato che la versione attuale di Ruby è la 1.8.5, e che Rails funziona con questa versione, con la 1.8.2 e la 1.8.4 ma non funziona con la 1.8.3, che viene usata ad esempio in Ubuntu Hoary Hedgehog. Se desiderate installare Ruby da sorgenti recuperate l'ultima versione stabile da ftp.ruby-lang.org ed effettuate un'installazione standard con il classico approccio: scompattate il pacchetto tar.gz e poi date un colpo di sh configure && make &&

Page 4: Ruby on Rails

make install. In questo potete farvi aiutare dal nostro articolo dedicato alla compilazione dei programmi per Linux.

Ottenere RubyGemsRubyGems è un sistema di gestione per librerie ed applicazioni scritte in Ruby. In sostanza questo strumento svolge un ruolo simile a quello di Pear per PHP o CPAN per Perl. La differenza fondamentale sta nel fatto che con RubyGems è possibile mantenere contemporaneamente molte versioni differenti di una stessa libreria senza che questa vadano in conflitto, il che lo rende uno strumento particolarmente interessante per l'uso su macchine condivise.

Si può ottenere un archivio contenente il pacchetto da rubygems.rubyforge.org. Una volta ottenuto lo si dovrà scompattare e dopo essere entrati nella directory dare il comando ruby setup.rb per installare il tutto. Sarà poi possibile utilizzare il comando gem, verificate che esso funzioni provando alcuni dei suoi comandi come gem list o gem help.

Installare RailsL'installazione di Ruby On Rails da qui in poi è semplicissima: è sufficiente che diate il comando segnato in rosso:

$ gem install rails -yAttempting local installation of 'rails'Local gem file not found: Rails*.gemAttempting remote installation of 'rails'Updating Gem source index for: http://gems.rubyforge.orgSuccessfully installed Rails-1.1.2Successfully installed activesupport-1.3.1Successfully installed activerecord-1.14.2Successfully installed actionpack-1.12.1Successfully installed actionmailer-1.2.1Successfully installed actionwebservice-1.1.2Installing RDoc documentation for activesupport-1.3.1...Installing RDoc documentation for activerecord-1.14.2...Installing RDoc documentation for actionpack-1.12.1...Installing RDoc documentation for actionmailer-1.2.1...Installing RDoc documentation for actionwebservice-1.1.2...

Lo switch -y fa sì che vengano automaticamente installate tutte le dipendenze; Rails è infatti composto di diversi moduli usabili indipendentemente. Al momento dell'installazione verranno generate anche delle pagine HTML contenenti la documentazione di questi moduli che sarà possibile visualizzare subito lanciando il comando gem_server e puntando il browser su http://localhost:8808.

Ora è possibile verificare che Rails funzioni correttamente: tramite il comando rails nomeprogetto è possibile creare la struttura di una nuova applicazione. Dopo aver creato un nuovo progetto entrate nella directory e date il comando ruby script/server . Collegandovi tramite browser all'indirizzo http://localhost:3000 dovreste essere in grado di visualizzare la pagina che si complimenta per l'avvenuta installazione.

Potete notare come nei due casi l'indirizzo termini con :numero , cioè il servizio è accessibile tramite delle specifiche porte che non sono quelle standard del web (ovvero la 80 per HTTP e la 443 per HTTPS). Questo perché si assume che sulla macchina su cui lavorate possa già essere presente un Web server che occupi tali porte, e quindi se ne usano altre che ci si aspetta siano libere. Se lo

Page 5: Ruby on Rails

desiderate potete usare l'opzione -p numero con ruby script/server per avviare il Web server su di una porta differente, ad esempio la 80.

Notate che non è necessario installare un Web server. Rails è in grado di funzionare con diversi Web server, ma Ruby include una libreria che permette di creare semplici server multithread, quindi insieme al framework viene fornito un miniserver che è più che sufficiente per lo sviluppo e per piccoli carichi di lavoro. In particolare, tra i server professionali supportati i preferiti dagli sviluppatori sono lighttpd, un server molto leggero e veloce, e mongrel, un server scritto parzialmente in ruby e pensato per essere usato in configurazioni particolari. Se questi pacchetti sono installati script/server sarà in grado di individuarli ed usarli automaticamente senza bisogno di alcuna configurazione.

Se decidete di utilizzare Apache invece del Web server di default potete trovare informazioni dettagliate sulle configurazioni specifiche per diversi sistemi operativi e distribuzioni sul wiki di Rails, che tratta anche le varie possibilità di utilizzo, attraverso mod_ruby (Ruby che gira direttamente dentro Apache) o fastcgi (un modello in cui alcuni processi Ruby vengono tenuti sempre attivi ed associati ad ogni nuova richiesta, tramite un meccanismo di pooling). Queste sono operazioni che potrebbero essere necessarie se vorrete mettere in produzione le vostre applicazioni su host che non supportano Rails attivamente. Su quelli che lo fanno vi basterà, in generale, copiare semplicemente la cartella dell'applicazione in qualche directory remota.

Anche se Apache è decisamente più performante e potente del Web server minimale distribuito con Ruby, quest'ultimo è più che sufficiente per piccoli carichi di lavoro e, poiché è utilizzabile senza perdere nemmeno un minuto in configurazioni complicate, esso è la scelta più semplice per lo sviluppo locale.

Installazione databaseRails supporta molti Database differenti, ed ovviamente la procedura di installazione è differente per ognuno di essi. In generale è possibile installare un DBMS come MySQL o PostgreSQL tramite il proprio package manager, ed usare lo stesso package manager per installare i binding verso Ruby. Ad esempio per installare MySQL su Debian o Ubuntu basterà dare il comando apt-get install libmysql-ruby. Alcuni driver possono essere installati direttamente anche tramite RubyGems, come ad esempio i pacchetti mysql, sqlite3 e postgres-pr. Attenzione, in alcuni casi potrebbe essere necessario aver installato i pacchetti di sviluppo delle relative librerie, tipicamente contrassegnate dal suffisso -dev, tramite il package manager di sistema.

Per l'installazione di un database MySQL su Linux e Windows rimandiamo alla lezione dedicata della nostra guida a MySQL. Altre utili informazioni si trovano anche nelle guide all'installazione di PHP e MySQL su Windows e su Linux.

Installazione su WindowsRuby è in grado di funzionare piuttosto bene anche su Windows, e lo stesso vale per Rails. La presenza di un piccolo Web server integrato nell'applicazione fa sì che sia anche molto semplice cominciare a lavorare con Rails senza dover installare sistemi più ingombranti come Apache.

Esistono due modi per installare Rails: il primo è piuttosto simile a quello usato per l'installazione su Linux, il secondo è più veloce ma offre meno controllo.

Installare Ruby e Rails separatamenteIniziate scaricando il one-click installer, che è un classico file di Setup per Windows, la cui installazione richiede soltanto di cliccare sul bottone Avanti qualche volta. Questo pacchetto, grande

Page 6: Ruby on Rails

più di 20 MByte, include l'interprete e le sue librerie standard, RubyGems, un editor semplice ma potente (SciTE), la prima edizione del libro "Programming Ruby" e alcune estensioni utili.

Una volta scaricato ed installato è sufficiente aprire un prompt dei comandi del Dos e, con la connessione Internet attiva, dare il comando gem install rails -y per ottenere un ambiente funzionante.

Figura 1: La finestra del prompt del Dos

Ovviamente, a questo punto sarà necessario anche installare un Database manager. Usando RubyGems potete scaricare un pacchetto precompilato che include SQLite, e che permette di essere subito operativi. In rosso riportiamo sempre i nostri comandi digitati dal prompt di DOS di Windows.

C:\Documents and Settings\casa>gem install sqlite3-rubyAttempting local installation of 'sqlite3-ruby'Local gem file not found: sqlite3-ruby*.gemAttempting remote installation of 'sqlite3-ruby'Select which gem to install for your platform (i386-mswin32) 1. sqlite3-ruby 1.1.0 (mswin32) 2. sqlite3-ruby 1.1.0 (ruby) 3. sqlite3-ruby 1.0.1 (ruby) 4. sqlite3-ruby 1.0.1 (mswin32) 5. sqlite3-ruby 1.0.0 (mswin32) 6. sqlite3-ruby 1.0.0 (ruby) 7. sqlite3-ruby 0.9.0 (ruby) 8. sqlite3-ruby 0.9.0 (mswin32) 9. sqlite3-ruby 0.6.0 (ruby) 10. sqlite3-ruby 0.5.0 (ruby) 11. Cancel installation>

come vedete viene chiesto quale pacchetto si desidera installare, basterà premere "1" per scegliere il pacchetto più appropriato, cioè quello più recente per Win32. Potete altrimenti installare il DBMS che preferite, e separatamente i binding per Ruby. Il Wiki di Ruby on Rails è una preziosissima fonte di informazioni a riguardo.

Page 7: Ruby on Rails

Fate riferimento alla spiegazione dell'installazione su Linux per capire come verificare che tutto sia andato a buon fine.

Installazione rapida su WindowsL'alternativa per lo sviluppatore frettoloso è usare InstantRails. Si tratta di nuovo di un pacchetto autoinstallante, che contiene però non solo Ruby e RubyGems, ma anche Rails, Apache, MySQL, PhpMyAdmin e alcune applicazioni web dimostrative. Attenzione, c'è un'altra differenza chiave tra questo pacchetto e l'uso del One-Click installer: nel primo caso Ruby non viene integrato nel sistema (ovvero non vengono modificate variabili d'ambiente né il registro di configurazione), mentre nel secondo caso sì.

Questo significa che, usando InstantRails, se volete poter usare Ruby comodamente da riga di comando dovrete inserire a mano alcune funzioni aggiuntive, in particolare aggiungere ;C:\CartellaDiInstantRails\ruby\bin al path (accessibile tramite Risorse del Computer / Proprietà / Avanzate / Variabili D'ambiente).

È consigliabile anche creare una nuova variabile d'ambiente chiamata "RUBYOPT" che abbia per valore "rubygems", la quale farà si che Ruby provveda sempre a caricare il supporto per i pacchetti installati da RubyGems. Per farlo andate di nuovo nel pannello precedente e cliccate su Nuova, poi inserite nome e valore come appena detto.

L'alternativa è accedere alla console sempre tramite la piccola GUI di InstantRails, tramite il menu contrassegnato da una "I" e scegliendo poi "Rails Applications" e "Open Ruby Console Window". Ad ogni accesso le variabili verranno impostate correttamente, e l'utente si troverà nella directory rails_apps nella quale sono presenti gli esempi e nella quale creare le proprie applicazioni.

Per verificare che InstantRails sia correttamente installato vi basterà avviare l'applicazione con lo stesso nome. A questo punto dovrebbe aprirsi una piccola finestra di colore rossiccio, contraddistinta da una area di testo che contiene un log delle operazioni, da due bottoni che permettono di avviare/fermare/riavviare Apache e MySql e da un quadratino con una "I" in alto a sinitra che permette di accedere al menu di InstantRails. Cliccando su I / Rails Applications / Manage Rails Applications potrete vedere che alcune applicazioni d'esempio sono già installate e selezionandole potrete avviarle, andare a lavorare sui file o aprirne la console di lavoro.

Poiché è probabile che dopo l'ultima revisione di InstantRails sia stata rilasciata una versione più recente di Rails può essere consigliabile effettuare un aggiornamento delle librerie tramite RubyGems. Per farlo aprite la console come spiegato precedentemente, e poi usate il comando gem update -y.

Struttura di un'applicazione Rails - ILe applicazioni sviluppate con Rails hanno una peculiarità, ovvero sono tutte organizzate secondo una struttura comune. Questo è una conseguenza del fatto che il comando rails genera in automaticamente una serie di directory e file che forniscono una certa linea guida nello sviluppo, linea che se rispettata permette a Rails di effettuare molte cose automaticamente (ad esempio caricare i file, generarli ed individuarli a runtime e molto altro). Questa struttura comune permette anche di comprendere con semplicità il codice di progetti realizzati da altri, in quanto sono organizzati nella stessa maniera. Vediamo nel dettaglio il significato delle varie directory e dei file che contengono.

Page 8: Ruby on Rails

AppÈ il cuore dell'applicazione, quella che contiene il codice specializzato per l'applicazione web. All'interno di essa esistono 4 sottodirectory, ovvero controllers, models, views e helpers. Ognuna di questa cartella contiene un file per ogni elemento concettuale dell'applicazione, affronteremo nel dettaglio il significato di ognuno di essi nei prossimi capitoli. Per ora limitatevi a pensare che i modelli sono gli oggetti con cui si lavora (Persone, Articoli), le viste sono le singole pagine HTML, i controller sono quello che c'è in mezzo e gli helper sono piccoli metodi che aiutano a creare le viste.

ComponentsContiene componenti di alto livello riutilizzabili da diverse applicazioni, ad esempio si potrebbe avere un componente per gestire il login condiviso tra più applicazioni. In realtà i componenti vengono usati molto raramente perché Rails fornisce meccanismi migliori per rendere il codice riutilizzabile, e sono sostanzialmente deprecati.

ConfigInizialmente questa directory contiene informazioni relative a tre cose: gli environment, i dati di connessione al database e le route.

Gli environment sono una caratteristica di Rails particolarmente utile, in quanto permettono di utilizzare una stessa applicazione in tre modalità differenti, ovvero development, production e test. Questo perché un'applicazione Rails dovrebbe tipicamente essere sviluppata in questo modo, usando un database per lo sviluppo, uno per i test ed uno invece per quando l'applicazione è pronta ad essere aperta agli utenti finali.

Questi tre ambienti hanno caratteristiche predefinite differenti e possono essere configurati indipendentemente per usare ad esempio diversi database o abilitando e disabilitando l'autenticazione, attivando il caching delle pagine o dei file eccetera. Per configurare nel dettaglio ogni ambiente si deve intervenire sul file config/environment/nomeambiente.rb, mentre per configurazione condivise andrebbe modificato il file config/environment.rb. Nel corso di questa guida non interverremo su questi file, ed useremo sempre l'ambiente di sviluppo, che è quello predefinito.

Le opzioni di accesso al database vengono invece controllate da un singolo file, config/database.yml che è scritto in formato YAML ed ha tre blocchi di questo tipo:

nomeambiente: adapter: mysql database: applicazione_nomeambiente username: nome password: pass host: localhost

A seconda del database che decidete di usare potrete usare opzioni differenti (ad esempio, SQLite è un database file based, e non ha bisogno di host, username e password). Nel prossimo capitolo vedremo un esempio di configurazione.

Il file routes.rb contiene infine le associazioni tra un URL ed una determinata azione. Lavorando su questo file è possibile ad esempio far sì che un URL come http://foo.com/pages/title richiami il metodo search() della classe PagesController con argomento "title", oppure il metodo title() della classe PageManager, o altro.

Esso contiene delle associazioni predefinite per cui accedendo a /pages/view/name si

Page 9: Ruby on Rails

richiama il metodo view sul controller PagesController con argomento "name", ma potete modificare questa associazione di default se lo volete, o se è necessario per ottenere degli url migliori, in modo simile a quanto si fa con apache e mod_rewrite.

DbIn questa directory verranno mantenute informazioni sul database, listati SQL e codice ruby relativo alle migration. Le migration sono una potentissima funzionalità di Rails tramite le quali è possibile effettuare modifiche incrementali al proprio database facendolo evolvere col tempo, e con la possibilità aggiuntiva di poter andare indietro nel tempo se lo si desidera.

DocIl luogo dove raccogliere la documentazione relativa al progetto, inizialmente contiene solo un file "README" di default.

LibLe funzionalità non prettamente legate al lato web dell'applicazione vanno inserite in questa directory. Ad esempio se avete un modulo per convertire dei dati o per effettuare calcoli, o per interagire con sistemi esterni, questo è il posto in cui mantenerla. In generale se una cosa non è un modello, controller, helper o vista, va messa in lib.

LogContiene i log del webserver.

Struttura di un'applicazione Rails - IIContinuiamo la descrizione di come è strutturata un'applicazione scritta con Ruby on Rails.

PublicÈ la web root, cioè il posto in cui vanno messi file HTML statici, immagini, JavaScript e CSS. Per questi ultimi tre esistono delle specifiche sottodirectory. Anche in questo case si capisce che questa piccola convenzione permette alle applicazioni Rails di essere molto omogenee tra loro.

Scriptin questa cartella sono presenti alcuni piccoli programmi che permettono di fare molte operazioni utili. Abbiamo già visto, ad esempio script/server, ovvero il Web server integrato in Rails, utile per iniziare a sviluppare in tempi minimi e senza problemi. Tra gli altri script quelli di cui è particolarmente importante parlare sono script/generate e script/console. Il primo permette di generare gli scheletri di alcuni file, ad esempio se decidiamo di creare un modello per gli User potremo usare script/generate model User ed otterremo un file per il modello User, una nuova migration, un file dove scrivere i test per la classe ed uno dove inserire dei dati predefiniti.

Il fatto che questi file vengano generati automaticamente, così come anche le directory che li contengono, rende lo sviluppo molto più veloce nelle fasi iniziali.

Page 10: Ruby on Rails

script/console è invece uno strumento molto utile per interagire direttamente con l'applicazione, scrivendo codice "dal vivo". Dopo aver creato un'applicazione, anche se vuota, è già possibile utilizzarlo. Eseguendo quindi lo script (cioè scrivendo ruby script/console) vedrete un prompt di questo tipo:

$ ruby script/consoleLoading development environment.>>

In pratica ci viene fornito un prompt, esattamente come per una shell Unix o per un prompt DOS su Windows. I comandi utilizzabili in questo prompt però non sono quelli di una shell, bensì normale codice ruby:

>> 1+1=> 2>> puts("ciao")ciao=> nil>> array= [1,2,3,4]=> [1, 2, 3, 4]>> array[0]=> 1

Quando viene inserita un'istruzione e si preme invio la console la interpreta e mostra a schermo il risultato (nelle righe che cominciano per => ). nil è una costante che significa niente, nulla. Senza entrare nel dettaglio della sintassi di Ruby, cui sarà dedicata una guida a parte, notate il modo in cui vengono immessi numeri: stringhe e Array. Potete anche definire dei metodi in questo modo:

>> def ciao>> return "ciao gente">> end=> nil>> ciao=> "ciao gente"

Notate che in Ruby si possono omettere le parentesi quando non c'è ambiguità (ad esempio ciao e ciao() sono equivalenti in questo caso). Si possono poi definire classi in questo modo:

>> class Portiere>> def saluta>> return "salve signore">> end>> end=> nil>> portiere=Portiere.new=> #<Portiere:0x3b3c2f0>>> portiere.saluta=> "salve signore"

Qua ci sono due novità: primo, la sintassi per richiamare i metodi su di un oggetto è uguale a quella di Java, Python e C#, ovvero oggetto.metodo. Secondo, è possibile creare un oggetto chiamando il metodo new della classe, come in Portiere.new. Notate infine che per assegnare un valore ad una variabile è sufficiente scrivere variabile = espressione, senza doverla dichiarare.

Per uscire dalla console basta scrivere exit o quit. La console di Rails è in realtà basata su uno script che viene fornito insieme a ruby che si chiama irb (Interactive RuBy) ed ha molte funzionalità. Ovviamente Ruby è un linguaggio molto più potente di queste semplici istruzioni ma per ora sarà sufficiente imparare le nozioni descritte sopra.

Page 11: Ruby on Rails

TestQuesta directory è destinata a contenere i test che svilupperete per la vostra applicazione. In particolare essa conterrà test relativi ad ogni parte dell'applicazione (controller, modelli, viste) e test che invece attraversano tutti gli strati. Inoltre in questa directory si potranno mantenere dei dati di prova utili a far girare i test (le cosi dette fixture).

TmpOvviamente, contiene file temporanei, tra cui quelli relativi alle sessioni.

VendorIn un qualsiasi progetto ci si ritrova a utilizzare librerie di terze parti, ed esse vanno mantenute in questa directory. Quindi se nel nostro sviluppo utilizziamo la libreria RedCloth per creare HTML da semplice testo, essa andrà messa in vendor/redcloth. Un particolare tipo di librerie di terze parti sono i plugin, ovvero delle piccole librerie che contengono funzionalità che estendono Rails e che possono essere installate automaticamente tramite script/plugin, in questo modo:

$ ruby script/plugin listaccount_location http://dev.rubyonrails.com/svn/Rails/plugins/account_location/acts_as_taggable http://dev.rubyonrails.com/svn/Rails/plugins/acts_as_taggable/ browser_filters http://dev.rubyonrails.com/svn/Rails/plugins/browser_filters/ ...upload_progress http://dev.rubyonrails.com/svn/Rails/plugins/upload_progress/

$ ruby script/plugin install acts_as_taggable+ ./acts_as_taggable/init.rb+ ./acts_as_taggable/lib/README+ ./acts_as_taggable/lib/acts_as_taggable.rb+ ./acts_as_taggable/lib/tag.rb+ ./acts_as_taggable/lib/tagging.rb+ ./acts_as_taggable/test/acts_as_taggable_test.rb

Il plugin acts_as_taggable, che permette di creare in due righe modelli che siano taggabili, usati in moltissime applicazioni moderne, è stato automaticamente cercato ed installato, salvato in vendor/plugins, e può essere aggiornato indipendentemente dal resto dell'applicazione. La lista di plugin esistenti per Rails è enorme, dai sistemi di autenticazione agli effetti grafici all'integrazione con sistemi esterni.

Architettura di Rails - Il modelloLe applicazioni Rails utilizzano il noto approccio Model-View-Controller, o MVC, un metodo per organizzare il codice nei software che interagiscono con delle persone diventato ormai comune anche per la realizzazione di applicazioni web. Esso permette infatti di raggiungere un ottimo isolamento tra il codice che gestisce i dati e quello che li presenta all'utente, permettendo di estendere e modificare facilmente un'applicazione in qualsiasi momento.

Usando il modello MVC, un'applicazione viene divisa concettualmente in tre parti: modello, viste e controller. Il modello rappresenta gli oggetti del dominio applicativo, siano essi Utenti o Prodotti o Messaggi, insieme a tutte le regole relative ad essi, ad esempio il fatto che un Reparto abbia un solo Direttore e che il Direttore debba essere laureato e così via.

Page 12: Ruby on Rails

La libreria ActiveRecordIl layer che si occupa di gestire il modello in Rails è una libreria chiamata ActiveRecord, che permette l'accesso a numerosi database basati su SQL, da Oracle a PostgreSQL a SQLite. Grazie a questa libreria è possibile stabilire un'associazione tra classi scritte in Ruby e tabelle presenti nel database in modo da non dover mai usare SQL (rimane comunque possibile utilizzarlo per effettuare operazioni particolari, a costo di sacrificare la portabilità dell'applicazione su diversi DBMS). Se una tabella Products contiene dei campi relativi a prezzo e disponibilità, questi verranno resi accessibili come attributi della classe Product, automaticamente, e tramite questa classe sarà possibile eseguire ricerche, inserimenti e cancellazioni dei record nel database.

Per far sì che questa mappatura tra database ed oggetti funzioni è sufficiente seguire alcune semplici convenzioni, senza la necessità di descrivere esplicitamente le corrispondenze. Il concetto chiave in questo caso, applicato sistematicamente anche nel resto del framework, è compreso nell'acronimo DRY, che sta per 'don't repeat yourself' ('non ripeterti'). Quello che si intende è che ogni informazione deve essere espressa, per quanto possibile, una sola volta, quindi niente file XML dedicati ad associare tabelle e classi, via le istruzioni per il caricamento dei file, e nessuna descrizione del modello in forma di codice, in quanto è già presente una rappresentazione dello stesso nel database.

Ad esempio, supponiamo di voler creare un'applicazione molto semplice in stile del.icio.us, nella quale poter inserire dei link ed un piccolo commento. Avremo bisogno quindi di un database per contenere ogni elemento e di un'interfaccia in HTML che ci permetta di visualizzare gli elementi presenti e di inserirli tramite una form appropriata, più, naturalmente, il codice che si occupa di elaborare i dati inviati ed inserirli nella tabella appropriata.

Creiamo dunque un database di nome minidel_development, ed una tabella chiamata entries con 4 campi: id, url, comment e created_on, con un codice simile a questo (il codice è per MySQL, ma è facilmente portabile su altri RDBMS):

CREATE TABLE entries ( id INT NOT NULL AUTO_INCREMENT , url VARCHAR( 100 ) NOT NULL , comment TEXT NOT NULL , created_on DATE NOT NULL , PRIMARY KEY ( id ));

Tutto quello che dobbiamo fare per manipolare oggetti di questo tipo è creare un modello, ovviamente dopo aver creato un'applicazione Rails. diamo quindi il comando Rails minidel ed entriamo nella cartella minidel appena creata.

Una breve nota sulla configurazione del database: potete indicare alla libreria ActiveRecord quale DBMS usare e con quali parametri, tramite il file database.yml nella directory config. Aprendolo troverete tre spezzoni di questo tipo:

development: adapter: mysql database: minidel_development username: root password: segreta host: localhost

Se avete seguito le istruzioni alla lettera questo andrà bene, altrimenti provvedete a cambiare le opzioni sostituendo i valori appropriati. Come abbiamo già detto precedentemente, il file contiene un blocco di codice per ogni ambiente di lavoro, cioè environment, production e test, ma per noi sarà sufficiente usare quello di sviluppo.

Page 13: Ruby on Rails

Abbiamo già visto che esiste una directory script contenente molti piccoli programmi utili. Uno dei più interessanti è script/generate che serve a creare alcuni file con contenuti predefiniti per diversi scopi. Usiamo dunque questo script per creare il codice del modello:

$ ruby script/generate model Entry exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/entry.rb create test/unit/entry_test.rb create test/fixtures/entries.yml

Potete notare che viene inizialmente verificata la presenza di alcune directory, e poi vengono creati tre file: gli ultimi due sono relativi alla scrittura di test e per ora li ignoreremo, entry.rb invece contiene il codice della nostra classe, che è semplicemente questo:

class Entry < ActiveRecord::Base end

il codice definisce una classe chiamata Entry, che è una sottoclasse della classe Base contenuta nel modulo ActiveRecord. L'equivalente in Java sarebbe una cosa come

public class Entry extends activerecord.Base {}

Non abbiamo alcun bisogno di aggiungere altro, tutte le informazioni sono già disponibili nel database, e sarà ActiveRecord a occuparsi di creare tutte le funzionalità al posto nostro. Per sperimentare useremo un altro utilissimo script, ovvero script/console, il quale ci permette di avere una piccola shell interattiva nella quale usare direttamente il codice Ruby. Guardate questa piccola sessione di prova:

$ ruby script/consoleLoading development environment.>> Entry.find_all=> []>> entry=Entry.new=> #<Entry:0x3991690 @new_record=true, @attributes={"created_on"=>nil, "url"=>"", "comment"=>""}>>> entry.url = "http://www.html.it"=> "http://www.html.it">> entry.comment = "finalmente hanno aggiunto una guida su rubyonrails!" => "finalmente hanno aggiunto una guida su rubyonrails!">> entry.save=> true>> Entry.find_all=> [#<Entry:0x397ea90 @attributes={"created_on"=>"2006-06-11", "url"=>"http://www.html.it", "id"=>"1", "comment"=>"finalmente hanno aggiunto una guida su ruby on Rails!"}>]

Le linee contrassegnate da >>, che per facilitare la lettura abbiamo segnato in rosso, sono quelle in cui scrive l'utente, mentre quelle indicate da => mostrano il risultato dell'istruzione.

Inizialmente proviamo a cercare tutti gli elementi del database, usando il metodo find_all della classe Entry, e ci viene restituito un Array vuoto, indicato con "[]". Attenzione, find_all è un metodo, ed in realtà nella maggior parte dei linguaggi andrebbe richiamato con delle parentesi alla fine, ma in Ruby, quando è ovvio che si sta chiamando una funzione, queste possono essere omesse.

Poi creiamo un nuovo oggetto, usando il metodo new, e lo assegniamo alla variabile entry, in

Page 14: Ruby on Rails

seguito ne impostiamo gli attributi url e comment, ed infine lo salviamo con il metodo save. A questo punto, proviamo di nuovo a cercare tutti gli elementi, e correttamente, ci viene restituito un oggetto identico. Provate a fare degli esperimenti con la console, per uscire è sufficiente che usiate l'istruzione exit.

Per ora non vi preoccupate dello strano formato in cui viene mostrato l'oggetto, in seguito spiegheremo anche quello, ma guardate come i campi del nostro oggetto vengano aggiornati. In particolare si può notare che dopo il salvataggio il nostro oggetto ha ottenuto magicamente anche un valore corretto per created_on, ed infatti ActiveRecord è in grado di individuare alcuni attributi particolari e gestirli automaticamente, risparmiandoci molte righe di codice.

Un discorso speciale va fatto per l'attributo id, il quale viene gestito magicamente da Rails ma non viene in genere mostrato all'utente. Questo attributo infatti è pensato per essere usato solo da ActiveRecord, e noi dovremo preoccuparci generalmente solo di usarlo come chiave di ricerca, come vedremo più avanti.

Architettura di Rails - Controller e VisteNel modello MVC la gestione dell'interazione con l'utente è demandata ai Controller, rappresentati dalla libreria ActionController, che si occupa di fornire le funzionalità di alto livello dell'applicazione. Ogni Controller è una normale classe, ed ogni metodo pubblico definito in questa classe corrisponde ad un'azione specifica.

Rails contiene un meccanismo portabile per la creazione di URL user friendly ed effettua una trasformazione predefinita da un URL come http://host/page/show/1 in una chiamata al metodo show di un oggetto di classe PageController con un parametro 1. In ogni controller di Rails possono essere inoltre essere inserite informazioni relative a filtri da applicare prima o dopo ogni operazione, che permettono di realizzare in poche righe l'autenticazione, la compressione delle pagine, la gestione del caching e molto altro.

Ad ogni azione definita in un Controller corrisponde una Vista, ovvero ciò che l'utente effettivamente si trova davanti.

Per definire una vista con Rails si usano file con estensione .rhtml che contengono delle direttive scritte in Ruby immerse all'interno di codice XHTML, in modo simile a quanto si fa con PHP, JSP o HTML::Mason. Evidentemente, differenti viste possono far uso delle stesse funzionalità fornite da metodi del controller, ad esempio in un sito di informazione è possibile utilizzare una funzionalità che individua le ultime dieci notizie sia per realizzare l'home page sia per realizzare un feed RSS.

Il modo più semplice di cominciare a lavorare con controller e viste è utilizzare nuovamente a script/generate.

$ ruby script/generate controller Home exists app/controllers/ exists app/helpers/ create app/views/home exists test/functional/ create app/controllers/home_controller.rb create test/functional/home_controller_test.rb create app/helpers/home_helper.rb

Come vedete l'output è simile a quello per la creazione del modello ma in questo caso i file importanti sono due: home_helper.rb e home_controller.rb. Su primo torneremo più avanti, il secondo è molto simile a quello per il modello:

class HomeController < ApplicationController

Page 15: Ruby on Rails

end

La superclasse, in questo caso, è ApplicationController che è stata anch'essa creata dallo script. Il compito di questa classe è di raccogliere funzionalità condivise nell'intera applicazione.

Molto spesso però è possibile associare un controller direttamente ad un modello, per rendere possibili le operazioni CRUD (cioè creazione, lettura, modifica e cancellazione) sugli oggetti relativi. Rails fornisce la possibilità di generare automaticamente un insieme di controller, viste e modello che permettono di effettuare tali operazioni trami le scaffolding.

Per poter cominciare ad utilizzare il nostro sistema ci basta aggiungere una riga di codice al controller Home, che possiamo trovare in app/controllers/home_controller.rb :

class HomeController < ApplicationControllerscaffold :entryend

Questa linea può sembrare strana ma in realtà è molto semplice. Come abbiamo già detto, Rails si fonda sul concetto di domain specific language,ovvero di minilinguaggi che servono ad esprimere in modo conciso dei concetti relativi all'applicazione: scaffold non è altro che un normalissimo metodo, solo che in Ruby:

• possiamo applicare un metodo senza mettere le parentesi, se non c'è ambiguità • non c'è differenza tra dichiarazioni e parte eseguibile del codice, quindi possiamo chiamare

dei metodi dove vogliamo • :entry, come ogni altra cosa composta da ":" seguita da una parola, è un simbolo, senza

entrare nei dettagli potete pensare che un simbolo serve a chiamare una cosa per nome, o a specificare il valore di un'opzione.

Queste peculiarità della sintassi di Ruby fanno sì che sia semplice scrivere questo tipo di metodi che assomigliano a macro o parole chiave.

Il metodo scaffold non fa altro che generare automaticamente una serie di azioni nel controller che ci permettono di manipolare gli oggetti di tipo Entry. Per provare usate di nuovo script/server e dirigetevi su http://localhost:3000/home, dove troverete ad aspettarvi la vostra prima Entry, creata precedentemente da console.

Per orientarvi meglio nel mondo di Rails, metodi come scaffold vengono detti class method, in quanto sono metodi della classe e non di un oggetto. Anche i metodi find e new che avevamo visto lavorando con il modello erano metodi di classe. Alternativamente, spesso i metodi come questo che aggiungono "magicamente" funzionalità ad una classe vengono chiamati macro, anche se il termine è inesatto. Tenete a mente queste informazioni se effettuate una ricerca o se dovete chiedere aiuto in mailing list ed altro.

Tornando alla pratica, provate a verificare un po' le funzionalità generate automaticamente per capire quanto lavoro è stato svolto automaticamente da Rails. Potete visualizzare gli oggetti, crearne di nuovi ed andare a modificare quelli esistenti, o cancellare quelli più vecchi. Se create molti elementi verrà addirittura generato un sistema di paginazione che li presenti in schermate differenti per non avere una sola pagina gigante.

$ ruby script/generate scaffold Entry exists app/controllers/ exists app/helpers/ create app/views/entries exists test/functional/dependency model exists app/models/ exists test/unit/ exists test/fixtures/ identical app/models/entry.rb

Page 16: Ruby on Rails

identical test/unit/entry_test.rb identical test/fixtures/entries.yml create app/views/entries/_form.rhtml create app/views/entries/list.rhtml create app/views/entries/show.rhtml create app/views/entries/new.rhtml create app/views/entries/edit.rhtml create app/controllers/entries_controller.rb create test/functional/entries_controller_test.rb create app/helpers/entries_helper.rb create app/views/layouts/entries.rhtml create public/stylesheets/scaffold.css

Potete notare come siano stati creati molti più file, in particolare un file con estensione per ogni azione, ed un file CSS di default. Anche la classe relativa al modello viene creata se non era stato fatto precedentemente.

Se ora provate ad avviare script/server e ad accedere a http://localhost:3000/entries potrete vedere come sia già possibile creare, modificare ed eliminare degli elementi. I file creati non sono pensati per rimanere stabili fino alla fine del progetto ma per essere una base su cui lavorare.

Il file controller che è stato generato questa volta è decisamente più complesso, e non preoccupatevi se non lo comprendete appieno adesso, nel corso della guida apprenderete tutte le informazioni necessarie a comprenderlo, e scoprirete che in realtà è molto semplice.

Oltre a queste funzionalità elementari le viste, gestite dalla libreria ActionView, offrono meccanismi avanzati per il riutilizzo di codice, tramite l'uso di viste parziali, layout, metodi helper pensati per generare xhtml (ad esempio realizzare un form per dei dati anagrafici in una riga) o dedicati alla creazione di interfacce AJAX-based, sulle quali torneremo più avanti. Definire helper è semplicissimo, basta definire dei semplici metodi all'interno di file specifici, che come abbiamo visto, vengono creati automaticamente quando creiamo un controller.

Per le viste è possibile utilizzare linguaggi di template differenti se lo si desidera, anche se questi potrebbero essere leggermente meno integrati. Questo è un concetto che vale in tutto l'ambiente, e che ritorna continuamente: convention over configuration, significa che finché si seguono le idee suggerite dagli autori del framework non si dovrà configurare nulla, e tutto funzionerà automaticamente, come già visto, ad esempio per costruire l'associazione tra oggetti del database e classi, o per quella tra URL e controller. È però sempre possibile decidere di non seguire le convenzioni, al prezzo di dover specificare esplicitamente quello che si desidera.

Oltre il modello MVCOvviamente Rails non è solo ciò che abbiamo visto. Durante l'installazione con RubyGems ad esempio, potreste aver notato che abbiamo installato dei pacchetti relativi ai Web service e alle email.

Utilizzando Rails è possibile realizzare Web service tramite le tecnologie SOAP o XML-RPC in modo molto semplice, sfruttando i controller ed i modelli definiti precedentemente e permettendo uno sviluppo estremamente rapido, anche se la comunità degli sviluppatori Rails tende a preferire Web service di tipo REST, ovvero nessun formato o protocollo complesso ma semplici richieste HTTP che restituiscano risultati facilmente utilizzabili. Spesso è però necessario poter fornire servizi di questo tipo e il pacchetto ActionWebService serve proprio a questo.

ActionMailer è invece pensato per tutte quelle situazioni in cui è necessario inviare email, come può essere la verifica della validità di un account in fase di registrazione. Sebbene alcune tecnologie moderne abbiano diminuito l'importanza dell'email (ad esempio l'uso dei feed ha spesso soppiantato la necessità delle newsletter) ci sono numerosi casi in cui esse rimangono la soluzione

Page 17: Ruby on Rails

migliore, quando è necessario fornire notifiche relative a spedizioni o accrediti o problemi analoghi. ActionMailer si integra perfettamente con il resto di Rails, ad esempio è possibile usare dei template rhtml per creare email standardizzate ma con elementi variabili.

Rails possiede inoltre una funzionalità interessantissima che non ha riscontro nella maggior parte dei framework per lo sviluppo web, ovvero le migration. Questa tecnologia permette di far evolvere la propria base di dati in modo riproducibile e permette inoltre di effettuare dei rollback se la migrazione dei dati non ha successo. Questo significa che è possibile cominciare a sviluppare con un modello molto semplice e poi modificarlo secondo le proprie necessità in modo facile e sicuro,ed è un servizio straordinariamente utile nel caso in cui si voglia effettuare l'upgrade di un'applicazione Rails che è stata già messa in uso in diversi posti.

Il framework offre poi un'infrastruttura straordinaria per lo sviluppo test driven, sostenuto dai seguaci delle metodologie agili ed in particolare da chi segue le teorie dell'extreme programming. Rails rende straordinariamente semplice scrivere test, il che permette di sviluppare software migliore e più facile da modificare.

Infine, Rails offre due differenti funzionalità per il riuso di codice tra applicazioni differenti. Il primo sono i componenti, ovvero interi pacchetti di funzionalità che possono essere trasportati da un'applicazione all'altra semplicemente copiandoli in una directory. Il secondo è il meccanismo dei plugin, che permette di andare a modificare funzionalità anche basilari del framework permettendo un'integrazione perfetta di nuovi strumenti nella base esistente.

Come abbiamo già sottolineato esistono dozzine di plugin per gli usi più differenti, dai pacchetti per aggiungere un wiki all'applicazione alla creazione di mappe con google maps, dall'indicizzazione full text del database alla realizzazione di wizard. La disponibilità di una comunità molto estesa e vitale che rilascia questi plugin liberamente garantisce la possibilità di includere funzionalità complesse in pochi attimi, ed è un vantaggio immenso per lo sviluppo di applicazioni in tempo molto ridotto.

Le convenzioni e la struttura del databasePer osservare più da vicino il funzionamento di questo ambiente realizzeremo un'applicazione non troppo complessa ma con dei punti interessanti, ovvero un semplice Forum che permetta di creare nuovi argomenti di discussione, di aggiungere dei messaggi e di permettere agli utenti di registrarsi ed effettuare il login.

Il primo passo da fare è stabilire quali sono gli oggetti del modello. Abbiamo ovviamente necessità di avere delle entità che rappresentino i messaggi, quindi creeremo una tabella messages. Ogni messaggio dovrà appartenere ad un determinato topic, per cui la nostra seconda tabella sarà appunto topics. Infine, per identificare chi invia i messaggi creeremo una tabella authors.

Due parole sulla nomenclatura: come abbiamo già detto, Rails preferisce le convenzioni alle configurazioni, e quindi cerca di evitare allo sviluppatore il peso di dover specificare l'associazione tra tabelle e classi. Ovviamente però, ActiveRecord, la libreria che gestisce il database, deve avere un modo per capire da solo questa relazione, e questo modo è la lingua inglese.

Infatti, per far sì che ActiveRecord trovi da solo le tabelle, è sufficiente che esse siano chiamate con il plurale del nome della classe e che siano scritte in minuscolo. La tabella messages sarà dunque mappata sulla classe Message, authors su Author e topics su Topic. Il meccanismo di ActiveRecord che si occupa di questa conversione è molto più intelligente di quel che potrebbe pensare, e ad esempio è in grado di capire che il plurale di "person" è "people" o che il plurale di "status" è "statuses".

Se questa convenzione non vi piace potete, come sempre, non seguirla, a patto di specificare esplicitamente il nome delle tabelle all'interno della classe, guardate la documentazione di

Page 18: Ruby on Rails

ActiveRecord per maggiori dettagli. Per ogni autore ci interessa mantenere informazioni sul nickname e sulla password. Un topic avrà un titolo ed un campo che indica quando è stato aggiornato l'ultima volta e un messaggio avrà un corpo e dei riferimenti al proprio autore ed al proprio topic. Il DBMS che utilizzeremo in questo esempio è MySQL perché è quello più comune ed è disponibile con InstantRails, ma il codice di definizione delle tabelle per altri non cambia molto da questo:

CREATE DATABASE hforum_development;

CREATE TABLE messages ( id int(11) NOT NULL AUTO_INCREMENT, body text NOT NULL, created_at DATETIME NOT NULL, author_id int(11) NOT NULL REFERENCES authors (id), topic_id int(11) NOT NULL REFERENCES topics (id), PRIMARY KEY (id)); CREATE TABLE authors ( id INT NOT NULL AUTO_INCREMENT , name VARCHAR( 20 ) NOT NULL , password VARCHAR( 40 ) NOT NULL , PRIMARY KEY ( id ));

CREATE TABLE topics ( id int(11) NOT NULL AUTO_INCREMENT, title varchar(60) NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (id));

Di nuovo una nota sui nomi: per default, le foreign key dovrebbero essere indicate come nometabella_id; updated_at, rappresenta invece uno degli attributi speciali che vengono gestiti in automatico da ActiveRecord, e che contiene un timestamp dell'ultimo accesso all'oggetto. Gli attributi speciali relativi al tempo sono created_at/created_on e updated_at/updated_on: i primi due registrano la creazione di un oggetto, mentre i secondi l'aggiornamento. La differenza del suffisso indica se essi sono di tipo DATE o DATETIME, per ricordarlo basta tradurre in italiano come creati "il" o creati "alle".

La creazione dell'applicazionePossiamo ora passare alla creazione dell'applicazione, come abbiamo già visto in precedenza, tramite il comando Rails hforum. Dopo averlo fatto usiamo script/generate model per creare i modelli per tutte e tre le tabelle (ricordate di modificare config/database.yml se non usate la stessa configurazione di questo articolo) .

Ora però ci troviamo di fronte ad un problema: come rappresentare le relazioni che hanno tra loro gli oggetti? Sappiamo che un messaggio corrisponde ad un autore, e ad un topic, ma ActiveRecord non ha modo di sapere se queste relazioni siano effettivamente tra uno ed uno o tra uno e molti.

Poiché dunque le capacità di mappatura automatica del database non sono più sufficienti, dobbiamo cominciare a scrivere del codice. Fortunatamente, questo codice è molto semplice: modifichiamo le classi in modo che risultino fatte in questo modo

Page 19: Ruby on Rails

# app/model/author.rbclass Author < ActiveRecord::Base has_many :messagesend

has_many :messages significa "ha molti messaggi", cioè ad un autore sono collegati N messaggi. Questo metodo farà si che per gli oggetti Author ci sia un attributo messages che restituisce una lista di messaggi.

# app/model/message.rbclass Message < ActiveRecord::Base belongs_to :author belongs_to :topicend

belongs_to :author significa che un messaggio appartiene ad un autore. In un certo senso si tratta della relazione inversa alla precedente. In questo caso verrà creato un attributo author per gli oggetti Message che permetterà di risalire all'autore. Ovviamente, lo stesso discorso vale per belongs_to :topic.

# app/model/topic.rbclass Topic < ActiveRecord::Base has_many :messagesend

Anche in questo caso, una relazione uno a molti, vale il discorso fatto in precedenza.

Non abbiamo fatto altro che usare il linguaggio di dominio definito da ActiveRecord per esprimere in uno stile vicino alla lingua inglese quello che avevamo detto prima. Notate come singolare e plurale vengano usati in modo da massimizzare la similitudine linguistica. Ma cosa fanno di preciso questi metodi? Essi generano degli attributi virtuali, che permettono di lavorare direttamente con gli oggetti senza preoccuparsi della loro rappresentazione. Vediamo un veloce esempio usando ancora script/console, in rosso indichiamo sempre i comandi scritti da noi a mano:

>> pippo=Author.new(:name=>"pippo", :password=>"segreta")=> #<Author:0x3964798 @new_record=true, @attributes={"name"=>"pippo", "password"=>"segreta"}>>> topic=Topic.new(:title=>"Benvenuti!")=> #<Topic:0x3961210 @new_record=true, @attributes={"updated_at"=>nil, "title"=>"Benvenuti!"}>>> message=Message.new(:body=>"Il primo messaggio nel nostro forum")=> #<Message:0x395cb48 @new_record=true, @attributes={"topic_id"=>0, "body"=>"Il primo messaggio nel nostro forum", "author_id"=>0, "created_at"=>nil}>>> message.topic= topic=> #<Topic:0x3961210 @new_record=true, @attributes={"updated_at"=>nil, "title"=>"Benvenuti!"}>>> message.author= pippo=> #<Author:0x3964798 @new_record=true, @attributes={"name"=>"pippo", "password"=>"segreta"}>>> message.save=> true>> pippo.messages=> [#<Message:0x39476b0 @attributes={"topic_id"=>"1", "body"=>"Il primo messaggio nel nostro forum", "id"=>"1", "author_id"=>"1", "created_at"=>"2006-06-11 22:31:32"}>]

La prima cosa che vediamo è la possibilità di passare dei parametri al metodo new in modo da inizializzare facilmente gli attributi. La sintassi è oggetto=>valore ed è la sintassi che si usa in Ruby per definire un Hash, o dizionario, in cui il primo elemento è la chiave ed il secondo il valore.

Page 20: Ruby on Rails

In questo caso la chiave è un simbolo, anche se in realtà in Ruby è possibile usare un oggetto qualunque.

In seguito, assegniamo gli oggetti di classe Topic e Author agli attributi del messaggio, e possiamo vedere che automaticamente vengono aggiornati i loro attributi. Poiché un autore ed un topic hanno molti messaggi, l'attributo non è message ma messages, ed è un Array.

Un metodo equivalente per collegare le entità sarebbe stato di prendere l'oggetto che rappresenta il messaggio ed aggiungerlo all'array tramite l'operatore "<<":

topic.messages << message

Il vantaggio di usare queste strutture collegate è che ora salvando il messaggio automaticamente salviamo anche gli oggetti connessi, se ancora non lo sono:

>> message.save=> true>> Topic.find_first.messages=> [#<Message:0x3987550 @attributes={"topic_id"=>"1", "body"=>"Il primo messaggio nel nostro forum", "id"=>"1", "author_id"=>"1", "created_at"=>"2006-06-11 22:31:32"}>]

Oltre alle relazioni uno-uno ed uno-molti che abbiamo visto, esiste anche la possibilità di realizzare associazioni molti-molti tramite il metodo has_and_belongs_to_many, che funziona in maniera leggermente differente.

Infatti per associare dei record in questa configurazione è necessario avere una join table, ovvero una tabella composta da due campi che contengono foreign key verso gli id degli oggetti. C'è una convenzione sul nome della tabella, ovvero seguire lo schema primatabella_secondatabella, ma visto che nel nostro esempio non ne avremo bisogno siete invitati a leggere la documentazione di Rails per maggiori dettagli.

Infine, parliamo dei metodi find_qualcosa. Oltre ai già visti find_all e find_first, il cui significato dovrebbe essere ovvio, è possibile utilizzare Modello.find. Questo metodo è in realtà quello consigliato in quanto può svolgere non solo le funzionalità dei due già visti, ma molte altre.

In particolare, usando i simboli come parole chiave si può scrivere

• Model.find( :first): recupera il primo elemento • Model.find( :all): recupera tutti gli elementi

Ma è anche possibile cercare un elemento tramite il suo id

• Model.find( 12): restituisce l'elemento con id uguale a 12 • Model.find(1,23, 456): restituisce gli elementi con id 1, 23, 456

e molte altre possibilità, incluso il passare SQL direttamente. Di nuovo, siete invitati a leggere la documentazione di Rails per scoprire tutte le potenzialità di questo metodo.

Infine, la cosa più interessante è la possibilità di ricercare tramite dei metodi creati dinamicamente a seconda degli attributi della tabella, quindi ad esempio potremo cercare un Topic tramite il suo titolo con Topic.find_by_title("titolo") e addirittura avere ricerche incrociate con metodi come Model.find_by_qualcosa_and_qualcosaltro_and_altroancora(a, b, c).

A questo punto possiamo cominciare a costruire la nostra applicazione. La prima cosa che desideriamo è la possibilità di visualizzare la lista dei topic e secondariamente visualizzare i messaggi per ognuno di essi.

Creiamo quindi un controller chiamato Home tramite il solito script/generate ma

Page 21: Ruby on Rails

passandogli due argomenti aggiuntivi, che servono a far costruire il codice base per delle azioni. Le azioni che creeremo sono due, ovvero un'azione show che mostri tutti i messaggi per un topic, e un'azione chiamata index. Quest'ultima è un po' speciale, in quanto è l'azione che viene richiamato di default quando accederete a http://hostname/home.

$ ruby script/generate controller Home index show exists app/controllers/ exists app/helpers/ create app/views/home exists test/functional/ create app/controllers/home_controller.rb create test/functional/home_controller_test.rb create app/helpers/home_helper.rb create app/views/home/index.rhtml create app/views/home/show.rhtml

Come già visto in precedenza, il comando crea una classe HomeController in app/controller/home_controller.rb, un helper e un file per scrivere i test di questo controller.

Inoltre, poiché abbiamo aggiunto gli argomenti index e show sono stati creati anche due file .rhtml, ovvero due viste, che vedremo fra poco. Ad ogni vista corrisponde un metodo, e quindi nel codice del controller sono stati creati anche quelli:

class HomeController < ApplicationController

def index end

def show endend

Ricordiamo che il def in Ruby serve a definire un metodo.

Questi metodi sono vuoti ma in realtà dovrebbero occuparsi si trovare le informazioni che vogliamo, quindi ridefiniamo il metodo index in app/controllers/home_controller.rb sostituendo quello vuoto con questo:

def index @topics = Topic.find(:all)end

Se vi state chiedendo cosa sia quella cosa con la chiocciola state tranquilli. In Ruby come abbiamo visto, non c'è una parte dedicata alle dichiarazioni, e l'interprete potrebbe non capire quando parlate di una variabile d'istanza visibile in tutto l'oggetto o quando parlate di una variabile locale. Quindi le variabili d'istanza devono avere una "@" davanti, potete vederlo come un this in Java o PHP ma più conciso.

A questo punto è possibile anche spiegare il significato della stringa che viene mostrata in console quando lavoriamo con gli oggetti, ad esempio:

#<Entry:0x3991690 @new_record=true, @attributes={"created_on"=>nil, "url"=>"", "comment"=>""}>

Significa che abbiamo un oggetto di classe Entry, identificato da un numero esadecimale che dice dove si trova in memoria, il quale ha due variabili d'istanza chiamate rispettivamente @new_record e @attributes. La prima ha un valore booleano, ed indica che l'oggetto è stato creato da zero e non recuperato dal database, mentre la seconda è un Hash e

Page 22: Ruby on Rails

contiene i vari attributi dell'oggetto come coppie nome/valore.

Ma, tornando all'azione index, perché mettiamo dei valori nella variabile @topic? Semplicemente perché le variabili d'istanza sono visibili anche nel file .rhtml, come vedremo fra poco.

Popolare i file HTMLIniziamo con l'avviare il Web server attraverso script/server e colleghiamoci a http://127.0.0.1:3000/home, dovrebbe essere visibile un messaggio che dice "Find me in app/views/home/index.rhtml".

Ogni azione infatti corrisponde ad un un file azione.rhtml nella cartella app/views/nome_controller, ed infatti aprendolo troveremo uno spezzone di HTML.

Per utilizzare la variabile @topics dobbiamo utilizzare del codice ruby embedded nell'HTML, quindi apriamo il file app/views/home/index.rhtml e sostituiamone il contenuto con questo:

<h1>HForum, Ultimate Bulletin Board</h1><p>I forum disponibili sono</p> <ul><% for topic in @topics %> <li><%= topic.title %></li><% end %></ul>

Come vedete usiamo due tag differenti per il codice Ruby, ovvero <% codice %> e <%= codice %>. La differenza è semplice: nel primo caso non ci importa del risultato (la sintassi for.. end non dà risultati utili) mentre nel secondo vogliamo prendere il risultato e metterlo nel nostro codice, in modo simile all'uso di echo in PHP, o alla sintassi identica in JSP.

Provate ad accedere ora alla pagina modificata, senza riavviare il server, e dovreste vedere una lista dei topic disponibili. In verità potete quasi dimenticarvi del server in quanto non ci sarà bisogno di riavviarlo mai finché lavoriamo in modalità di sviluppo, ed in particolare finché modifichiamo controller e viste, che vengono ricaricate automaticamente dopo ogni cambiamento.

Se la vostra lista è ancora composta di un solo topic provate ad aggiungerne altri da console, tanto per rendere l'applicazione più realistica. Magari provate ad utilizzare il metodo Topic.create(:title=>"titolo"), un altro metodo autogenerato che crea un oggetto e lo salva immediatamente.

Però vedere i forum in questo modo non è molto utile, sarebbe più comodo se quelli fossero dei link. Modifichiamo quindi la pagina, inserendo anche il timestamp già che ci siamo:

<ul> <% for topic in @topics %> <li><%= link_to topic.title, :action=>"show", :id=>topic.id %> (ultima modifica: <%= topic.updated_at.to_s(:short) %>)</li> <% end %></ul>

Il metodo to_s serve a convertire qualcosa in una stringa, e lo vedrete spesso in Ruby, in quanto il linguaggio non effettua trasformazioni automatiche tra i tipi come Perl o PHP. L'argomento serve a specificare che vogliamo una rappresentazione compatta dell'orario. Il metodo link_to serve appunto a costruire un link. Il primo argomento deve essere una stringa, e sarà la cosa che viene

Page 23: Ruby on Rails

mostrata all'utente, mentre gli elementi seguenti servono a costruire un URL.

Ricordate che Rails effettua un'analisi degli URL come http:// hostname/controller/azione/id? Beh, vale anche il contrario, se specifichiamo controller, azione ed id possiamo costruire un URL. In realtà i vari pezzetti sono opzionali, quindi possiamo specificare un controller ma non un'azione oppure entrambi e non l'id oppure altri parametri e così via.

Ora però c'è il problema che se seguiamo il link non succede niente, quindi definiamo il contenuto dell'azione show in modo appropriato:

def show @topic =Topic.find(params['id'])end

Potete vedere che utilizziamo params per accedere agli argomenti ricevuti tramite GET (ma se fossero stati inviati via form con POST sarebbe stata la stessa cosa). Si tratta di un metodo che ereditiamo da ActionController::Base e che restituisce un insieme di coppie chiave/valore, utilizzando la sintassi oggetto[chiave] possiamo ottenere il valore che ci interessa, nel nostro caso, il valore associato all'id del topic. Ricordate che in Ruby è possibile non mettere esplicitamente le parentesi, e che params è equivalente a params().

Modifichiamo ora il codice della vista, cioè il file app/views/home/show.rhtml:

<h2><%= @topic.title %> </h2>

<% for message in @topic.messages %> <div> <h4><%= message.author.name %> ha detto</h4> <p> <%= message.body %> </p> </div><% end %>

Davvero molto semplice.

Fin qui abbiamo visto come viene strutturata alla base una tipica applicazione. I modelli si occupano di tenere traccia dei dati, e di fornire un'interfaccia semplice per manipolarli. I controller si occupano di prendere le informazioni e renderle disponibili alle viste tramite variabili d'istanza. Infine, le viste usano codice Ruby embedded nell'HTML per mostrare i dati ricevuti dai controller.

I Layout in Ruby on RailsSe avete un minimo di spirito d'attenzione, avrete notato che la directory views non contiene solo una sottodirectory chiamata home ma anche una chiamata layouts. I layout sono una caratteristica abbastanza peculiare di Rails, e sono pensati per raccogliere codice condiviso tra le diverse pagine di un controller o di un'intera applicazione.

La situazioni in cui si usano i layout sono quelle in cui si vuole avere una sorta di cornice nella quale inserire il corpo delle pagine. Ad esempio, potreste volere che all'inizio di tutte le pagine sia presente un header, poi un menu che permetta di navigare nel sito, ed alla fine di tutte le pagine delle note sul copyright. L'approccio classico che si userebbe con altri ambienti di sviluppo sarebbe di creare un file per contenere l'header, poi uno per il menu ed infine uno per il footer, e poi di includerli in ogni singola pagina che dovesse usarli.

Rails invece usa un solo file, il layout, che contiene sia l'header sia il menu sia il footer, più uno specie di segnaposto in cui verrà infilato il contenuto della pagina. Per capire meglio, creeremo un

Page 24: Ruby on Rails

file app/views/layouts/application.rhtml fatto in questo modo:

<html>

<head> <title> HForum </title></head>

<body><%= @content_for_layout %> <div> <small>Powered by Ruby On Rails</small> </div></body>

</html>

come vedete è un file .rhtml come gli altri, solo che fa riferimento ad una variabile @content_for_layout. Questo è il segnaposto di cui parlavamo prima, ma ormai conosciamo il funzionamento dei file rhtml e quindi possiamo capire cosa sia in realtà: si tratta di una normale variabile, che non fa altro che contenere il risultato del processing delle singole pagine.

Poiché la gestione è invertita rispetto al solito (è il layout che si occupa delle pagine, non le pagine che gestiscono header e footer) è possibile velocizzare molto lo sviluppo, in quanto non dobbiamo preoccuparci di scrivere in ogni pagina quali file deve caricare, con tutti i problemi di manutenibilità e codice ridondante che questo comporta, ma ci basta, come al solito, seguire le convenzioni di Rails.

Come fa Rails ad associare un layout ad un'azione? Il meccanismo è banale, se dentro la directory app/views/layouts, create un layout chiamato mio_controller.rhtml, esso verrà usato automaticamente per tutte le azioni definite in MioController. Se invece, come abbiamo appena fatto, definite un layout application.rhtml esso verrà usato come default per l'intera applicazione. Sarà poi ovviamente possibile disabilitare il layout per una singola azione, come vedremo più avanti, per far si che non compaia, ad esempio, uno spezzone di html in un'azione che gestisce i feed.

Un layout è una vista come le altre, e questo significa che potete accedere a tutte le variabili d'istanza definite normalmente dalle azioni. Questo è molto utile per lasciare degli elementi fissati ma con contenuto differente, come ad esempio un tag <title> che abbia un valore dipendente dalla pagina. Nel nostro caso basterebbe cambiare l'inizio del layout in questo modo:

<head> <title> <%= @title %> </title></head>

ed aver cura di impostare la variabile @title in ogni azione.

I Partial di Ruby on RailsI layout sono uno strumento straordinario per evitare la duplicazione di codice, ma non sono gli unici messi a disposizione da Rails. In particolare è possibile prendere dei pezzetti di pagina e renderli riutilizzabili, creando i cosiddetti partial.

Un partial non è altro che un ritaglio di codice, messo in un file chiamato _qualcosa.rhtml. Notate la presenza di un underscore iniziale, che serva a far sì che esso non venga interpretato come un'azione.

Page 25: Ruby on Rails

Mentre i layout servono a condividere il codice che fa da cornice alla pagina, i partial sono particolarmente utili nel caso in cui si vogliano riutilizzare degli elementi tra viste differenti e ancora di più quando ci sono elementi multipli.

Per esempio, in un Weblog la prima pagina contiene quasi sempre un certo numero di post, e poi è possibile accedere a un singolo post per vedere anche i commenti. Il pezzo di codice relativo alla visualizzazione del messaggio, che si occupa quindi del corpo dell'articolo, del titolo, di mostrare tag e categorie e quant'altro si può facilmente riutilizzare usando un partial.

Per capire come usare i partial, prendiamo la vista dell'azione show, (che trovate ancora in views/home/show.rhtml) ed estraiamo il codice relativo alla visualizzazione dei messaggi:

<div><h4><%= message.author.name %> ha detto</h4><p> <%= message.body %></p></div>

salviamo questo codice in un file chiamato _message.rhtml nella stessa cartella, ed abbiamo concluso. Ma come facciamo ad usarlo?

La cosa è molto semplice, è sufficiente modificare il corpo del ciclo for in questo modo

<% for m in @topic.messages %> <%= render(:partial => "message", :object=>m) %><% end %>

che va letto come "restituisci la stringa che viene fuori prendendo il partial chiamato message e mettendoci dentro questo oggetto". Il modo in cui la vista riesce a trovare il partial è ovvio, semplicemente aggiunge un underscore ("_") davanti al nome, meno ovvio è il modo in cui viene passata la variabile: in sostanza, il contenuto della variabile passata a render viene infilato dentro una variabile che ha lo stesso nome del partial. Quindi poiché il nostro si chiama "message" sarà la variabile "message" ad essere inizializzata con i valori necessari.

Nel nostro caso, ed in generale vale quasi sempre, la scelta del nome del partial è stata dettata proprio dal fatto che esso usava una variabile con tale nome, e che questo nome spiega abbastanza bene ciò che fa (mostra un messaggio). Ovviamente, nel caso in cui aveste usato una variabile con un nome senza nessuna ombra significato, come "i" o "mg" sarebbe consigliabile intervenire su questo codice e cambiarlo leggermente, cosa che in effetti sarebbe consigliabile comunque per renderlo più chiaro.

Una nota aggiuntiva sui partial ripetuti. Come si può intuire, è molto comune che un partial sia usato in congiunzione con una collezione di oggetti: pensiamo ad esempio a mostrare dei prodotti, o una lista di utenti, o di commenti o altro. Dovremmo ogni volta scrivere un ciclo for come quello qui sopra, il che sarebbe uno spreco di codice ed una fatica inutile, quindi Rails offre un meccanismo per automatizzare il tutto, usando di nuovo render.

Possiamo cancellare tutte e tre le righe del ciclo e sostituirle con una sola:

<%= render(:partial => "message", :collection=>@topic.messages ) %>

Questo modo di usare il metodo farà si che il partial venga usato con ogni elemento della collezione automaticamente, esattamente come se avessimo scritto un ciclo sugli elementi della collezione a mano.

Il metodo render ha numerose altre applicazioni, ad esempio è possibile utilizzare render :text=>"testo" all'interno del metodo relativo ad un'azione per saltare completamente l'analisi di un file rhtml, il che può essere utile per creare dei web service RESTful. Per scoprire le varie

Page 26: Ruby on Rails

opzioni di render però è consigliabile vedere le api di Rails, in quanto ne ha una dozzina con varie sotto opzioni, permettendo di specificare se si desidera renderizzare un template o un'azione e quale codice HTTP si vuole restituire e molto altro.

I form in Ruby - IL'applicazione che stiamo sviluppando comincia a prendere forma, ma manca della parte fondamentale: la possibilità di inserire nuovi topic e messaggi, nonché la possibilità per gli utenti di registrarsi ed effettuare il login.

Tutte e tre le funzionalità dipendono da una delle funzionalità più importanti per applicazioni web strutturate, e cioè la possibilità per i visitatori di inserire nuovi dati, operazione che è possibile fare tramite i form.

Rails fornisce diversi livelli di supporto per la creazione di form, permettendo sia di andare ad un livello molto vicino al normale codice HTML, sia di automatizzare l'inserimento di oggetti complessi.

Aggiungere un nuovo messaggio, così come un nuovo topic, è un'operazione strettamente collegata all'identificazione di un utente, ma inizialmente non ce ne preoccuperemo, rimandando la gestione dell'autenticazione alla prossima volta, e nasconderemo le funzionalità di ricerca dell'autore dietro un metodo ausiliario che andremo a definire in HomeController (cioè nel file app/controllers/home_controller.rb), con un codice come questo:

def find_author return Author.find(:first)endprotected :find_author

Questo è un metodo di utilità che ha senso solamente all'interno del controller. In futuro, quando avremo ben definito la parte di autenticazione potremo includere in questo metodo qualcosa di più utile, senza intervenire sul resto dell'applicazione. Riguardo al codice c'è solo una cosa da notare: il metodo viene dichiarato come protected, il che fa sì che non sia visibile dall'esterno e quindi non sia considerato un'azione.

Notate come protected non sia una parola chiave, ma ancora una volta sia un semplice metodo, proprio come avevamo visto per scaffold. Una nota sulla sintassi: è possibile usare protected (ed il suo analogo private) sia con un argomento in questo modo:

class Foo def bar end def baz end protected :bazend

sia senza nessun argomento, ed in questo caso esso fa sì che tutti i metodi definiti dopo quella linea seguenti siano considerati protetti:

class Foo def bar end protected def baz endend

Page 27: Ruby on Rails

Il risultato è esattamente lo stesso.

Far sì che si possa aggiungere un messaggio ad un topic esistente richiede due cose: che ci sia un form nella pagina di visualizzazione di una discussione, e che ci sia un'azione a gestire i dati del form. Il file show.rhtml (app/views/entries/show.rhtml) dovrà quindi diventare una cosa simile:

<h2><%= @topic.title %> </h2>

<%= render(:partial => "message", :collection=>@topic.messages ) %> <h3>Aggiungi un nuovo messaggio</h3>

<form action="/home/add_message" method="post"> Testo: <%= text_area_tag "body" %> <%= hidden_field_tag "topic_id", @topic.id %> <br /> <%= submit_tag("Aggiungi messaggio") %></form>

Notate il modo in cui abbiamo riutilizzato la variabile @topic che era servita per mostrare i messaggi, evitando di dover cambiare anche solo una linea di codice nel metodo relativo a questa vista.

I form in Ruby - IIVisualizziamo, per comodità, il codice che abbiamo incluso nella lezione precedente all'interno del file show.rhtml (app/views/entries/show.rhtml):

<h2><%= @topic.title %> </h2>

<%= render(:partial => "message", :collection=>@topic.messages ) %> <h3>Aggiungi un nuovo messaggio</h3>

<form action="/home/add_message" method="post"> Testo: <%= text_area_tag "body" %> <%= hidden_field_tag "topic_id", @topic.id %> <br /> <%= submit_tag("Aggiungi messaggio") %></form>

In questo codice usiamo dei semplici metodi che generano il codice HTML per un'area destinata al corpo del messaggio, per un campo nascosto (che servirà a trasmettere l'id del topic) ed per un bottone che invii i dati. Questi metodi sono degli helper, e sono forniti dalla libreria di Rails relativa a controller e viste, chiamata ActionPack. Il compito di un helper è di generare del codice HTML, e la sua utilità è nel far sì che il codice rimanga semplice, racchiudendo delle funzionalità anche complesse in un unico punto.

Ovviamente in questo caso stiamo usando degli helper di livello molto basso, ed il vantaggio rispetto all'uso di HTML semplice è minore, ma maggiore sarà la complessità degli helper maggiore sarà il risparmio di tempo che proviene dal loro uso, come ad esempio per l'uso di funzionalità AJAX.

Sia hidden_field_tag sia text_area_tag (evidenziati in rosso nel codice precedente)

Page 28: Ruby on Rails

accettano diversi argomenti, il primo è relativo al nome che verrà usato per indicare i dati inviati dalla form, mentre il secondo è il valore predefinito di questi dati. L'argomento usato per submit_tag corrisponde all'attributo value in HTML, e quindi è il testo che verrà mostrato sul bottone della form.

Il form fa riferimento a una nuova azione, che definiremo sempre all'interno della classe HomeController in questo modo:

def add_message topic=Topic.find(params['topic_id']) message=Message.new(:body=>params['body']) message.topic=topic message.author=find_author() message.save redirect_to(:back)end

In questo codice possiamo scoprire due cose nuove: anzitutto, possiamo vedere come i valori del form siano accessibili con il nome che gli abbiamo assegnato (il primo argomento di hidden_field_tag e text_area_tag) tramite params, indipendentemente dal fatto che le richieste siano fatte via GET o via POST. Poi vediamo il metodo redirect_to, che appunto reindirizza il browser dell'utente verso un'altra azione. Anche nel caso di redirect_to, come nel caso di link_to,possiamo specificare un controller, un'azione e argomenti opzionali, ed in più abbiamo la possibilità di usare come argomento il simbolo :back, che farà si che l'utente sia reindirizzato alla pagina da cui proviene.

Ora il significato di questo codice dovrebbe essere comprensibile: nel momento in cui l'utente invia i dati cliccando il bottone del form, essi arrivano a questa azione dove vengono utilizzati per creare un nuovo messaggio, associarlo ad un topic e ad un autore e poi salvarlo nel database. L'utente viene poi reindirizzato di nuovo alla pagina dalla quale aveva inviato il form, permettendogli di vedere il risultato dell'operazione.

Sarebbe possibile ora creare una nuova azione che permetta agli utenti di aggiungere un nuovo topic in modo assolutamente identico, ma preferiamo avere una pagina separata, quindi aggiungiamo un link ad essa in index.rhtml:

<p> <%= link_to("Aggiungi un nuovo Topic", :action=>"add_topic") %></p>

Per gestire i topic, useremo una nuova azione, add_topic, ancora nel controller HomeController, con una vista di questo tipo:

<h2>Aggiungi un nuovo topic</h2><form action="add_topic" method="post"> Titolo: <%= text_field_tag("title") %> <br /> Testo: <%= text_area_tag("body") %> <br /> <%= submit_tag("aggiungi") %></form>

Come potete notare guardando l'attributo action della form, i dati vengono di nuovo inviati alla stessa azione, che definiremo in questo modo:

def add_topic if request.post? topic=Topic.new(:title=>params['title']) message=Message.new(:body=>params['body']) message.topic=topic

Page 29: Ruby on Rails

message.author= find_author( ) message.save redirect_to(:action=>'index') endend

Il codice è grossomodo equivalente a quello già visto in precedenza, solo che invece di cercare un topic nel database ne creiamo uno nuovo con il titolo, titolo che è stato possibile inviare dalla form grazie text_field_tag.

La differenza è nell'uso di request. Questo è un metodo (ancora una volta, Ruby permette di omettere le parentesi, ma avremmo potuto scrivere request().post?()) che permette di accedere alla richiesta appena fatta. L'oggetto restituito da request fornisce dei metodi, come appunto post?, che offrono la possibilità di capire come è stata effettuata la richiesta.

Poiché tutto il corpo di questo metodo è all'interno del blocco if, esso verrà eseguito solamente quando la richiesta è di tipo POST, ovvero quando c'è stato l'invio di una form. Invece, quando la richiesta è una GET request.post? sarà falso, e ciò significa che non verrà eseguita nessuna operazione (anche qui avremmo comunque potuto usare un'azione differente come nel caso di add_message).

Dunque, quando l'utente segue un link o accede tramite la barra degli indirizzi del suo browser a questa azione, gli verrà mostrata la pagina con la form, mentre quando egli inserisce i dati e li trasmette al server, l'azione li analizzerà e dopo aver creato gli oggetti, reindirizzerà l'utente verso la home page.

Gestire gli utentiA questo punto gli ultimi form che dobbiamo aggiungere sono quelli relativi alla registrazione e al login degli utenti, per poi far sì che solo gli utenti registrati possano inviare messaggi e creare nuovi argomenti di discussione. La prima cosa necessaria sarà quindi la possibilità per un autore di registrarsi e di effettuare il login.

Creiamo quindi un nuovo controller adibito a questo scopo, con due metodi new e login tramite il solito script/generate.

L'azione new sarà molto simile a quelle già realizzate, ma ovviamente il form sarà differente, in quanto avrà bisogno di avere tre caselle di testo: una per il nickname, una per la password ed una per verificare quest'ultima. Per realizzare il modulo, però, utilizzeremo un codice leggermente differente:

<%= start_form_tag(:action=>"new") %> Username: <%= text_field( "author", "name") %> <br /> Password: <%= password_field( "author", "password") %> <br /> Verifica Password: <%= password_field( "author", "password_confirmation" ) %> <br /> <%= submit_tag "registra" %><%= end_form_tag %>

Questa volta abbiamo usato degli helper per creare i tag del form, il cui significato dovrebbe essere chiaro. Abbiamo anche usato degli helper leggermente più sofisticati per gestire user e password, potete riconoscerli in quanto il loro nome non finisce con "_tag". Questi helper, sono specificamente pensati per costruire form dedicati alla creazione di oggetti, ed ovviamente ne esistono di adatti ad ogni tipo di input, come aree di testo, check box, file upload e così via.

Page 30: Ruby on Rails

Quando l'azione richiamata dal form andrà ad analizzare i valori tramite params, troverà qualcosa di meglio di una semplice stringa, infatti alla chiave "author" corrisponderà un Hash con chiavi "password", "password_confirmation" e "name". Se vi state chiedendo dove sia l'utilità in tutto ciò, vi basti ricordare che il metodo Author.new può accettare in input proprio un Hash di questo tipo, e quindi la creazione di un oggetto diventa semplicemente Author.new(params['author']).

Usando lo stesso approccio già usato per creare un nuovo topic, potremo definire il metodo relativo all'azione in questo modo:

def new if request.post? @author=Author.new(params['author']) if @author.save logged end endend

logged per ora non farà altro che rimandare l'utente alla prima pagina, si tratta di un metodo protetto definito in questo modo:

def logged redirect_to(:controller=>"home")endprotected :logged

Validazione dell'inputMa cosa fare se l'utente sbaglia ad inserire la password e la verifica fallisce? E se un autore con lo stesso nome esiste già? E se uno dei parametri non viene inserito? I problemi di validazione dell'input in Rails vengono gestiti sempre nel modello. In questo modo, le regole di validazione possono essere riutilizzate da ogni altro elemento dell'applicazione o anche essere riutilizzate in applicazioni differenti.

Per far sì che la validazione sia la più semplice possibile ActiveRecord mette a disposizione differenti metodi speciali, simili a quel metodo scaffold già visto nei controller. In particolare, potremo cambiare il modello in app/models/author.rb in questo modo:

class Author < ActiveRecord::Base validates_presence_of :name, :password validates_uniqueness_of :name validates_confirmation_of :password has_many :messagesend

Cosa significano quelle tre nuove linee? Semplicemente, la prima verifica che gli attributi password e name abbiano un valore. La seconda verifica che l'attributo name non sia già presente nel database, preoccupandosi di effettuare al posto nostro le operazioni in SQL. La terza verifica che password e password_confirmation siano uguali. Ancora una volta, ci basta seguire una piccola convenzione, ovvero chiamare i campi nome e nome_confirmation per far sì che Rails gestisca tutto automaticamente.

Sebbene esistano molte di queste validazioni predefinite, che permettono ad esempio di verificare il

Page 31: Ruby on Rails

formato o la lunghezza di un campo, di escludere alcuni valori predefiniti e persino di controllare che l'utente abbia accettato delle condizioni (come la classica check box per il trattamento dei dati personali) è comunque sempre possibile definire un metodo apposito, chiamato validate, che verrà eseguito per verificare regole più complesse, controllando il contenuto dell'oggetto, le sue relazioni ed applicando un controllo di qualunque altro tipo che non corrisponda a quelli disponibili.

Provando a creare degli oggetti di tipo Author con script/console possiamo vedere cosa succede quando fallisce una validazione:

>> author=Author.new( :name=>"nome", :password=>"pass", :password_confirmation=>"sbagliata")=> #<Author:0x3b8a1b8 @new_record=true, @attributes={"name"=>"nome", "password"=>"pass"}, @password_confirmation="sbagliata">>> author.save=> false

save restituisce false invece del solito true, ad indicare che in effetti l'oggetto non è stato salvato, perché qualcosa è andato male. Gli errori vengono salvati in un attributo speciale degli oggetti del modello, chiamato errors, e sono accessibili come coppie chiave/valore in questo modo:

>> for name, error in author.errors>> puts name+ ": "+ error>> endpassword: doesn't match confirmation

Avendo capito come accedere a queste informazioni, ci basterà riutilizzare l'oggetto nella vista, e mostrarne gli errori. Ma ancora una volta, Rails fornisce già un meccanismo molto veloce per farlo, ovvero l'helper error_messages_for, che può essere usato in questo modo:

<%= error_messages_for "author" %>

Ovviamente, questo produce un codice standard (ed altrettanto standard messaggi di errore) che potrebbero non essere adeguati al nostro sistema, nel qual caso si potrà comunque gestirli da soli con l'approccio già visto (un'iterazione esplicita), o tramite gli altri metodi messi a disposizione dalla classe Errors. Notate che questo helper utilizza il nome di una variabile, e quindi nel metodo del controller sarà necessario utilizzare una variabile @author.

Notate che quando l'azione viene richiamata tramite un collegamento, cioè tramite GET, l'intero blocco if non viene eseguito, e quindi la variabile è vuota e nella pagina non viene mostrato nulla, esattamente il comportamento che desideriamo.

La pagina di login sarà sostanzialmente identica, ma in questo caso possiamo rendere la vista ancora più semplice utilizzando un altro helper, chiamato semplicemente form . Questo helper prende in input il nome di una variabile d'istanza, come error_messages_for, poi la analizza per vedere quali sono i suoi attributi ed infine costruisce una form appropriata per i suoi campi. Ad esempio, per uno dei nostri oggetti di classe Author, inserirà un form per la password ed uno per il nome. form non ha difficoltà a gestire oggetti anche molto complessi, anzi, più il modello è articolato tanto più è utile questo helper, che ci permette di creare una form per una tabella con una mezza dozzina di colonne in una sola riga di codice.

Possiamo inoltre passare degli argomenti aggiuntivi, tra cui le specifiche di quale azione deve essere richiamata dal form, nel nostro caso, specificheremo di nuovo login. Il codice della vista in app/views/user/login.rhtml potrà essere dunque una cosa del genere:

<h1>Esegue il login</h1>

<%= @invalid_login_error %>

Page 32: Ruby on Rails

<%= form("author", :action=>'login') %>

Mentre il metodo definito in UserController, dentro app/controller/user_controller.rb sarà:

def login @author=Author.new(params['author']) if request.post? if @author.valid_credentials? logged else @invalid_login_error="User o password errati" end endend

abbiamo spostato la creazione dell'oggetto al di fuori dell'if, in quanto esso ci serve per l'helper form, in ogni caso.

All'interno del metodo poi, impostiamo una variabile d'errore se l'utente non inserisce dati corretti, e lo redirigiamo verso la home page se invece li ha inseriti correttamente. Il metodo valid_credentials? invece è un nuovo metodo, che definiremo nel modello per gli autori e cioè nel file app/model/author.rb, in questo modo:

def valid_credentials? saved=Author.find_by_name(name) return (saved and (password == saved.password))end

Ricordate che i vari metodi find restituiscono nil se non trovano un oggetto, e che nil è considerato un valore falso in Ruby.

Ancora una volta, cercate di fare attenzione a dove abbiamo definito le varie funzionalità: capire se un utente è valido o meno è un compito del modello, e quindi abbiamo messo in quel punto la logica. Se un domani decidessimo di offrire la possibilità all'utenti di effettuare operazioni tramite Web service, potremmo riutilizzare questa parte di logica.

Invece, sapere cosa fare con un utente che cerca di effettuare il login via web è compito del controller, e il fatto che questo codice non sia strettamente collegato alla vista fa sì che sia possibile riutilizzarlo ad esempio con una form che si trova in un'altra azione, ad esempio mettendo un box per il login nella homepage.

Filtri e sessioniAbbiamo oramai capito come sia possibile validare dei modelli e come sia semplice verificare i dati di un autore per permettergli di inviare dei messaggi e di creare nuovi topic di discussione. Ma la nostra Web application è ancora ignara di tutto questo, poiché le informazioni sul login non sono salvate da nessuna parte. La soluzione per mantenere dei dati persistenti risiede nell'uso delle sessioni.

HTTP è un protocollo stateless, ovvero non mantiene una memoria delle informazioni registrate tra una pagina e l'altra. L'unico modo per tenere traccia di cosa ha fatto l'utente è utilizzare delle sessioni, ovvero mantenere delle informazioni lato server e far sì che l'utente si porti dietro (tramite un cookie) soltanto i dati necessari a recuperare queste informazioni. In Rails, come in altre piattaforme, questo è astratto tramite il meccanismo delle sessioni.

È possibile accedere alla sessione corrente tramite il metodo session, disponibile

Page 33: Ruby on Rails

automaticamente in tutti i controller così come params o request visti in precedenza. L'oggetto accessibile tramite questo metodo è ancora una volta una collezione di coppie chiave/valore, dove il valore può essere un qualsiasi oggetto Ruby (ma attenzione: se gli oggetti sono "semplici" essi possono essere usati direttamente, mentre in alcuni casi potrebbe essere necessario insegnare a Rails come recuperarli, fate riferimento alla documentazione per i dettagli).

Quello che è necessario per noi dunque, è di salvare le informazioni di login, ed in particolare, il nome con cui un utente è entrato nel sistema, per poi riutilizzarlo in seguito.

Fortunatamente, abbiamo un metodo che è perfettamente adatto a questo scopo, ovvero logged in UserController. Per memorizzare le informazioni sarà sufficiente cambiarlo in questo modo:

def logged session['login'][email protected] redirect_to(:controller=>"home")end

come vedete è semplicissimo modificare questi parametri, ed è altrettanto semplice accedervi. Il punto in cui avremo bisogno di recuperare queste informazioni è il metodo find_author che avevamo definito in HomeController proprio a questo scopo, e che cambieremo in questo modo:

def find_author return Author.find_by_name(session['login'])end

A questo punto però si pone il problema di far sì che un utente non possa inviare dei messaggi né creare dei nuovi topic se non dopo aver eseguito il login. In realtà quello di cui abbiamo bisogno è un modo di dire a Rails che prima di poter accedere ad alcune azioni l'utente deve essere autenticato.

Gli autori di Rails però hanno affrontato questo problema in un modo che rende il tutto assolutamente evidente, grazie al meccanismo dei filtri. Un filtro non è altro che una funzione che viene associata ad una o più azioni, e che permette di effettuare delle operazioni prima che essa venga richiamata (come nel nostro caso, per costringere un utente ad autenticarsi) o dopo, per operare sul risultato (applicare una trasformazione XSLT, comprimere la pagina o altro). Per associare un filtro a delle azioni o controller si usa un metodo a livello di classe nel controller, come scaffold, e gli si indica di fare riferimento ad un metodo del controller stesso, quindi nel nostro caso si può aggiungere all'interno della classe HomeController (in app/controllers/home_controller.rb) una cosa del genere:

before_filter :authorize, :only=>[:add_topic,:add_message]def authorize if not @session['login'] redirect_to(:controller =>'user', :action=>'login') endend

la sintassi usata per la linea di before_filter potrebbe sembrarvi strana, ma in realtà è molto semplice. Per default, un filtro viene associato a tutti gli elementi di un controller, e questo è il comportamento che si ottiene scrivendo

before_filter :nome_metodo

Alternativamente, possiamo specificare una lista delle azioni sulle quali deve lavorare un filtro passando come argomento una coppia composta dalla chiave :only e da un array che contiene i nomi dei metodi. Al contrario, se vogliamo applicare il filtro a tutte le azioni definite nel controller, tranne che ad alcune, possiamo utilizzare un argomento nel quale la chiave sia chiamata :except,

Page 34: Ruby on Rails

ed il valore sia nuovamente un array contenente i nomi delle azioni da ignorare.

Il contenuto del metodo authenticate è molto semplice: se l'utente ha eseguito il login, nella sessione sarà memorizzato il suo nome relativamente alla chiave "login", e non accadrà nulla. Se invece l'utente non ha eseguito il login, la sessione non avrà niente associato alla chiave, e quindi il valore sarà nil, che come sempre in Ruby è considerato un valore falso, e quindi l'utente verrà rediretto alla pagina di login del controller User.

A questo punto, dovrebbe essere ovvio anche come realizzare un meccanismo di logout, definendo un metodo come questo in UserController:

def logout session['login']=nil redirect_to(:controller=>'home')end

che semplicemente azzera la variabile di sessione e fa tornare l'utente alla home. Notate che questa azione non ha bisogno di alcuna vista, e che è raggiungibile tramite una semplice GET, ovvero tramite un link. Far sì che questo link sia raggiungibile in tutta l'applicazione richiede solo l'aggiunta di qualche una riga al layout application.rhtml:

<% if session['login'] %> <div> <p>Effettua il <%=link_to("Logout", :controller=>"user", :action=>"logout") %></p> </div><% end %>

Abbiamo fatto uso di una semplice condizione if per far si che questo link sia mostrato solo agli autori effettivamente autenticati, e potete vedere come una vista abbia accesso allo stesso oggetto session visibile nei controller, il che ci permette di semplificare il codice.

Attenzione: in teoria un link (ovvero una chiamata al webserver tramite GET) non dovrebbe mai cambiare lo stato, in quanto può capitare che esso sia seguito da un crawler o da un qualunque programma automatizzato. In questo caso abbiamo usato un link per il logout in quanto è l'approccio più comune e non è in generale "dannoso", ma nel realizzare siti senza login fate attenzione ad usare azioni pericolose raggiungibili tramite link, come ad esempio collegamenti che permettano di cancellare degli oggetti.

Prima di proseguire, possiamo notare come ci sia una piccola incongruenza tra l'invio di messaggi e la creazione di nuovi topic. Nel primo caso, il form è visibile fin da subito, anche se un autore non ha ancora eseguito il login, il che potrebbe portarlo ad inserire dei dati senza poi poterli inviare. Possiamo facilmente modificare la vista usando di nuovo di un blocco condizionale, facendo sì che agli utenti autenticati sia mostrato il form per inviare messaggi e agli utenti sconosciuti sia fornito il link per eseguire il login. La vista avrà dunque un pezzo di codice simile a questo:

<% if session['login'] %><h3>Aggiungi un nuovo messaggio</h3>

<form action="/home/add_message" method="post"> Testo: <%= text_area_tag "body" %> <%= hidden_field_tag "topic_id", @topic.id %> <br /> <%= submit_tag("Aggiungi messaggio") %></form>

<% else %><%= link_to("Esegui il login per inviare messaggi", :controller=>'user', :action=>'login') %>

Page 35: Ruby on Rails

<% end %>

L'uso dell'oggetto "flash"Un difetto rilevante del nostro sistema di autenticazione è che l'utente riceve un feedback molto poco evidente riguardo il fatto che il login è andato a buon fine, ovvero il piccolo link "logout" in cima alla pagina. L'ideale sarebbe di mettere un testo ben evidente "Il login è stato eseguito" subito dopo che l'utente è stato reindirizzato all'indice, ma se questo rimanesse lì per tutta la durata della navigazione risulterebbe inutile e fastidioso. L'uso di session è quindi, sebbene possibile, inadatto a questo problema specifico.

Il problema in realtà è un caso particolare di una situazione molto più generale, ovvero la necessità in un'applicazione web di mantenere alcuni dati tra una pagina e l'altra, senza però renderli disponibili per l'intera durata della visita. Dovrebbe essere evidente quanto ciò sia utile, ad esempio, quando sia necessario mostrare dei messaggi d'errore ad un utente, messaggi che sono concettualmente legati ad una singola pagina e non all'intera applicazione. Fortunatamente, ancora una volta, questo problema è stato già affrontato in ambiti di produzione dagli autori di Rails e quindi è stato progettato un meccanismo che permette di affrontarlo con una semplicità estrema: l'oggetto chiamato flash.

Un flash, è ancora una volta un'insieme di valori accessibili per chiave, che vengono impostati da un'azione e sono accessibili solamente in quella seguente. Si può raggiungere questo oggetto tramite il metodo omonimo, esattamente come per session e request. L'utilizzo è davvero molto semplice, nel nostro caso sarà possibile aggiungere una sola linea al metodo logged in UserController :

def logged session['login']= @author.name flash['notice'] = "login effettuato per "[email protected] redirect_to(:controller=>"home")end

ed ovviamente mostrare questo messaggio nella vista index.rhtml del controller Home:

<%= flash['notice'] %>

Se provate ad eseguire il login adesso noterete che la home page mostra questo messaggio la prima volta che essa viene caricata, ma che le volte seguenti esso non appare più proprio come desideriamo.

Sebbene quanto appena visto sia già molto utile, l'oggetto flash offre alcune funzionalità aggiuntive, di cui però non abbiamo bisogno nella nostra semplice applicazione. In particolare, esiste il modo di far durare il flash più a lungo o ancora meno. Se nell'azione azione_di_mezzo vogliamo mantenere un'informazione ottenuta da azione_precedente e renderla accessibile anche ad azione_seguente dovremo usare il metodo flash.keep(messaggio). Questo ha senso, ad esempio, in un processo di registrazione articolato in più pagine, ma ovviamente non deve essere usato come un sostituto di session.

Il metodo per rendere un flash ancora più labile è invece flash.now[messaggio], che fa sì che le informazioni messe nel flash siano accessibili solo in una determinata azione, e non nella seguente, come se stessimo usando delle normali variabili d'istanza. L'utilità di questo metodo è decisamente ridotta rispetto agli altri, ma ha una sua ragione se pensiamo di utilizzare il flash in un template: un controller può verificare se un'azione ha impostato un messaggio nella chiave "notifica", ed in caso contrario provvedere a mettercene uno generico, senza che la vista debba comportarsi in modo differente.

Page 36: Ruby on Rails

I template Rxml - IAbbiamo già detto che Rails di default fornisce il supporto per diversi tipi di template: i file rhtml, i file rxml ed i file rjs. I file del primo tipo utilizzano semplice codice Ruby incluso nell'HTML, e sono il meccanismo più utile ed usato. I secondi sono scritti in un domain specific language costruito con Ruby per rendere semplice la realizzazione di XML generico, ed i terzi sono pensati per generare JavaScript dinamicamente, sempre tramite un piccolo DSL, per integrare meglio le tecnologie AJAX.

I file Rxml, sono molto semplici da usare ed in molti casi permettono di descrivere un albero XML in modo molto semplice. Per spiegarli però dobbiamo introdurre un concetto di Ruby che abbiamo evitato per tutta la guida, e cioè i blocchi. I blocchi sono una sorta di funzioni senza nome che vengono definite al volo e passate ad un metodo specifico, che può usarli in qualsiasi modo, ad esempio:

[1,2,3,4,5].each do |num| print numend

Questo codice è equivalente ad un ciclo for, e lo stesso vale per questa altra sintassi:

12345.each { |num| print num}

Se guardate la documentazione di Rails troverete spesso i blocchi: i vari metodi per la costruzione di form automatici, ad esempio, in genere accettano un blocco per aggiungere elementi. Poiché questa guida non è specifica su Ruby, siete invitati ad approfondire il linguaggio se una cosa vi risulta del tutto incomprensibile.

Tornando ai template Rxml, vediamo un esempio pratico, creando un controller chiamato "feed" nel quale abbiamo intenzione di mantenere le funzionalità di gestione dei contenuti RSS o ATOM, usando ancora il solito script/generate. Un template Rxml è fatto in questo modo:

xml.instruct! :xml, :version=>"1.0",xml.nodo do xml.sottonodo do xml.contenuto("ciao") endend

il risultato di un codice come questo sarà una cosa del genere:

<?xml version="1.0" encoding="UTF-8"?><nodo> <sottonodo> <contenuto>ciao</contenuto> </sottonodo></nodo>

La linea instruct! (in Ruby i metodo possono avere un "!" alla fine) serve ad impostare i dati generali di un documento. Ogni chiamata ad un metodo qualunque dell'oggetto xml creerà un nuovo nodo, se questo metodo viene chiamato con una stringa il nodo avrà come contenuto la stringa stessa, altrimenti se gli passiamo un blocco esso conterrà altri elementi. Se infine gli passiamo un Hash, esso verrà utilizzato per impostare gli attributi del tag.

Per realizzare un file RSS 2.0 servirà creare un file rss.rxml nella cartella app/views/feed/ esattamente come se si trattasse di un altro tipo di template, ma con un codice di questo tipo:

Page 37: Ruby on Rails

xml << %(<?xml version="1.0"?>\n)xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do xml.channel do xml.title("HForum") xml.link("http://html.it") xml.description("Ultimi messaggi nel forum di html.it")

for message in Message.latest xml.item do xml.title(message.topic.title) xml.description(message.body) xml.link( url_for(:controller=>'home', :action=>'show', :id=>message.topic.id) ) end end

endend

Rails è in grado di capire basandosi sull'estensione che tipo di template si trova davanti, e quindi di trattarlo in modo appropriato. Se salvate questo file come rss.rhtml esso verrà considerato come un template con ruby immerso nell'HTML e quindi non funzionerà.

In questo codice ci troviamo per la prima volta di fronte ad url_for, nell'ultima riga prima degli end. Questo helper è pensato per costruire indirizzi, con lo stesso meccanismo che abbiamo già visto per link_to/redirect_to, o per le opzioni di alcuni altri helper. In effetti tutti questi metodi si appoggiano ad url_for, quindi se avete dei dubbi relativi a come costruire un particolare url ricordate di verificare la documentazione relativa ad url_for.

Notate che in questo codice abbiamo usato solamente due blocchi do..end ma avremmo potuto realizzare documenti arbitrariamente complessi annidandone di più. Abbiamo anche fatto uso di un ciclo for per generare un elemento <item></item> per ogni messaggio, il che mostra come si possa integrare normale codice Ruby all'interno della vista senza alcuna difficoltà, con ovvi vantaggi di espressività.

I template Rxml - III messaggi vengono recuperati dal database tramite il metodo latest della classe Message, che non è stato ancora creato e che scriveremo adesso. La ragione per cui è giusto aggiungere un metodo alla classe Message piuttosto che scrivere il codice nel controller, o nella vista o in un helper, è che l'operazione di ricerca degli ultimi messaggi è strettamente collegata al modello.

Concettualmente è il modello che sa come andare a pescare i dati e non la vista o il controller.

Modifichiamo quindi il modello in app/models/message.rb in questo modo:

class Message < ActiveRecord::Base belongs_to :author belongs_to :topic def self.latest find :all, :order=>"created_at DESC", :limit=> 10 endend

Page 38: Ruby on Rails

Il metodo latest è definito con un self davanti. I metodi definiti in questo modo sono metodi di classe, come find o new che abbiamo già visto. Possiamo vedere anche alcune altre possibilità nell'uso di find, ovvero la scelta di inserire informazioni aggiuntive nella query di ricerca, usando come sempre degli argomenti opzionali passati per nome. In particolare, gli argomenti order e limit riflettono esattamente i meccanismi SQL sottostanti ed esprimono funzionalità piuttosto semplici per selezionare un numero preciso di risultati ordinati in base ad una precisa clausola (in questo caso selezionando gli ultimi in ordine di creazione).

Disabilitare il layoutSe però ora provate a visualizzare questo file, vi troverete di fronte ad un problema: intorno al nostro XML troviamo di nuovo il layout dell'applicazione, il che lo rende codice non valido. Fortunatamente come abbiamo già detto parlando dei layout, è possibile effettuare un'impostazione più precisa agendo a livello di singolo controller o di singola azione. In particolare, è possibile impostare un layout specificamente per un controller usando il metodo di classe layout:

class MyController < ApplicationController layout "mylayout"end

Anche questo metodo offre diverse possibilità, come il poter usare argomenti opzionali :except ed :only (con comportamento identico a quanto già visto per before_filter). Poiché stiamo parlando di template rxml, è giusto anche far notare come sia possibile scrivere dei layout in rxml, anche se è una pratica decisamente inusuale.

Per far sì che il nostro controller non usi nessun layout per nessuna delle sue viste (in quanto progettiamo di usare solo azioni che forniscano file xml per aggregatori di feed, e non html per i browser), è sufficiente aggiungere una linea come questa a feed_controller.rb :

layout nil

Caching con Rails - IAbbiamo visto che Rails offre un potenza ed un'espressività enormi, ma al mondo non c'è niente che sia gratis. Sebbene Ruby sia un un'interprete piuttosto veloce, Rails è composto da un numero notevole di astrazioni sulle funzionalità di base e per alcune applicazioni particolari questo potrebbe degradare troppo la velocità del framework, rendendolo inadatto. Ovviamente questo non è un problema specifico di Rails, ma di qualunque ambiente di livello molto alto, un po' lo stesso problema che si propone sviluppando in Java piuttosto che in Assembly. La presenza di un framework però può non essere solo una cosa negativa, in quanto è possibile fornire un meccanismo per affrontare questi problemi.

In particolare, Rails include un sistema particolarmente potente ed articolato di caching, che permette di raggiungere ottime prestazioni con una fatica ridotta. Esistono diverse applicazioni web scritte con questa piattaforma che sono in grado di gestire milioni di accessi al giorno senza avere setup hardware eccessivi, e anche se non sempre ci si trova di fronte a problemi di questo tipo, è importante sapere che esiste un modo provato per affrontarli.

Page cachingIl sottosistema di caching di Rails lavora a tre livelli di granularità: caching delle pagine, delle azioni e dei frammenti. Il caching delle pagine salva il risultato di un'azione come file HTML in una directory di default (ma configurabile) accessibile dal Web server, in modo che nelle richieste seguenti venga servito un file statico piuttosto che svolgere di nuovo le operazioni che potrebbero

Page 39: Ruby on Rails

avere un impatto prestazionale notevole. Sebbene questo modello di caching non sia adatto a tutte le situazioni, esso permette un'aumento di prestazioni impressionante, in quanto il Web server è in grado di servire le pagine html alla massima velocità possibile, ed anche su macchine "domestiche" si può arrivare tranquillamente a 3000 pagine al secondo.

Nella nostra applicazione d'esempio non ci sono molte pagine che si prestano a questa ottimizzazione, in quanto abbiamo disegnato l'applicazione in modo che molte pagine cambino a seconda che l'utente abbia eseguito o no il login, ma possiamo ad esempio applicare questa ottimizzazione ai feed. Per far si che sia effettuato il caching della pagina relative ad rss è sufficiente aggiungere una linea al controller:

class FeedController < ApplicationControllercaches_page :rss

Come è evidente, un meccanismo di caching come questo è straordinariamente facile da applicare, ed è perfettamente ragionevole cercare di far si che alcune pagine particolarmente visitate siano riproducibili staticamente in questo modo. La cosa, ad esempio è quasi sempre perfetta per i feed, in quanto questi file tendono ad essere oggetto di moltissime richieste pur non avendo sostanzialmente nessuna interazione con l'utente.

Ma ovviamente, dopo aver salvato una pagina, è necessario far sì che la cache venga in qualche modo invalidata quando i dati non sono più aggiornati. Rails fa si che sia possibile raccogliere queste funzionalità di pulitura della cache in apposite classi, gli sweeper. Creiamo dunque una classe FeedSweeper in app/models/feed_sweeper.rb, fatta in questo modo:

class FeedSweeper < ActionController::Caching::Sweeper observe Message

def after_save(message) expire_page(:controller=>'feed', :action=>'rss') endend

Il metodo observe serve a dire allo sweeper che deve attivarsi quando ci sono interventi su determinati oggetti. Si possono attivare diversi metodi di callback, che cioè verranno richiamati quando succede qualcosa, relativi ai vari momenti della vita di un oggetto, come la creazione, la distruzione o la scrittura nel database.

Ovviamente, il metodo expire_page serve a cancellare uno dei file della cache, e prende in input il solito insieme di argomenti per costruire un url. Ma uno sweeper deve essere associato a determinate azioni, in modo che possa intervenire solo quando vengono effettuate determinate azioni, quindi bisogna usare la direttiva cache_sweeper in un controller, come in questo caso:

class FeedController < ApplicationControllercache_sweeper :feed_sweeper, :only=>[:add_message, :add_topic]

Ancora una volta, troviamo gli argomenti :only ed :except, che ci permettono di associare le operazioni dello sweeper a determinate azioni. Non sarebbe utile effettuare un controllo per azioni come index, o show, dove gli oggetti non possono essere creati. Per quel che riguarda add_topic, invece, lo sweeper verrà attivato, ma non sarà eseguita nessuna callback se l'oggetto non viene creato, il che farà si che non venga invalidata la cache esattamente come vogliamo.

Sebbene il page caching sia straordinariamente efficace, i suo ambiti d'uso sono abbastanza ristretti. Ad esempio non possiamo utilizzarlo per nessuna pagina che cambi dopo il login. Fortunatamente Rails mette a disposizione altri due meccanismi di caching, il fragment caching e l'action caching.

Page 40: Ruby on Rails

Caching con Rails - II

Fragment caching e action cachingL'action caching è sostanzialmente identico al page caching, ma le richieste vengono ancora fatte passare attraverso il controller, il che fa si che ad esse possano essere applicati filtri ed altro. Il meccanismo d'uso è pero praticamente immutato, semplicemente si userà caches_action nei controller, e negli sweeper si potrà usare expire_action. Il controller HomeController, ad esempio, può far uso di un comodo caching di questo tipo:

caches_action :index, :add_topic, :show

Ovviamente, per gestire l'invalidazione della cache dovremo far uso di nuove regole di sweeping, creando una nuova classe TopicSweeper ex novo o aggiungendo delle regole allo sweeper esistente, e facendo si che sia controllata anche la classe Topic. Se scegliamo di gestire diverse classi in un unico sweeper, dovremo stare attenti alla gestione delle callback, in quanto l'oggetto potrà essere di qualsiasi tipo, e quindi dovremo usare una cosa come questa:

def after_save(thing) case thing when Topic expire_action :controller=>'home',:action=>'index' when Message expire_action :controller=>'home', :action=>'show', :id=>thing.topic.id endend

Il codice dovrebbe essere autoesplicativo, ma possiamo vedere una cosa interessante nella gestione relativa al caso in cui venga creato un nuovo messaggio: poiché il caching è relativo ad una azione con un parametro specifico, l'identificativo del topic, utilizziamo l'informazione salvata nel messaggio per costruire un url specifico, in modo da invalidare la cache soltanto per quell'oggetto.

L'ultimo tipo di caching è il caching dei fragment, frammenti, ed è quello pensato per gestire pezzetti statici all'interno di pagine dinamiche, sostanzialmente è necessario soltanto usare il metodo cache, ed un blocco:

<%= codice variabile %>

<% cache(:action=>"azione", :parametro=>"qualcosa") do %> <p> codice statico </p><% end %>

Per cancellare i dati possiamo utilizzare expire_fragment(:controller=>"mycontroller", :action=>"azione", :parametro=>"qualcosa"), se non utilizziamo argomenti per il metodo cache il frammento verrà associato univocamente alla coppia controller/azione. Il fragment caching permette di raggiungere una flessibilità estrema, ed in effetti l'action caching è implementato usando i frammenti, ma ovviamente è quello che offre il minore incremento prestazionale, quindi utilizzatelo solo quando gli altri metodi non sono possibili.

L'ultima cosa da dire sul page caching è che esso di default funziona solamente in modalità production, quindi se sperimentando con la nostra applicazione di prova vi sembra che non funzioni provate ad eseguire script/server con l'opzione "-e production", o altrimenti intervenite nel file config/environments/development.rb cambiando il valore della linea

Page 41: Ruby on Rails

config.action_controller.perform_caching = false

da false a true.

Il resto di Ruby on RailsEsaurire Rails in una guida così breve è impossibile, ma con le nozioni viste fino a questo punto è già possibile sviluppare applicazione complesse con grande facilità.

Tra gli argomenti che non sono stati trattati ma che sono decisamente interessanti e meritano approfondimento c'è anzitutto l'ottima integrazione della tecnologia AJAX in Rails, ovvero la possibilità di aggiornare una pagina o parti di essa utilizzando JavaScript senza doverla caricare interamente da zero.

Rails offre degli helper appositi che permettono di applicare effetti dinamici e trasformazioni assortite, di creare caselle di testo con ricerca live, editor in place e molto altro, utilizzando una sola riga di codice. Inoltre un particolare tipo di viste, le viste .rjs sono state appositamente pensate per questo scopo, e grazie ad esse è possibile realizzare trasformazioni anche molto complesse utilizzando solamente ruby e lasciando che il codice javascript sia generato automaticamente.

Altro argomento importante è quello delle migration. Abbiamo accennato come esse permettano di evolvere il database dal vivo senza rischiare la perdita di dati, e poiché le migration sono normale codice Ruby è possibile riutilizzare una migration per aggiornare anche molti differenti sistemi. Ad esempio Typo, un'applicazione per blog scritta con Rails, nel passaggio dalla versione 3 alla versione 4 ha cambiato radicalmente il proprio database ma l'aggiornamento da una all'altra è stato possibile per tutti gli utenti grazie alle migration definite dagli autori.

E poi un grande approfondimento sarebbe necessario riguardo ai test. Lo sviluppo di software moderno passa obbligatoriamente per la scrittura di test, che sono la migliore garanzia di qualità del codice. Rails rende la scrittura di test decisamente semplice e piacevole, aiutando ad abbattere la barriera psicologica che molti sviluppatori si trovano ad affrontare quando devono usare questi strumenti.

Insomma Rails è un mondo che merita di essere esplorato in profondità, e persino durante la scrittura di questa guida sono comparse delle novità molto interessanti, come ad esempio il modulo ActiveResource che permette di creare applicazioni dove gli oggetti sono accessibili direttamente tramite chiamate HTTP elementari fornendo quella che viene definita un'interfaccia "RESTful" praticamente senza fare nulla. Per non parlare del plugin che permette di generare completamente il modello a partire dal database, senza neanche dare un comando.

D'altronde non bisogna cadere nell'errore di pensare che Rails sia un ambiente perfetto. Sebbene sia adatto a moltissimi scopi esso perde molto del suo appeal di fronte a situazioni particolari. Se è vero che è possibile non usare ActiveRecord ed interfacciarsi con sistemi legacy direttamente, la mancanza di quel componente rende Rails molto meno potente. Allo stesso modo, se è vero che è semplice costruire dei web service con Rails, va valutata con cura la loro interazione con altri sistemi (SOAP è notoriamente un inferno per quel che riguarda l'interoperabilità).

Il consiglio è dunque quello di provare a fare qualche progettino con Rails, imparare il framework ed anche se si decide di non usarlo, mantenerlo come opzione aperta per i progetti futuri, continuando a tenere d'occhio il suo sviluppo. Magari oggi potrebbe non essere adatto, ma fra un paio di mesi, chissà.