#238 Mongoid
- Download:
- source codeProject Files in Zip (109 KB)
- mp4Full Size H.264 Video (18.2 MB)
- m4vSmaller H.264 Video (12.1 MB)
- webmFull Size VP8 Video (28.3 MB)
- ogvFull Size Theora Video (22.7 MB)
Hace unos meses, en el episodio 194 [verlo, leerlo], repasábamos MongoDB y MongoMapper. MongoMapper es una gema muy útil pero ahora existe una nueva alternativa llamada Mongoid que nos puede resultar interesante si estamos pensando en usar MongoDB con nuestra aplicación Rails. Mongoid destaca, entre otras cosas, por el cuidado aspecto de su sitio web y por lo detallado de su documentación. Muchos otros proyectos de código abierto podrían tomar nota en este aspecto.
Instalación de MongoDB
Si aún no hemos instalado MongoDB en nuestro sistema lo primero que tendremos que hacer es visitar la página de descargas de MongoDB para descargar los archivos apropiados. Si estamos usando Mac OS X podemos, en vez de esto, instalar MongoDB utilizando Homebrew. Una vez que esté instalado podemos comprobar que MongoDB está arrancado visitando http://localhost:28017
. Si vemos una página como la siguiente podemos estar seguros de que todo está funcionando correctamente.
Una nueva aplicación Rails con Mongoid
Una vez que tenemos MongoDB instalado, vamos a crear una aplicación Rails 3 con Mongoid. Siguiendo la tradición de las aplicaciones Rails de ejemplo, haremos un blog.
$ rails new blog
Lo primero que tenemos que hacer es añadir la gema Mongoid al Gemfile
. Como a día de hoy la versión de Mongoid que soporta Rails 3 es la versión 2 y está todavía en beta, tendremos que especificar el número de versión.
source 'http://rubygems.org' gem 'rails', '3.0.1' gem 'sqlite3-ruby', :require => 'sqlite3' gem 'mongoid', '2.0.0.beta.19' gem 'bson_ext'
También tenemos que añadir la gema bson_ext
. BSON es una versión binaria de JSON, y esta gema instala varias extensiones en C para acelerar la serialización de BSON en Ruby. Podemos luego instalar las gemas de la forma habitual.
$ bundle
Una vez instaladas las gemas tenemos que ejecutar el generador de configuración de Mongoid para que pueda crear el fichero YAML de configuración.
$ rails g mongoid:config
A continuación se muestra el fichero por defecto. Podemos dejarlo tal cual mientras desarrollamos la aplicación.
defaults: &defaults host: localhost # slaves: # - host: slave1.local # port: 27018 # - host: slave2.local # port: 27019 development: <<: *defaults database: blog_development test: <<: *defaults database: blog_test # set these environment variables on your prod server production: host: <%= ENV['MONGOID_HOST'] %> port: <%= ENV['MONGOID_PORT'] %> username: <%= ENV['MONGOID_USERNAME'] %> password: <%= ENV['MONGOID_PASSWORD'] %> database: <%= ENV['MONGOID_DATABASE'] %>
Ya tenemos todo en su sitio para empezar a desarrollar nuestra aplicación. Empezaremos creando el modelo Article
con los campos name
y content
y utilizaremos el andamiaje de Rails para crear el código de la vista y el controlador asociados.
$ rails g scaffold article name:string content:text invoke mongoid create app/models/article.rb
Cuando creamos un modelo se invoca el generador propio de Mongoid, no el de ActiveRecord. Si abrimos el fichero del modelo veremos que se trata de una simple clase que incluye Mongoid::Document
.
class Article include Mongoid::Document field :name, :type => String field :content, :type => String end
Una diferencia entre esta clase y un modelo de ActiveRecord normal es que cada campo tiene que ser explícitamente definido junto con su tipo. El tipo por defecto es String
por lo que en este caso particular podemos omitir los tipos.
class Article include Mongoid::Document field :name field :content end
Nuestra aplicación está lista para ejecutarse. No tenemos que lanzar ninguna migración en la base de datos porque MongoDB no necesita esquema y podemos pasar todos los campos que queramos a un documento. Si visitamos la página /articles
veremos la conocida página generada automáticamente por el generador de andamiajes y podremos crear un nuevo Article
igual que si estuviéramos trabajando con ActiveRecord.
Más campos
Una de las ventajas de utilizar una base de datos sin esquema es que es muy sencillo añadir campos a un modelo. Supongamos que nos olvidamos de añadir un campo para la fecha de publicación de un Article
. Para añadirlo tan sólo tenemos que modificar la clase del modelo.
class Article include Mongoid::Document field :name field :content field :published_on, :type => Date end
Para poder ver y modificar esta nueva fecha de publicación tendremos que modificar el código de vista y añadir el nuevo campo al formulario.
<div class="field"> <%= f.label :name %><br /> <%= f.text_field :name %> </div> <div class="field"> <%= f.label :published_on %><br /> <%= f.date_select :published_on %> </div> <div class="field"> <%= f.label :content %><br /> <%= f.text_area :content %> </div> <div class="actions"> <%= f.submit %> </div>
También en la vista de la acción show
.
<p id="notice"><%= notice %></p> <p> <b>Name:</b> <%= @article.name %> </p> <p> <b>Content:</b> <%= @article.content %> </p> <p> <b>Published:</b> <%= @article.published_on %> </p> <%= link_to 'Edit', edit_article_path(@article) %> | <%= link_to 'Back', articles_path %>
Ya podemos editar el artículo que ha sido recién creado y añadir una fecha de publicación.
Validaciones
Mongoid se apoya en ActiveModel, lo que quiere decir que tenemos disponible mucha de la funcionalidad a la que estamos acostumbrados con ActiveRecord: validaciones, callbacks, seguimiento de atributos modificados, attr_accessible
, etc. Esto hace que en Mongoid sea tan fácil añadir validaciones a los modelos como en ActiveRecord.
class Article include Mongoid::Document field :name field :content field :published_on, :type => Date validates_presence_of :name end
Si ahora intentamos crear el artículo sin nombre recibiremos los mismos errores de validación que veríamos en un modelo ActiveRecord equivalente.
Asociaciones
Todo blog necesita comentarios, por lo que crearemos un modelo Comment
con su asociación para que cada artículo pueda tener varios comentarios. En Mongoid hay dos maneras de definir asociaciones. La primera es a través de una asociación mediante referencia. Se comporta de forma similar a las relaciones entre tablas en ActiveRecord y las bases de datos relacionales en que hay dos regitros separados que se relacionan mediante una columna id
. La otra forma es mediante una asociación embebida que podría significar en nuestro caso que los comentarios se encuentran embebidos dentro del mismo documento que el artículo.
Para decidirnos por uno u otro enfoque deberíamos preguntarnos si alguna vez nos hará falta disponer de los registros asociados por sí mismos, o si siempre accederemos a ellos desde el modelo padre. En este caso sólo vamos a recuperar los comentarios a través del artículo asociado por lo que usaremos una asociación embebida. Definiremos la relación en la clase Article
utilizando el método embeds_many
.
class Article include Mongoid::Document field :name field :content field :published_on, :type => Date validates_presence_of :name embeds_many :comments end
A continuación generaremos el modelo Comment
.
$ rails g model comment name:string content:text
En esta nueva clase Comment
podemos definir la relación con Article
.
class Comment include Mongoid::Document field :name field :content embedded_in :article, :inverse_of => :comments end
embedded_in
se usa para definir la relación de comentarios con un artículo. La opción inverse_of
es necesaria para que Mongoid sepa a través de qué relación debe embeberse el comentario.
Para crear comentarios para cada artículo y verlos deberemos crear un controlador llamado CommentsController
y sus vistas, pero antes de eso vamos a alterar el fichero de rutas. En el caso de asociaciones embebidas como estas por lo general nos interesará usar rutas anidadas porque siempre se accede al objeto hijo a través de su padre: anidaremos el recurso de los comentarios debajo de los artículos.
Blog::Application.routes.draw do resources :articles do resources :comments end end
Luego generaremos el controlador.
$ rails g controller comments
En el controlador vamos a escribir una acción create
para que podamos crear nuevos comentarios para un artículo. Esta acción encontrará el artículo basándose en el parámetro article_id
, le añadirá el nuevo comentario y luego redirigirá a la página del artículo.
CommentsController < ApplicationController def create @article = Article.find(params[:article_id]) @comment = @article.comments.create!(params[:comment]) redirect_to @article, :notice => "Comment created!" end end
Finalmente añadiremos algo de código a la vista de show
del artículo para que muestre los comentarios de un artículo y permita añadir comentarios.
<% if @article.comments.size > 0 %> <h2>Comments</h2> <% for comment in @article.comments %> <h3><%= comment.name %></h3> <p><%= comment.content %></p> <% end %> <% end %> <h2>New Comment</h2> <%= form_for [@article, Comment.new] do |f| %> <p><%= f.label :name %> <%= f.text_field :name %></p> <p><%= f.text_area :content, :rows => 10 %></p> <p><%= f.submit %></p> <% end %>
Si ahora visitamos la página de un artículo podremos crear un nuevo comentario y tras enviarlo aparecerá debajo del artículo.
Si miramos el log de desarrollo veremos las consultas a MongoDB. Esta es la consulta que se hizo cuando creamos el comentario anterior.
MONGODB blog_development['articles'].update({"_id"=>BSON::ObjectId('4cd01fa4a74209eacc000003')}, {"$push"=>{"comments"=>{"_id"=>BSON::ObjectId('4cd04c74a74209ecb4000002'), "name"=>"Eifion", "content"=>"I agree."}}})
La consulta actualiza el modelo del artículo y le añade un atributo de comentario (recordemos que los comentarios no se almacenan de forma separada). Esto quiere decir que si abrimos la consola de Rails y contamos todos los comentarios daremos con resultado inesperado.
> Comment.count => 0
Los comentarios que hemos embebido en los artículos no están disponibles a nivel global. Para acceder a ellos tenemos que hacerlo siempre a través del artículo al que están asociados.
> Article.first.comments.count => 1
Como en nuestra aplicación los comentarios son registros embebidos, para llegar a los atributos de un comentario siempre tendremos que recuperar dicho comentario a través de la asociación.
Asociaciones referenciales
Si queremos que los registros asociados también estén disponibles como documentos separados tendremos que crear una asociación basada en referencias. Demostraremos su uso modificando nuestra aplicación para poder asociar cada artículo con su autor. Primero generaremos un andamiaje para el modelo Author
.
$ rails g scaffold author name:string
Antes de demostrar el uso de estas asociaciones le vamos a echar un vistazo a una característica interesante de Mongoid. Si añadimos el método key
a una clase de modelo de Mongoid, dicha clave se utilizará como el id
para identificar dicho modelo. Haremos que el atributo name
sea la clave de Author
.
class Author include Mongoid::Document field :name key :name end
Si ahora creamos un autor seremos redirigidos a la página de dicho autor, donde veremos el id
en la URL.
Si vamos a usar esta funcionalidad tendremos que asegurarnos de que el campo escogido como clave no sea editable de forma que el documento tenga como id
una cadena permanente que no cambie durante el período de vida del documento.
Volvamos a las asociaciones. En la clase Author
utilizaremos references_many
para definir las relaciones con los artículos.
class Author include Mongoid::Document field :name key :name references_many :articles end
Luego en el modelo Article
utilizaremos referenced_in
.
class Article include Mongoid::Document field :name field :content field :published_on, :type => Date validates_presence_of :name embeds_many :comments referenced_in :author end
Ya podemos utilizar dicha asociación igual que haríamos con ActiveRecord. En nuestro formulario para editar un artículo podemos añadir un collection_select
para poder escoger un autor cuando creemos o actualicemos un artículo.
<div class="field"> <%= f.label :author_id %><br /> <%= f.collection_select :author_id, Author.all, :id, :name %> </div>
Si ahora modificamos nuestro artículo y escogemos un autor podremos ver cuando lo examinemos por consola que el id
del autor queda embebido en el artículo.
> Article.first => #<Article _id: 4cd01fa4a74209eacc000003, name: "Mongoid", content: "it's awesome!", published_on: 2010-11-02 00:00:00 UTC, author_id: "eifion-bedford">
Sin embargo, al contrario que con la asociación de los comentarios, podemos acceder a los autores de manera separada.
> Author.first => #<Author _id: eifion-bedford, name: "Eifion Bedford">
Con esto concluimos este episodio dedicado a Mongoid. Por supuesto todavía nos queda mucho por ver, pero la documentación es bastante completa y en ella podremos encontrar todo lo que necesitaremos conocer para utilizar Mongoid y MongoDB en nuestras aplicaciones Rails.