Rails è un un framework open source per applicazioni Web, altamente produttivo, che implementa strettamente, e in maniera pressochè automatica, il pattern MVC. Dispone di uno strumento ORM molto potente e risulta ideale per la creazione di una “classica” applicazione web basata su DBMS.
Introduzione
Dopo aver rapidamente cercato di inquadrare il linguaggio Ruby nei precedenti due articoli, iniziamo a percorrere i “binari” di Rails (Ruby on Rails o più comunemente RoR).
È nostra intenzione descrivere i punti di forza di RoR, anche con qualche confronto con il mondo Java, e fare un esempio di applicazione realizzata con Rails. L‘esempio utilizzerà ActiveRecord, lo strumento di Object to Relational Mapping a disposizione per mappare i dati della nostra banca dati, nonchè alcuni script di generazione automatica del codice disponibili nello strumento.
Le origini di RoR
Il framework Ruby on Rails proviene dall‘applicazione Basecamp, uno strumento di project management di 37signals. Padre del framework può essere considerato David Heinemeier Hansson. È stato rilasciato per la prima volta in luglio 2004.
Nascendo da un contesto produttivo/commerciale tiene conto delle necessità che sorgono in tale ambito. Risulta quindi “produttivo”.
Alcune considerazioni di base
Al fine di comprendere la filosofia del framework mettiamo in campo alcuni concetti e cerchiamo di capire quali sono gli obiettivi che RoR cerca di raggiungere.
DRY (Don‘t Repeat Yourself)
Si riferisce al concetto secondo il quale non ci debbano essere duplicazioni in una applicazione Rails. Ogni idea deve essere espressa una sola volta. Questo obiettivo viene raggiunto utilizzando il linguaggio Ruby.
Convention Over Configuration
Questo concetto è forse il più importante tra quellli qui elencati. Si riferisce al fatto che Rails ha “sensible default” per pressochà© ogni componente dell‘applicazione. Questo implica che il lavoro principale si riduce a mettere insieme i pezzi piuttosto che programmare. Negli esempi che seguiranno cercheremo di mettere in evidenza le implicazioni di tale concetto.
Agility
Il concetto fa parte della struttura di Rails e quindi non si parlerà , come avremmo fatto nel caso di un contesto Java, del ricorso alle metodologie e agli strumenti necessari a raggiungere tal fine (vedi TDD, Ant, JUnit e quant‘altro). Nella struttura del framework il concetto è già implicito con una serie di strumenti che orientano (e spingono quasi) il programmatore a questo approccio.
Dal punto di vista architetturale RoR non si discosta troppo da una classica applicazione Web basata su di un framework Java tipo Struts. Se a questo aggiungiamo poi uno strumento ORM (tipo Hibernate) per il mapping del DB, le differenze si limitano al linguaggio e ad alcune convenzioni specifiche del framework. Ma vediamo in dettaglio.
Installiamo RoR
Supponendo di avere Ruby installato e funzionante sulla nostra macchina, apriamo una finestra DOS (alternativamente, una shell e i comandi sono equivalenti) e digitiamo
gem –version
Se come output ci viene restituita, senza alcun errore, la versione di RubyGem (p.e. 0.9.0) possiamo utilizzarlo per installare Rails.
Digitiamo (eventualmente con un utente con i necessari permessi)
gem install rails –include-dependencies
Attendiamo che l‘installazione termini ed è tutto fatto.
Digitiamo
rails -v
ed attendiamo la risposta della versione installata (p.e. Rails 1.2.2).
Nel caso in cui non avessimo Ruby installato correttamente sulla nostra macchina è necessario provvedere in tal senso (vedere il primo articolo della presente serie).
Hello World Application
Muoviamoci nella cartella nella quale vogliamo creare la nostra prima applicazione con RoR e digitiamo
rails hello
L‘operazione crea, automaticamente, una cartella di nome hello e una serie di sottocartelle e files che andremo ad analizzare nel corso della trattazione.
Figura 1 – File di progetto generati
così facendo abbiamo reso disponibili tutte le risorse necessarie allo sviluppo dell‘applicazione. Editando il file README immediatamente otteniamo un discreto numero di informazioni relative sia alla struttura ed utilizzo della directory del progetto creato, sia delle varie opzioni utilizzabili in ambiente di sviluppo, test e produzione.
Concentriamoci sulla cartella app. All‘interno di questa è contenuto il source code dell‘applicazione.
Strutturata in sottocartelle dal nome parlante, qui ritroviamo
- controllers che contiene i controller dell‘applicazione, classi che dovrebbero estendere ApplicationController
- models dove sono contenuti i model, mapping delle tabelle del database generate con il tool ORM ActiveRecord e che discendono da ActiveRecord::Base
- views che contiene i file .rhtml per la view dell‘applicazione
- helpers in cui troviamo gli helpers della componente view
I binari si iniziano a intravvedere. Torniamo al livello superiore della gerarchia di cartelle e focalizziamoci su config. All‘interno troviamo il file database.yml. Editiamo il file.
Figura 2 – File database.yml in config
Troviamo di default la configurazione dei tre ambienti classici (development, test, production) per MySQL. Rails pensa già a gestire le tre alternative classiche del processo. Lo vedremo a seguire quando utilizzeremo un database per la nostra applicazione.
Ovviamente possiamo cambiare DBMS in funzione delle nostre esigenze. Nel prosieguo utilizzeremo MySQL in quanto sono presenti nell‘installazione base di Rails dei driver (non particolarmente performanti ma adatti allo sviluppo e alla didattica, ma da sostituire per ambienti di esercizio) che possiamo utilizzare per essere velocemente produttivi.
Nella cartella script presente nella root del progetto è presente lo script server. Questo consente di lanciare un Web Server scritto in Ruby (WEBrick), embedded nei pacchetti base, ideale per l‘ambiente di sviluppo. Quindi digitiamo
ruby script/server
Vedremo in ouput qualcosa del tipo
=> Booting WEBrick...
=> Rails application started on http://0.0.0.0:3000
=> Ctrl-C to shutdown server; call with --help for options
...
Apriamo il browser e puntiamo a http://localhost:3000 e otteniamo qualcosa del tipo
Figura 3 – Pagina di default
Quindi il nostro web server è utilizzabile per la nostra prima demo. Creiamo un controller di nome Say. A tal fine utilizziamo lo script generate (presente nella cartella script).
ruby scriptgenerate controller Say
Figura 4 – Ouput dell‘esecuzioneÃÂ script
Nella cartella app/controllers troviamo un file say_controller.rb che contiene il seguente source
class SayController < ApplicationController
end
Aggiungiamo la nostra prima action. Come?
class SayController < ApplicationController
ÃÂ def helloword
ÃÂ end
end
Per invocare ciò che abbiamo prodotto, digitiamo nel browser http://localhost:3000/say/helloworld e richiamiamo la pagina. Otteniamo un errore in quanto non ci siamo ancora occupati del front end.
Figura 5 - Non è presente la pagina helloworld.rhtml
Andiamo nella cartella app/views/say e creiamo un file helloworld.rhtml con il seguente più che basico contenuto HTML.
ÃÂ Hello World
Invochiamo di nuovo http://localhost:3000/say/helloworld. A questo punto tutto dovrebbe essere a posto e dovremmo poter visualizzare la nostra "interessante" pagina html.
A questo punto risultano essere d‘obbligo alcune considerazioni relativamente ai binari imposti da Rails. Notiamo che il controller si chiama say e la action helloworld. La classe controller sarà SayController e il metodo in questa contenuto si chiama helloworld. La view sarà a questo punto helloworld.rhtml e sarà contenuta nella cartella viewssay . Nulla da eccepire, anzi molto comodo in quanto ci fa concentrare sull‘implementazione delle funzionalità lasciando al framework ogni altro dettaglio relativo all‘architettura e all‘interazione tra le varie componenti. Se vogliamo fare un veloce confronto con Struts, il framework per applicazioni web Java più utilizzato, notiamo come in Rails non sono presenti file (xml) di configurazione (struts.xml) con il fine di mettere insieme le varie componenti dell‘applicazione (realizzate in maniera indipendente) ma ci si basa piuttosto sulla semantica. Ovviamente tutte le convenzioni utilizzate sono suscettibili di override.
Scaffold application
Passiamo ad un esempio un pò più interessante. Andiamo a realizzare un‘applicazione Web che interagisce (in modalità CRUD) con i dati presenti in una tabella di una base dati rendendo dinamiche le pagine .rhtml, al momento utilizzate come se fossero statiche. Utilizzeremo per far questo una delle opzioni tra quelle disponibili nello script generate.
Creiamo un nuovo progetto ripetendo l‘operazione già fatta in precedenza
rails mydynamicapplication
In output avremo ottenuto la creazione della solita alberatura con tutti i file all‘interno.
Usiamo MySQL per creare i nostri tre database (sviluppo, test, produzione) necessari all‘applicazione. Li chiameremo countries_development, countries_test, countries_production. Queste informazioni, con relative user e password di accesso, saranno configurati in config/database.yml come accennato in precedenza. Nella cartella DB vanno gli script del database. Creiamo un file create.sql con il seguente contenuto e poniamolo nella cartella DB.
drop table countries if exists;
CREATE TABLE ‘countries‘ (
ÃÂ ‘id‘ int(11) NOT NULL auto_increment,
ÃÂ ‘name‘ varchar(128) collate utf8_bin NOT NULL,
ÃÂ ÃÂ ‘continent‘ enum(‘Asia‘,‘Europe‘,‘Africa‘,‘Oceania‘,‘America‘) collate utf8_bin NOT NULL,
ÃÂ PRIMARY KEYÃÂ (‘id‘)
)
Lanciamo lo script per creare la tabella nel database di sviluppo.
Quindi facciamo generare la nostra "scaffold application". Di che cosa si tratta? Si tratta di una vera e propria applicazione generata dal framework a cui stiamo dicendo "voglio un controller che mi gestisca un model". Eseguiamo l‘operazione di generazione
ruby scriptgenerate scaffold Country Admin
che corrisponde al più generico
ruby scriptgenerate scaffold [model] [controller]
Se l‘operazione è andata a buon fine, abbiamo generato tutto il necessario per gestire il contenuto della tabella Country via web. Lanciamo WEBrick e invochiamo tramite browser http://localhost:3000/admin. Al di là della gradevolezza del front-end con cui interagiamo, abbiamo disponibile un‘applicazione web con le 4 operazioni di base di interfacciamento alla base dati già disponibili.
Figura 6 - Pagina di defaultÃÂ della scaffold application
Clicchiamo su "New Country" e provvediamo ad inserire la nostra prima nazione. Questa, dopo aver effettuato l‘operazione di inserimento, comparirà nella lista delle nazioni unita ai relativi link di "Show", "Edit", "Destroy" del record. Velocissimo!!!
Andiamo a vedere cosa è accaduto dietro le quinte. Partiamo dal core e analizziamo il file admin_controller.rb
class AdminController < ApplicationController
ÃÂ def index
ÃÂ ÃÂ list
ÃÂ ÃÂ render :action => ‘list‘
ÃÂ end
ÃÂ # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
ÃÂ verify :method => :post, :only => [ :destroy, :create, :update ],
ÃÂ :redirect_to => { :action => :list }
ÃÂ def list
ÃÂ ÃÂ @country_pages, @countries = paginate :countries, :per_page => 10
ÃÂ end
ÃÂ def show
ÃÂ ÃÂ @country = Country.find(params[:id])
ÃÂ end
ÃÂ def new
ÃÂ ÃÂ @country = Country.new
ÃÂ end
ÃÂ def create
ÃÂ ÃÂ @country = Country.new(params[:country])
ÃÂ ÃÂ if @country.save
ÃÂ ÃÂ ÃÂ flash[:notice] = ‘Country was successfully created.‘
ÃÂ ÃÂ ÃÂ redirect_to :action => ‘list‘
ÃÂ ÃÂ else
ÃÂ ÃÂ ÃÂ render :action => ‘new‘
ÃÂ ÃÂ end
ÃÂ end
ÃÂ def edit
ÃÂ ÃÂ @country = Country.find(params[:id])
ÃÂ end
ÃÂ def update
ÃÂ ÃÂ @country = Country.find(params[:id])
ÃÂ ÃÂ if @country.update_attributes(params[:country])
ÃÂ ÃÂ ÃÂ flash[:notice] = ‘Country was successfully updated.‘
ÃÂ ÃÂ ÃÂ redirect_to :action => ‘show‘, :id => @country
ÃÂ ÃÂ else
ÃÂ ÃÂ ÃÂ render :action => ‘edit‘
ÃÂ ÃÂ end
ÃÂ end
ÃÂ def destroy
ÃÂ ÃÂ Country.find(params[:id]).destroy
ÃÂ ÃÂ redirect_to :action => ‘list‘
ÃÂ end
end
Ovviamente il nostro controller AdminController estende ApplicationController e contiene i metodi per effettuare le varie operazioni previste dall‘applicazione (list, show, create, upgrade, destroy). Inoltre troviamo il metodo invocato di default index che ridireziona all‘utilizzo di list
Proseguiamo con country.rb presente in model.
class Country < ActiveRecord::Base
end
Questo è quanto troviamo nel file. Questo è quanto è necessario per mappare la tabella del database di nome Country e rendere disponibili le operazioni di base (CRUD).
Dal punto di vista architetturale Rails utilizza ovviamente il pattern ActiveRecord. Questo significa che si tratta di oggetti che, oltre a contenere le informazioni presenti in una tabella o vista del database, incapsulano anche la logica di accesso al DB nonchè tutte le operazioni di recupero/aggiornamento.
Se vogliamo proprio trovare un neo all‘approccio, potrebbe essere il fatto che questa operazione viene effettuata in maniera "implicita". Questo significa che se voglio conoscere i campi presenti nella base dati devo accedere alla base dati e non posso far riferimento al codice generato.
A questo punto risulta d‘obbligo qualche considerazione in merito alla scaffold application. Ovviamente questa utility presente in Rails non consentirà di risolvere in automatico i problemi degli sviluppatori facendogli credere che "pensa a tutto la generazione automatica del codice". L‘utility risulta essere molto... utile, in particolare per applicazione di backend finalizzate alla manutenzione di tabelle della base dati e per dare un feeDBack immediato (ma tutt‘altro che definitivo) al cliente. In tutti gli altri casi sarà necessario provvedere alla realizzazione di componenti più personalizzate e meno automatizzabili.
Testing
Passiamo ad un altro punto di forza. Dalla root del progetto ci spostiamo nella cartella test. Vediamo cosa ci ha messo a disposizione Rails per la fase di testing.
Figura 7 - Il contenuto della cartella test
Ogni volta che lanciamo lo script generate, Rails intergra gli stub con unit test (per il model) e functional test (per il controller).
Occupiamoci di unit test. Editiamo country_test.rb
require File.dirname(__FILE__) + ‘/../test_helper‘
class CountryTest < Test::Unit::TestCase
ÃÂ fixtures :countries
ÃÂ # Replace this with your real tests.
ÃÂ def test_truth
ÃÂ ÃÂ assert true
ÃÂ end
end
Abbiamo la nostra classe che estende Test::Unit::TestCase già pronta e in attesa del codice dei nostri test. A questo punto dobbiamo effettuare due operazioni: predisporre il database di test e introdurre la verifica da effettuare nel codice.
A riguardo della prima operazione lanciamo lo script di creazione delle tabelle DBcreate.sql per il databaseÃÂ countries_test. Oppure ricorriamo all‘utilizzo di rake, strumento più che idoneo a tal fine.
rake DB:test:clone_strucure
La struttura del DB di sviluppo risulterà così copiata nel DB di test. È molto importante che il DB di test sia configurato con attenzione in quanto l‘esecuzione dei test elimina i dati immessi nel corso della verifica riportando il DB alla situazione originaria. Popoliamo il database di test. Utilizziamo lo strumento dei fixture. Ci spostiamo nella cartella test/fixtures. Qui troviamo un file countries.yml nel quale immettiamo i dati che utilizzeremo nel test. Come esempio il seguente potrebbe essere il contenuto del file
one:
ÃÂ id: 1
ÃÂ name: Albania
ÃÂ continent: Europe
A questo punto modifichiamo il file country_test.rb aggiungendo il codice di setup per il recupero del record dalla tabella Country
def setup
ÃÂ @country = Country.find(1)
end
e un test di verifica della creazione record.
def test_create
ÃÂ assert_kind_of Country, @country
ÃÂ assert_equal 1, @country.id
end
Salviamo il tutto e eseguiamo il test.
ruby testunitcountry_test.rb
Il risultato sarà , se tutto va a buon fine:
Loaded suite test/unit/country_test
Started
...
1 tests, 2 assertions, 0 failures, 0 errors
Non ci resta che continuare con tutti i test di cui abbiamo bisogno.
Conclusioni
Cosa abbiamo fatto oggi? Siamo passati da Ruby a Ruby On Rails installandolo con RubyGem, abbiamo creato un primo progetto statico, quindi un‘applicazione dinamica mediante lo strumento di generazione del codice presente nel framework (creando una scaffold application), abbiamo analizzato una parte dell‘alberatura disponibile nel progetto, abbiamo accennato a rake, eseguito degli unit test su una delle operazioni oggetto della generazione. Abbiamo quindi molti spunti per provare e approndire... in attesa del prossimo articolo. E i codici sorgenti degli esempi sono disponibili nell‘allegato in download a partire dal menu in alto a sinistra.
Riferimenti
[1] Dave Thomas, "Agile Software Development with Rails. The Pragmatic Programmer‘s Guide", Pragmatic Bookshelf, 2005
http://www.pragmaticprogrammer.com
[2] Patrick Peak, "Hibernate vs Rail: La sfida sulla persistenza" (traduzione di Carlo Possati)
https://www.mokabyte.it/cms/article.run?articleId=C1Q-6IT-KV5-JQ5
[3] Aaron Rustad, "Ruby on Rails and J2EE: Is there room for both?"
http://www-128.ibm.com/developerworks/linux/library/wa-rubyonrails/
[4] Ruby On Rails Site
http://www.rubyonrails.com
[5] Apache Struts
http://struts.apache.org/
[6] BaseCamp
http://basecamphq.com
[7] 37Signals
http://www.37signals.com