User Tools

Site Tools


doc:appunti:prog:ruby_on_rails

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: 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

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
doc/appunti/prog/ruby_on_rails.txt · Last modified: 2011/02/21 11:34 by niccolo