Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

CouchDB e Rails: la gemma sul divano

Facciamo la conoscenza di CouchRest, una alternativa ad ActiveRecord per db document-oriented
Facciamo la conoscenza di CouchRest, una alternativa ad ActiveRecord per db document-oriented
Link copiato negli appunti

In questo articolo, che vuole essere il follow-up di Introduzione a CouchDB, ci focalizzaremo sui passaggi necessari a relazionare questo database document-oriented ad un applicazione scritta utilizzando Ruby on Rails.

Impareremo a conoscere CouchRest; una valida alternativa ad ActiveRecord capace di interfacciarsi con CouchDB senza invalidare il pattern MVC che sottende questo framework Web.

Nonostante l'intera architettura sia lungi dall'essere matura, le potenzialità che già esprime sono tali da giustificarne l'implementazione e lo studio; assisteremo, a breve, ad un rafforzarsi di queste tecnologie, che diverranno standard de-facto per la risoluzione di tutta una serie di problematiche legate a dati disomogenei tra di loro.

CouchRest: CouchDB on Ruby

CouchRest offre una serie di metodi per dialogare con CouchDB utilizzando Ruby. Per installarlo è sufficiente digitare:

gem install jchris-couchrest -a http://gems.github.com

A questo punto, eseguendo una sessione irb e digitando:

>> require 'rubygems'
=> false
>> require 'couchrest'
=> true

potremo sperimentare alcune delle funzionalità di questa interessante gemma; ad esempio eseguendo:

>> db = CouchRest.database!("http://localhost:5984/travels")
=> #<CouchRest::Database:0x1013f0438 ...

assoceremo alla variabile db l'handler della connessione al database travel (il punto esclamativo, abbiamo chiamato 'database!' e non 'database', fa in modo che il database venga creato in caso non dovesse esistere).

Come è facile notare l'identificativo della connessione è specificato tramite un url HTTP, questo perché CouchDB dialoga in JSON attraverso un interfaccia RESTful su protocollo HTTP; le operazioni CRUD sono quindi, come vedremo, equiparabili di fatto alla gestione classica di una risorsa Rails.

Una volta ottenuto l'handler di connessione diventa molto semplice eseguire le classiche azioni di salvataggio, modifica, e recupero dei dati salvati; facciamo un esempio:

>> attributes = { "type" => "Travel", "destination" => "Madrid", "price_per_person" => 1200, "updated_at" => Time.now }
=> {"updated_at"=> ...
>> result = db.save_doc(attributes)
=> {"rev"=>"2-1128495433", "id"=>"8efc1ef0a44584a02b2dbbf77cbf1f37", "ok"=>true}
>> record = db.get(result['id'])
=> {"_rev"=>"1-1128495433", "updated_at"=>"2009/10/11 14:36:40 +0000", "destination"=>"Madrid", "_id"=>"8efc1ef0a44584a02b2dbbf77cbf1f37", "price_per_person"=>1200, "type"=>"Travel"}
>> record["destination"] = "Barcellona"
=> "Barcellona"
>> result = db.save_doc(record)
=> {"rev"=>"3-1888613028", "id"=>"8efc1ef0a44584a02b2dbbf77cbf1f37", "ok"=>true}

Ecco quanto accade in queste poche righe di codice:

L'istruzione save_doc comunica a CouchDB la necessità di salvare un documento; se all'interno dell'hash degli attributi non vi è la chiave _id (come in questo caso) il documento viene creato, altrimenti il documento con id corrispondente a quello della chiave _id viene aggiornato, a patto che il valore della chiave _rev sia lo stesso sia su CouchDB che nell'hash del documento (questo per prevenire errori di coerenza generati da operazioni di scrittura/lettura concorrenti).

L'istruzione get recupera dal database il documento con id equivalente a quello passato come parametro. In questo caso nella variabile record verrà memorizzato l'hash relativo al documento salvato nell'istruzione precedente.

Dopo aver eseguito queste istruzioni è possibile studiarne il risultato utilizzando Futon.

Integrazione con Rails

Quanto mostrato fin qui ci consente di operare liberamente su documenti e database utilizzando Ruby, ma è ben lungi dalle comodità alle quali ci ha abituato ActiveRecord; per avvicinare CouchRest ad un vero e proprio ORM possiamo però utilizzare una versione aggiornata del plugin basic_model, realizzato da Geoffrey Grosenbach, che fornisce una classe base (BasicModel) dalla quale possiamo derivare tutti i modelli della nostra applicazione (un po' come succede con ActiveRecord::Base).

Dico 'aggiornata' in quanto il plugin ufficiale non è rimasto al passo con l'ultima versione di CouchRest, per ovviare ho effettuato alcune piccole modifiche; quindi utilizzeremo la mia versione di BasicModel.

Ultimo prerequisito: l'installazione di couchapp, un piccolo script in python che ci consentirà di definire all'interno dell'applicazione le 'query' map-reduce necessarie. Per tutte le informazioni su questa procedura rimando all'ottimo documento ufficiale.

Una volta ultimati i preparativi creiamo l'applicazione rails:

rails trip_archive

Quindi aggiungiamo nel suo config/environment.rb le seguenti dichiarazioni, necessarie per beneficiare di couchrest:

# all'interno del blocco Rails::Initializer.run
    config.gem "jchris-couchrest", :lib => "couchrest"
    config.gem "json"
    config.frameworks -= [ :active_record ]

# alla fine del file, fuori da ogni blocco
    require 'json/add/core'
    require 'json/add/rails'
    ENV['TZ'] = 'UTC'

Completata questa parte scarichiamo ed installiamo il sopraccitato plugin digitando in un prompt posizionato nella root dell'applicazione il seguente comando:

ruby script/plugin install git://github.com/sandropaganotti/basic_model.git

Ora non ci resta che generare il nostro primo modello, per farlo creiamo un file travel.rb nella directory app/models e modifichiamone il contenuto come segue:

class Travel < BasicMode

    def default_attributes
    {	
        "destination"  => nil,
        "partecipants" => [],
        "dates"        => { 
            "from" => Time.now.strftime("%d/%m/%Y"), 
            "to" => nil 
        }
    }
    end
end

Il metodo default_attributes serve, come lo stesso nome sottolinea, per specificare la struttura di partenza da associare ad un documento di questo oggetto. Da notare che il plugin BasicModel aggiungerà automaticamente un attributo con chiave type contenente il nome della classe che il documento rappresenta (quindi gli oggetti creati dalla classe Travel saranno memorizzati con type = 'Travel').

Prima di aprire la console e sperimentare alcune operazioni sul modello appena creato è necessario creare una struttura nella quale memorizzeremo i file JavaScript dei costrutti map-reduce che implementeremo a breve; portiamoci quindi con una shell nella root dell'applicazione e digitiamo:

couchapp generate db/app/trip_archives

dove con trip_archives stiamo specificando il nome del database che utilizzeremo su CouchDB. Ignoriamo per ora il risultato di questo comando e lanciamo script/console per verificare la bontà di quanto sviluppato finora:

>> t1 = Travel.new('trip_archives', {"destination"=>"London", "partecipants"=>["sandro"]})
=> #<Travel:0x101d7cdf8 ...
>> t1.save
...
=> #<Travel:0x101e2c3e8 ...
>> t1.partecipants << "francesca"
=> ["sandro", "francesca"]
>> t1.save
=> #<Travel:0x101e2c3e8 ...

Per osservare i risultati di queste istruzioni possiamo collegarci a Futon e navigare all'interno del database trip_archives dove dovrebbe essere presente un documento con le caratteristiche appena specificate.

Interrogare il database

Concludiamo questo piccolo tutorial introducendo una ricerca per destinazione, per fare questo dovremo in primis creare la funzione map necessaria e successivamente invocarla dall'interno della nostra applicazione.

Il primo passo di questa procedura consiste nel creare una cartella all'interno di db/app/trip_archives chiamata (la scelta è arbitraria) by_destination (in quanto la 'query' che stiamo creando ci consentirà di filtrare i documenti a seconda della loro destinazione). All'interno di questa cartella posizioniamo un file dal nome map.js contenente:

function(doc) {
  if (doc.destination != null) {
    emit(doc.destination, doc);
  }
};

Possiamo valutare il funzionamento di quanto appena creato eseguendo in un prompt dei comandi posizionato nella root dell'applicazione:

couchapp push db/app/trip_archives trip_archives

e successivamente utilizzando la select select view all'interno di Futon per scegliere la 'query' by_destination (sempre dopo aver scelto il database trip_archives).

Apriamo ora la console della nostra applicazione (ruby script/console) e digitiamo:

>> Travel.view('trip_archives','trip_archives/by_destination', :key=>'London').rows.size
=> 1
>> Travel.view('trip_archives','trip_archives/by_destination', :key=>'London').rows.first.partecipants
=> ["sandro", "francesca"]
>> Travel.view('trip_archives','trip_archives/by_destination', :key=>'Nairobi').rows.size
=> 0

Il metodo view, non presente in ActiveRecord, consente di chiamare una 'query' (il cui nome formale è, com'è ormai intuibile. 'view') specificandone il nome. L'opzione :key server per filtrare l'elenco dei risultati su di uno specifico valore (es: 'London'); se omessa, la view ritornerà l'intero elenco di documenti che rispondono ai criteri della funzione map invocata.

L'applicazione trip_archive è disponibile per il download.

Conclusioni

In questo articolo di carne al fuoco ce n'è parecchia; ho cercato di coprire il maggior numero di funzionalità possibili in modo da proporre una buona visione d'insieme, visione che ritengo necessaria al fine di poter meglio valutare i pregi ed i difetti di questa tecnologia che, pur soffrendo di tutti problemi legati alla sua giovinezza (soprattutto per quanto riguarda la parte di integrazione con Rails) riesce comunque a proporre uno schema operativo innovativo e molto attraente.

CouchDB si pone come serio concorrente ai database relazionali per tutte quelle applicazioni che nativamente implementano un approccio document-oriented: cataloghi, CMS e forum sono tra le tipologie che più beneficiano di questa strutturazione. Senza contare che CouchDB offre un buon controllo di versione e, a differenza di molti suoi 'cugini' relazionali, non ha alcun problema a scalare in caso il traffico lo richieda.

Ti consigliamo anche