#194 MongoDB and MongoMapper
- Download:
- source codeProject Files in Zip (98.3 KB)
- mp4Full Size H.264 Video (19.5 MB)
- m4vSmaller H.264 Video (13.4 MB)
- webmFull Size VP8 Video (34.2 MB)
- ogvFull Size Theora Video (24.5 MB)
MongoDB è un database basato su documenti che lavora in maniera diversa rispetto ai database relazionali tradizionali, tipo MySQL, in quanto è privo di schema. Questo episodio tratterà l’introduzione dell’uso di MongoDB con il gem MongoMapper per creare una semplice applicazione Rails. Molti sviluppatori Rails hanno sentito parlare di MongoDB per la prima volta dopo la pubblicazione di un post da parte di John Nunemaker sul suo eccellente blog RailsTips, in cui vengono mostrate sette funzionalità di MongoMapper e MongoDB a confronto con i tradizionali database relazionali. Vale la pena leggerselo se pensate di poter essere interessati all’uso di MongoDB.
Una delle funzionalità menzionate nel blog ed una di quelle che rende interessante MongoDB è che non c’è bisogno di usare le migrazioni dal momento che essenzialmente si tratta di un database privo di schema. Ogni riga è il proprio documento che può avere il proprio insieme di attributi differenti da altre righe nel database. Poichè non c’è uno schema fisso, possiamo definirne uno al volo, se vogliamo.
Installare MongoDB e MongoMapper
Per creare la nostra applicazione, prima dobbiamo installare MongoDB. I pacchetti di installazione per le varie piattaforme si possono trovare nella pagina di download del sito MongoDB. Se state usando OS X c’è un grande articolo su come installare e configurare MongoDB sul sito di Chris Kampmeier. Questo articolo include un pratico file plist
che vi permetterà di creare un LaunchDemon in modo che MongoDB parta automaticamente all’avvio. L’articolo fa riferimento ad una vecchia versione di MongoDB, per cui assicuratevi di installare piuttosto l’ultima versione (la 1.4.3 al momento della stesura dell’episodio). Un volta installato e configurato MongoDB, potete visitare http://localhost:28017/ per vedere se funziona tutto come dovrebbe:
Creazione di una applicazione Rails con MongoDB
Ora che abbiamo installato e funzionante MongoDB, possiamo iniziare a creare l’applicazione Rails che lavorerà con tale database. Creiamo una nuova applicazione Rails da zero per questo episodio, chiamandola todo
:
rails todo
Useremo il gem MongoMapper per permettere alla nostra applicazione di interfacciarsi con MongoDB. Per fare questo, dobbiamo aggiungere la seguente linea nel blocco di config del file /config/environment.rb
:
config.gem "mongo_mapper"
Dobbiamo anche impostare alcune altre configurazioni che usando un file di inizializzazione. Nella cartella /config/initializers
creiamo un nuovo file che chiameremo mongo_config.rb
. In questo file dobbiamo solo aggiungere una linea per dire a MongoMapper quale database usare.
MongoMapper.database = "todo-#{Rails.env}"
Passando l’ambiente corrente come parte del nome del database, creiamo diversi database per i nostri tre ambienti di sviluppo, test e produzione. Se spostassimo in produzione questa applicazione dovremmo fare di più per l’autenticazione e robe simili, ma per lo scopo di questo episodio ciò che abbiamo è più che sufficiente.
Il passo finale dell’impostazione della nostra applicazione è di lanciare il seguente comando per assicurarci che il gem di MongoMapper sia installato:
sudo rake gems:install
Costruiamo la nostra applicazione
Ora possiamo partire a costruire la nostra applicazione. Si tratta di una semplice applicazione di todo list che avrà un modello Project
e in cui un Project
avrà diversi Task. Per rendere più semplice la scrittura dell’applicazione, useremo i Nifty Generator di Ryan Bates, anche se occorre notare che se ne potrebbe fare anche a meno.
La prima cosa che facciamo è di creare un layout per la nostra applicazione:
script/generate nifty_layout
Poi generiamo il modello Project
e uno scaffold associato. Project
avrà un solo campo, name
, e dal momento che non stiamo generando un normale modello ActiveRecord, con uno schema associato, passiamo --skip-migration
al comando del generatore, in modo che non siano generati i file di migrazione:
script/generate nifty_scaffold project name:string --skip-migration
Questo comando genererà un file di modello, il controller e le viste per noi. Il modello Project
generato sarà un ActiveRecord, per cui dovremo cambiarlo per far sì che lavori con MongoMapper:
class Project < ActiveRecord::Base attr_accessible :name end
Il codice generato per il modello Project
.
Tutto ciò che dobbiamo fare qui e di rimuovere la discendenza da ActiveRecord::Base
e includere MongoMapper::Document
.
Per definire gli attributi del modello usiamo un metodo key
. Gli passiamo il nome dell’attributo, in questo caso :name
, e anche un tipo che dovrebbe corrispondere ad una classe Ruby. Per il nostro attributo :name
sarà String
. Il nostro modello ora appare così:
class Project include MongoMapper::Document key :name, String end
Con le modifiche fatte al modello possiamo ora lanciare la nostra applicazione e usarla per creare, aggiornare e mostrare un elenco dei progetti come faremmo in una normale applicazione basata su di un database relazionale e ActiveRecord, con la sola differenza che dietro le quinte ci sono invece MongoMapper e MongoDB.
In termini di sue interfacce, MongoMapper lavora analogamente ad ActiveRecord. Possiamo eseguire delle find e delle create, update e destroy di record, esattamente come faremmo normalmente. Supporta perfino le validazioni allo stesso modo di ActiveRecord, per cui possiamo aggiungere:
validates_presence_of :name
al nostro modello Project e non potremo più creare un nuovo progetto privo di nome. Con MongoMapper c’è un modo migliore per aggiungere le validazioni, spostandole inline rispetto alla dichiarazione del campo. Per rendere l’attributo name obbligatorio, ad esempio, potremmo aggiungere un :required => true
ai parametri del metodo key:
class Project include MongoMapper::Document key :name, String, :required => true end
Aggiunta della validazione al modello Project
.
Aggiungiamo ulteriori attributi
Poichè MongoDB è un database privo di schema, possiamo modificare o aggiungere attributi in modo semplice in un modello, senza la necessità di lanciare alcuna migrazione. Se volessimo aggiungere un attributo priority
al nostro modello Project
, potremmo farlo semplicemente dichiarandolo nel modello:
class Project include MongoMapper::Document key :name, String, :required => true key :priority, Integer end
Possiamo interagire con questo nuovo attributo esattamente come faremmo con un normale attributo ActiveRecord. Per cui, nella form partial del Project
, possiamo aggiungere una tendina che ci permetta di scegliere una priorità all’atto della creazione o dell’aggiornamento di un progetto:
<% form_for @project do |f| %> <%= f.error_messages %> <p> <%= f.label :name %><br /> <%= f.text_field :name %> </p> <p> <%= f.label :priority %><br /> <%= f.select :priority, [1,2,3,4,5] %> </p> <p><%= f.submit "Submit" %></p> <% end %>
Poi possiamo cambiare la vista show
per far mostrare la priorità di un progetto:
<% title "Project" %> <p> <strong>Name:</strong> <%=h @project.name %> </p> <p> <strong>Priority:</strong> <%=h @project.priority %> </p> <p> <%= link_to "Edit", edit_project_path(@project) %> | <%= link_to "Destroy", @project, :confirm => 'Are you sure?', :method => :delete %> | <%= link_to "View All", projects_path %> </p>
Quando visitiamo la pagina di un nuovo progetto, ora vediamo la tendina di selezione della priorità e quando creiamo effettivamente il progetto, la sua priorità ci verrà mostrata.
Poichè abbiamo creato un progetto prima di aggiungere il campo priority
, potreste domandarvi quale priorità venga assegnata a quest’ultimo. Se controlliamo, vedremo che il campo in questione è vuoto. Dal momento che un valore per il campo priority di quel record non esiste sul database MongoDB, avrà un valore nil
:
Associazioni
Nella nostra applicazione di Todo, vorremmo anche avere un modello Task
; ciascun progetto Project
dovrà avere diversi Task
. Generiamo lo scaffolding per questo nuovo modello come abbiamo fatto prima per il modello Project
. Si noti che il project_id
è una stringa, mentre di solito siamo abituati ad avere chiavi intere:
script/generate nifty_scaffold task project_id:string name:string completed:boolean --skip-migration
Così come fatto per il modello Project
, dobbiamo modificare la classe di modello per farla funzionare con MongoMapper, sostituendo le parti di codice specifiche di ActiveRecord:
class Task include MongoMapper::Document key :project_id, ObjectId key :name, String key :completed, Boolean belongs_to :project end
Di nuovo, abbiamo incluso MongoMapper::Document
, rimosso la specializzazione di ActiveRecord::Base e usato il metodo key
per definire gli attributi di modello. Vi sareste potuti aspettare che project_id
fosse di tipo Integer
, ma MongoDB usa ObjectId
per memorizzare gli id.
Definiamo la relazione del Task
con Project come faremmo anche con ActiveRecord, usando il metodo belongs_to
. Nel Project
potreste aspettarvi di usare has_many :tasks
, ma per MongoWrapper si usa in questo caso many
:
class Project include MongoMapper::Document key :name, String, :required => true key :priority, Integer many :tasks end
Ora possiamo lanciare l’applicazione e usare il controller e le viste generati dallo scaffold per creare nuovi task. Inserire un id del progetto sul nuovo task sarà complicato, tuttavia, poichè lo scaffold ha generato un text box per il campo project_id
. Tutto ciò a causa del fatto che lo abbiamo definito come campo di tipo stringa quando abbiamo creato lo scaffold. Modifichiamo la vista in modo che usi una tendina in grado di farci scegliere un progetto fra quelli già esistenti. Esattamente come avremmo fatto con una form ActiveRecord, possiamo usare la collection_select
per creare una tendina che elenchi tutti i progetti:
<% form_for @task do |f| %> <%= f.error_messages %> <p> <%= f.label :project_id %><br /> <%= f.collection_select :project_id, Project.all, :id, :name %> </p> <!-- Resto della form... -->
Ora abbiamo un modo semplice per scegliere un progetto all’atto della creazione di nuovi task.
Dopoe che abbiamo creato un nuovo task, saremo riportati alla pagina di quel task che ci mostrerà l’id del progetto a cui quel task è stato associato. Sarebbe meglio se potessimo mostrare il nome del progetto, piuttosto. Lo possiamo fare sostituendo @task.project_id
nella vista show del task con @task.project.name
:
<% title "Task" %> <p> <strong>Project:</strong> <%=h @task.project.name %> </p> <!-- Resto della form -->
La form ora mostrerà il nome del progetto associato al task, proprio come se fosse una form basata su ActiveRecord:
Find in MongoDB
Concludiamo l’episodio mostrandovi un po’ di tecniche per il recupero mediante find di modelli Mongo da console. In un certo senso si comporta in modo molto simile ad ActiveRecord. Per esempio, possiamo recuperare tutti i progetti con Project.all
:
>> Project.all => [#<Project name: "Yardwork", _id: 4b39d8c9a175750357000001, priority: nil>, #<Project name: "Housework", _id: 4b39fbd1a175750357000002, priority: 3>]
Possiamo anche recuparare un progetto per id
…
>> Project.find('4b39d8c9a175750357000001') => #<Project name: "Yardwork", _id: 4b39d8c9a175750357000001, priority: nil>
…o fornire delle options al metodo all per recuperare i record un determinato ordine:
>> Project.all(:order => "name DESC") => [#<Project name: "Yardwork", _id: 4b39d8c9a175750357000001, priority: nil>, #<Project name: "Housework", _id: 4b39fbd1a175750357000002, priority: 3>]
Come con ActiveRecord, possiamo passare delle condizioni al find
. In questo si distingue da ActiveRecord, in quanto le condizioni sono passate inline, per cui, ad esempio, troveremmo tutti i progetti con priorità pari a 3 con:
>> Project.all(:priority => 3) => [#<Project name: "Housework", _id: 4b39fbd1a175750357000002, priority: 3>]
Che succede con condizioni più complesse? Poichè Mongo non si fonda su SQL, non possiamo semplicemente passare una stringa SQL nelle condizioni. Però ha il proprio linguaggio per la creazione di condizioni più complesse. MongoMapper fornisce un metodo comodo per superare questa cosa, fornendo un metodo per ogni simbolo. Per esempio, per ottenere tutti i progetti cha hanno una priorità di due o più, possiamo scrivere:
>> Project.all(:priority.gte => 2) => [#<Project name: "Housework", _id: 4b39fbd1a175750357000002, priority: 3>]
Possiamo anche usare la in
, passandogli un array di valori da recuperare. Ad esempio, per ottenere i progetti con priorità pari a 2 o 3:
>> Project.all(:priority.in => [2,3]) => [#<Project name: "Housework", _id: 4b39fbd1a175750357000002, priority: 3>]
Per ora la documentazione è un po’ sparsa fra le condizioni di find, ma potrete scoprire di più in merito leggendo l’appropriato file di test su Github.
Questo è, tutto per questo episodio. Abbiamo solo trattato le basi di MongoDB e MongoMapper, per cui siete invitati ad approfondire autonomamente se volete saperne di più. Esiste una mailing list a cui potete unirvi e potete anche seguire MongoDB su Twitter.
La vera domanda che dovete porvi è se dobbiate preferire MongoDB ad un database relazionale tradizionale. Spetta solo a voi decidere se pensiate che MongoDB e MongoWrapper siano adatti al vostro progetto Rails, ma siete incoraggiati comunque a provare un po’ MongoDB per vedere se potrebbe essere un buon candidato per i vostri progetti. Alla lunga, pare che i database documentali giocheranno un grosso ruolo nella costruzione di applicazioni Rails.