Ruby

III parte: Iniziamo a percorrere i binari di Railsdi

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

  1. controllers che contiene i controller dell‘applicazione, classi che dovrebbero estendere ApplicationController
  2. models dove sono contenuti i model, mapping delle tabelle del database generate con il tool ORM ActiveRecord e che discendono da ActiveRecord::Base
  3. views che contiene i file .rhtml per la view dell‘applicazione
  4. 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)
http://www2.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

Condividi

Pubblicato nel numero
118 maggio 2007
Laureato in Scienze Statistiche e particolarmente attento alle novità che lo circondano, dal 1998 lavora nel settore dell‘IT. Prevalentemente coinvolto in progetti legati a Java, da un paio d‘anni è interessato al mondo Open Source. Dopo aver accumulato una notevole esperienza presso vari clienti e con vari ruoli (da programmatore…
Articoli nella stessa serie
  • Ruby
    I parte: introduzione al linguaggio
  • Ruby
    II parte: approfondiamo alcuni aspetti del linguaggio Ruby
  • Ruby
    IV parte: Rails... non solo Web Services
  • Ruby
    V parte: Ruby on Rails e Web 2.0
Ti potrebbe interessare anche