Table of Contents
Ruby on Rails
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 Expenseviews/operations/*.html.erb
sono le viste principali relative alle quattro operazioni di base: index, show, edit e newcontrollers/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: postgres-pr e 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:
<td align="left"> <%=h expense.person.name %></td>
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
:
<!--[form:expense]--> <p><label for="expense_person_id">Person</label><br/> <%= select 'expense', 'person_id', Person.find(:all, :order => 'name').collect {|p| [p.name, p.id]}, :prompt => "Select a Person", :selected => session[:person_id] %> <p><label for="expense_amount">Amount</label><br/> <%= text_field 'expense', 'amount' %></p> <!--[eoform:expense]-->
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 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 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:
<Directory /var/www/rails/> SSLRequireSSL Allow from all AllowOverride FileInfo Options=FollowSymLinks,ExecCGI </Directory>
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 <Directory /var/rails/expenses/public/> SSLRequireSSL Allow from all AllowOverride FileInfo Options=FollowSymLinks,ExecCGI </Directory>
Notare l'obbligo della connessione https. Volendo aggiungere altre applicazioni Rails si devono aggiungere altre direttive 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 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