====== Ruby on Rails ====== FIXME Esperimenti con Ruby on Rails terminati con **esito negativo**. Principali problemi riscontrati * Ad ogni aggiornamento del framework l'applicativo ha cessato di funzionare, necessarie modifiche non ovvie ai sorgenti. * Rapidità di sviluppo non eccezionale, mancano componenti standard tipo editing di tabelle, ecc. * Linguaggio Ruby. Sarebbe meglio Python oppure PHP. Vedere piuttosto **[[django]]**. ===== Installazione ===== Su Debian Testing (Lenny) installati i seguenti pacchetti: irb irb1.8 libdbi-ruby1.8 liberb-ruby libgems-ruby1.8 libopenssl-ruby1.8 libpgsql-ruby1.8 libreadline-ruby1.8 libredcloth-ruby1.8 rails rake rdoc rdoc1.8 rubygems ===== Hello world ===== mkdir rails; cd rails rails spese cd spese ./script/server Il risultato può essere controllato col browser all'indirizzo **''%%http://localhost:3000/%%''**. Per il collegamento al database editare il file **''config/database.yml''**; si devono indicare tre database distinti: **development**, **test** e **production**. production: #adapter: mysql adapter: postgresql database: spese_production username: spese password: host: paros.rigacci.net ===== Controller, view (template), action ===== Si inizia creando un **controller** (cioè?) di nome //hello// che contiene il metodo predefinito di nome //index//: ./script/generate controller hello index Il risultato si vede all'indirizzo **''%%http://localhost:3000/hello%%''**. La logica del controller è nel file ''**app/controllers/hello_controller.rb**''; è lui che costruisce i dati per la **view**. La **view** associata è costituita dal file **''app/views/hello/index.rhtml''**. Se si definisce un **metodo pubblico** nel controller, questo diventa una **action**, ad esempio dopo il metodo //index// creiamo il metodo //world//: class HelloController < ApplicationController def index end def world @greeting = "hello world!" end end Visitando l'URL **''%%http://localhost:3000/hello/world%%''** un messaggio d'errore informa che manca il template, cioè la view. Basta creare il file **''app/views/hello/world.rhtml''** con questo contenuto: <%= @greeting %> ===== Tabelle di database, modelli e scaffold ===== Se si ha a che fare con un database, Ruby on Rails mette a disposizione delle impalcature (scaffold) preconfezionate per fare le operazioni di base (list, insert, update, delete, ecc.). Si tratta ovviamente di template (view) grezzi, ma costituiscono il punto di partenza per la programmazione perché la separazione tra logica del programma e presentazione è già predisposta. Nell'architettura **MVC** (Modello-Vista-Controller) di Ruby on Rails un **modello** corrisponde ad una tabella del database. In questo esempio vogliamo costruire un'impalcatura attorno alla **tabella operations** (movimenti su un conto bancario). La struttura del database si scrive nel file **''db/create.sql''**. CREATE TABLE operations ( id SERIAL PRIMARY KEY, operation_type_id INTEGER NOT NULL, date_time TIMESTAMP WITHOUT TIME ZONE NOT NULL, description CHARACTER VARYING NOT NULL, debit NUMERIC NOT NULL, credit NUMERIC NOT NULL ); **Attenzione alle convenzioni!** L'impalcatura **Operation** (singolare, con lettera maiuscola) si aspetta una tabella **operations** (plurale, minuscolo) con una chiave primaria **id**. Una chiave esterna ha nome del tipo **field_id**. Questa è una caratteristica di Ruby on Rails: **//convention over configuration//**. La tabella viene inizialmente creata nel database **conto_development**. Creiamo l'impalcatura: ./script/generate scaffold Operation date_time:datetime, description:string, operation_type_id:integer, debit:float, credit:float In relazione all'oggetto //Operation// (tabella nel database) vengono creati una serie di elementi: * **''models/operation.rb''**, nel modello si definiscono i controlli di validità e le relazioni dell'oggetto Expense * **''views/operations/*.html.erb''** sono le viste principali relative alle quattro operazioni di base: **index**, **show**, **edit** e **new** * **''controllers/operations_controller.rb''** il controller è il codice che gestisce le azioni ^ index | Mostra tutti i record: **''%%http://localhost:3000/operations/%%''** | ^ new | Per l'inserimento di un nuovo record | ^ show | Mostra i dettagli del singolo record | ^ edit | Mostra i dettagli del record e consente la modifica | ^ update | Accetta le modifiche fatte da //edit// | ^ destroy | Permette di eliminare un record | Notare che ogni action è collegata alle altre in modo intuitivo e naturale: dal //list// si può accedere al //new//, //edit// o //destroy//; ecc. Il modello corrisponde quindi a una tabella del database, ma per Ruby on Rails è una classe definita nel file **''app/models/operation.rb''**. Tutto quello che dal programma va al database passa per il modello, quindi se si vogliono aggiungere controlli di validità sui valori inseriti conviene metterli nel modello stesso: class Operation < ActiveRecord::Base belongs_to :operation_type validates_presence_of :operation_type_id, :date_time, :description, :credit, :debit validates_numericality_of :credit, :debit end ===== Scaffold dinamico ===== **ATTENZIONE:** Nella **versione 2** di Ruby on Rails lo scaffolding dinamico è stato **rimosso**. Invece di far generare uno scaffold per poi modificarlo si può chiederne uno dinamico, generato al volo. Anzitutto si crea il modello e un controller vuoto: ./script/generate model operation ./script/generate controller Operations poi dentro il controller si dichiara che si vuole lo scaffold dinamico: class OperationsController < ApplicationController scaffold :operation end Basta questo per avere a disposizione i metodi list, new, show, edit, update e destry. Perché lo scaffold dinamico funzioni non deve esistere né la view (es. ''app/views/operations/list.rhtml'') né la definizione del metodo nel controller: class OperationsController < ApplicationController scaffold :operation #def list #end end ===== Supporto PostgreSQL ===== Il supporto a PostgreSQL in Ruby si ottiene semplicemente (con Debian) installando il pacchetto **libpgsql-ruby1.8** (si tratta della libreria compilata, non quella //pure Ruby//). Queste invece sono le istruzioni per installare le estensioni RubyGem per PostgreSQL, qualora non si potessero installare come pacchetto. Per accedere a un database PostgreSQL da Ruby esistono due estensioni: **[[http://rubyforge.org/projects/ruby-dbi|postgres-pr]]** e **[[http://ruby.scripting.ca/postgres/|postgres]]**, la prima è una implementazione //pure ruby//, la seconda invece va compilata rispetto a Postgres (richiede pacchetti **-dev**), ma dovrebbe garantire prestazioni superiori. Il database abstraction layer **ruby-dbi** può usare indifferentemente entrambe. Le estensioni (le cosiddette //RubyGems//) si installano con **gem**. Eseguento **''gem install''** come **''root''** l'elenco delle RubyGems disponibili viene scaricato da **''%%http://gems.rubyforge.org%%''** e salvato in **''/var/lib/gems/1.8/source_cache''**. La directory predefinita per l'installazione delle RubyGems è **''/var/lib/gems/1.8/cache/''**. Si potrebbe operare come utente non privilegiato, in questo caso **''gem install''** salva l'elenco dei pacchetti disponibili in **''~/.gem/source_cache''**. La directory per installare i pacchetti si può specificare da riga di comando; ma poi come si fa a far trovare queste librerie a Ruby? gem install postgres-pr --install-dir /home/niccolo/lib/gems/1.8 L'installazione di **postgres-pr** avviene senza problemi: gem install postgres-pr Invece per installare la RubyGem **postgres** si devono installare anche i seguenti pacchetti Debian di sviluppo: ruby1.8-dev postgresql-server-dev-8.1 e bisogna indicare a ''gem'' dove trovare gli include di Postgres per compilare il driver: POSTGRES_INCLUDE=/usr/include/postgresql gem install postgres ===== Data Type money e PostgreSQL ===== Il tipo **money** di Postgres è deprecato, si consiglia di sostituirlo con un tipo **numeric**. Ruby on Rails addirittura non lo supporta: cercando di visualizzare un campo di tale tipo, viene visualizzato uno zero. Non esiste un metodo semplice per convertire un money in numeric, la strada più semplice pare essere quella di fare il dump, editare il file e poi il restore. ===== Tabelle correlate ===== Due tabelle collegate da una **relazione uno a molti**: ad esempio **expenses** e **people**, dove una persona può avere molte spese. La tabella people ha la chiave primaria **id**, mentre la tabella expenses ha il campo **person_id**. Notare la gestione dei plurali - anche irregolari - di Ruby on Rails! ^ people ^ | **id** | | name | ^ expenses ^ | **id** | | person_id | | amount | Si creano i due modelli Rails: ./script/generate model person ./script/generate model expense Il collegamento si ha semplicemente aggiungendo le calusole **has_many** e **belongs_to** ai rispettivi modelli: class Person < ActiveRecord::Base has_many :expenses class Expense < ActiveRecord::Base belongs_to :person Fatto questo nelle viste sarà possibile fare riferimento agli attributi della tabella correlata con un'istruzione del tipo: <%=h expense.person.name %> Infine bisogna che l'eventuale form di inserimento e modifica presenti un menu drop-down per il riempimento del campo. Purtroppo pare che questo non si possa ottenere con gli scaffold automatici per le azioni **new** e **edit**. Per fortuna la form deve essere definita solo in: **''%%app/views/expenses/_form.rhtml%%''**:


<%= select 'expense', 'person_id', Person.find(:all, :order => 'name').collect {|p| [p.name, p.id]}, :prompt => "Select a Person", :selected => session[:person_id] %>


<%= text_field 'expense', 'amount' %>

===== Integrazione con Apache2 ===== Invece di usare il web server WEBrick fornito con Ruby on Rails si vuole eseguire RoR tramite il modulo FastCgi di Apache. Il vantaggio è che ogni richiesta di pagina Ruby non deve avviare ex-novo l'interprete Ruby e l'ambiente Rails. Per Apache2 si consiglia il modulo **mod_fcgid** che è un'alternativa compatibile a **mod_fastcgi** (mod_fcgid is an actively maintained GPL project designed to replace mod_fastcgi). Si installano i pacchetti Debian Lenny: * libapache2-mod-fcgid * libfcgi-ruby1.8 * libfcgi0c2 **NOTA** Forse **libfcgi-ruby1.8** non serve, perché è un doppione di **libapache2-mod-fcgid**? Per utilizzare il modulo **mod_fcgid** si deve verificare che il file **''public/.htaccess''** dell'applicazione Ruby on Rails abbia le seguenti righe: AddHandler fcgid-script .fcgi RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] La prima usa la direttiva [[http://httpd.apache.org/docs/2.2/mod/mod_mime.html#addhandler|AddHandler]] per dire ad Apache che ogni richiesta per un file con estensione ''.fcgi'' deve essere servita tramite il modulo fcgid (avendo installato il modulo, probabilmente Apache ha già questa direttiva a livello globale). La seconda riga usa una [[http://httpd.apache.org/docs/2.2/mod/mod_rewrite.html#rewriterule|RewriteRule]] per dire ad Apache che ogni richiesta deve in realtà richiamare lo script ''dispatch.fcgi'', aggiungendo l'eventuale query string e senza applicare ulteriori regole (QSA = query string append, L = last rule). ===== Configurazione Apache2 ===== ==== Symbolic link ==== Questa dovrebbe essere la configurazione più semplice per servire una applicazione Ruby On Rails, tra l'altro offre la comodità di poter aggiungere nuove applicazioni Rails nel medesimo VirtualHost senza dover intervenire sulla configurazione di Apache. Si crea un link simbolico dalla DocumentRoot alla directory //public// del progetto Rails e per magia tutto dovrebbe funzionare. Supponiamo ad esempio che le applicazioni Rails risiedano in **''/var/rails/''**, per l'applicazione **expenses** si procede creando il link simbolico: /var/www/rails/expenses -> /var/rails/expenses/public Questa semplice procedura forse funzionava con Rails versione 1.1, con la versione 1.2.3 si ottiene invece il seguente errore (ad esempio cliccando su //About your application’s environment//): Routing Error no route found to match "/rails/info/properties" with {:method=>:get} Per risolvere si deve modificare il file **''config/boot.rb''** dell'applicazione, modificando la definizione di **''root_path''**: #root_path = Pathname.new(root_path).cleanpath(true).to_s root_path = Pathname.new(root_path).cleanpath(true).realpath().to_s Questo imposta la root_path dell'applicazione al suo percorso assoluto e in forma canonica. Siccome l'applicativo gira sotto FastCGI si deve effettuare il reload di Apache dopo aver modificato la configurazione di Rails. A livello di configurazione globale di Apache si potrebbe voler mettere solo quanto segue: SSLRequireSSL Allow from all AllowOverride FileInfo Options=FollowSymLinks,ExecCGI Questo tra l'altro impone che tutte le applicazioni Rails contenute in ''/var/www/rails/'' (in realtà sono solo dei link alle rispettive //public//) siano accedute obbligatoriamente via https. ==== Alias ==== L'unico vantaggio di questa configurazione pare essere quello di non richiedere alcuna configurazione particolare dell'applicazione Rails (in realtà non è vero: si deve comunque editare il file ''public/.htaccess''). Supponiamo che le applicazioni Ruby On Rails siano in **''/var/rails/''** e che si debba pubblicare l'applicazione **expenses**, nella configurazione globale di Apache si deve impostare: Alias /expenses /var/rails/expenses/public SSLRequireSSL Allow from all AllowOverride FileInfo Options=FollowSymLinks,ExecCGI Notare l'obbligo della connessione https. Volendo aggiungere altre applicazioni Rails si devono aggiungere altre direttive [[http://httpd.apache.org/docs/2.2/mod/mod_alias.html#alias|Alias]], quindi si deve essere amministratori di Apache. La sezione //Directory// può invece essere messa in comune tra tutte le applicazioni Rails; basta riferirla alla directory parente ''/var/rails/''. Nel file **''public/.htaccess''** dell'applicazione si deve mettere - oltre alle modifiche per utilizzare mod_fcgid - anche la direttiva [[http://httpd.apache.org/docs/2.2/mod/mod_rewrite.html#rewritebase|RewriteBase]]: RewriteBase /expenses Ricordarsi di effettuare il reload di Apache dopo aver cambiato la configurazione. ===== Filesystem e permessi ===== Prima domanda: **dove mettere le applicazioni Rails?** Non tutta la directory dell'applicativo deve essere accessibile via web: solo la //public//; quindi gli applicativi Rails devono stare fuori dalla DocumentRoot. Come visto sopra la //public// viene pubblicata con un link simbolico oppure con un alias di Apache. Come gli applicativi web stanno in ''/var/www/'' si è pensato di mettere gli applicativi Rails in **''/var/rails/''**, ciascuno in una propria sottodirectory. I file e le directory possono appartenere all'utente che sviluppa l'applicativo, ma il server web (utente www-data in Debian) deve avere i permessi di scrittura sulle directory **tmp/** e **log/** e gli oggetti in esse contenuti. Per funzionare sia tramite Apache sia tramite WEBrick eseguito dall'utente sviluppatore si impostano permessi **0770 per le directory** e **0660 per i file**, impostando utente e gruppo **utente:www-data**. Altra cosa importante: il file **''config/database.yml''** contiene le **credenziali per accedere al database** quindi dovrebbe essere leggibile al server web, ma **non leggibile a tutti**. In definitiva, una buona soluzione è questa: find tmp/ -type d -print0 | xargs -0 chmod 0770 find tmp/ -type f -print0 | xargs -0 chmod 0660 find tmp/ -type d -print0 | xargs -0 chmod 0770 find tmp/ -type f -print0 | xargs -0 chmod 0660 chown niccolo:www-data config/database.yml chown niccolo:www-data /database.yml chown niccolo:www-data /database.yml