#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 es una base de datos basada en documentos cuya diferencia con respecto a las bases de datos relacionales (como MySQL) es que no tiene esquema. En este episodio veremos los fundamentos del uso de MongoDB con la gema MongoMapper para crear una aplicación Rails sencilla. La primera vez que muchos desarrolladores Rails oyeron hablar de MongoDB fue con esta excelente anotación de John Nunemaker en el blog RailsTips que contrastaba siete funcionalidades de MongoMapper y MongoDB con los sistemas relacionales tradicionales. Merece la pena leer ese post si nos interesa utilizar MongoDB.
Una de las características mencionadas en esa anotación es que con MongoDB no tenemos que usar migraciones porque básicamente es un motor de base de datos al que se le ha eliminado el esquema. Cada fila es su propio documento, lo que significa que puede tener cualquier conjunto de atributos distinto o no de las otras filas de la base de datos. Dado que no hay un esquema fijo si queremos podemos definirlo sobre la marcha.
Instalación de MongoDB y MongoMapper
Para desarrollar nuestra aplicación en primer lugar tendremos que instalarnos MongoDB. Existen descargas para diferentes plataformas en la página de descargas del sitio oficial, pero para los que usamos OS X Chris Kampemier ha escrito un muy buen artículo en su blog. Este artículo incluye un práctico archivo plist
que sirve para poder arrancar MongoDB automáticamente en el arranque de nuestra máquina. Tenemos que asegurarnos de instalar la versión correcta (1.2.0) porque este artículo hace referencia a una versión anterior. Una vez que tengamos instalado y configurado MongoDB ya podemos visitar http://localhost:28017/ para ver si está funcionando.
Creación de una aplicación Rails con MongoDB
Ahora que tenemos MongoDB funcionando podemos empezar con la creación de nuestra aplicación. Crearemos una nueva aplicación desde cero llamada todo
.
rails todo
Vamos a utilizar la gema MongoMapper para que nuestra aplicación hable con MongoDB. Por tanto añadiremos la siguiente línea en el bloque de configuración correspondiente en /config/environment.rb
.
config.gem "mongo_mapper"
También tenemos que dar cierta información adicional que configuraremos en su propio archivo de inicialización. En el directorio /config/initializers
vamos a crear un nuevo archivo llamado mongo_config.rb
. Sólo tenemos que añadir aquí una línea para decirle a MongoMapper a qué base de datos se tiene que conectar.
MongoMapper.database = "todo-#{Rails.env}"
Al pasar el directorio actual como parte del nombre de la base de datos iremos creando diferentes bases de datos para nuestros entornos de desarrollo, pruebas y producción. Por supuesto para hacer un despliegue serio en producción tendríamos que tener en cuenta aspectos como la autenticación pero esto nos bastará para nuestros propósitos de demostración.
El último paso de la configuración de nuestra aplicación es la ejecución del siguiente comando para comprobar que tenemos la gema MongoMapper instalada.
sudo rake gems:install
Construcción de la Aplicación
Ya podemos empezar a desarrollar nuestra aplicación. Se trata de una sencilla lista de tareas donde tendremos un modelo Project
que puede tener muchas Tasks
. Para facilitarnos la tarea vamos a utilizar los Nifty Generators de Ryan Bates, aunque debemos tener en cuenta que podríamos escribir la aplicación sin utilizarlos.
Lo primero que haremos será crear un layout para la aplicación:
script/generate nifty_layout
A continuación generaremos el modelo Project
y un scaffold a juego. Project
tendrá un único campo, name
, y dado que no estamos generando un modelo normal de ActiveRecord con un esquema pasaremos la opci ón --skip-migration
para que no se genere ninguna migración.
script/generate nifty_scaffold project name:string --skip-migration
Esto nos generará un modelo, controlador y sus vistas. El modelo Project
así generado será un modelo ActiveRecord así que tendremos que cambiarlo para que trabaje con MongoMapper.
class Project < ActiveRecord::Base attr_accessible :name end
El código generado para el modelo Project
.
Todo lo que tenemos que hacer es eliminar la herencia de ActiveRecord::Base
y en su lugar incluir MongoMapper::Document
.
Para definir los atributos del modelo utilizamos el método key
. Le pasaremos el nombre del atributo, en este caso :name
, y también un tipo en forma de una calse Ruby. Para nuestro atributo :name
será String
. Nuestro modelo tendrá ahora el siguiente aspecto:
class Project include MongoMapper::Document key :name, String end
Con estos cambios que le hemos hecho al modelo ya podemos ejecutar nuestra aplicación y crear, actualizar y listar proyectos tal y como lo haríamos con una aplicación basada en una base de datos relacional sólo que en lugar de MySQL estamos utilizando MongoMapper y MongoDB.
En términos de interfaz MongoMapper funciona de manera parecida a ActiveRecord: podemos buscar, crear, actualizar y eliminar registros como siempre, e incluso soporta validaciones igual que ActiveRecord, así que podríamos añadir
class Project include MongoMapper::Document key :name, String, :required => true end
Validación en el modelo Project
.
Más atributos
Como MongoDB es una base de datos sin esquema podemos fácilmente añadir o alterar los atributos de un modelo sin tener que ejecutar migraciones. Si, por ejemplo, queremos añadir un atributo priority
a nuestro Project
tan sólo tenemos que añadírselo al modelo.
class Project include MongoMapper::Document key :name, String, :required => true key :priority, Integer end
Podemos manipular este nuevo atributo igual que lo haríamos en ActiveRecord, por lo que en el parcial del formulario de Project
podemos añadir un menú desplegable que nos permita escoger un valor al crear o modificar un proyecto.
<% 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 %>
Podemos modificar la vista show
para mostrar la prioridad de un proyecto:
<% 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>
Cuando visitemos la página para crear un proyecto veremos el menú para asignar la prioridad, que será mostrada una vez creado.
Dado que antes de añadir el atributo priority
ya habíamos creado un proyecto, podríamos preguntarnos qué prioridad tendrá asignada. Si miramos en ese proyecto veremos que la prioridad está vacía. Dado que no existe un valor de prioridad para el documento correspondiente en MongoDB tendrá un valor nil
.
Asociaciones
En nuestra lista de tareas queremos tener un modelo Task
; cada Project
tendrá muchas Tasks
. Vamos a generar el scaffold igual que hicimos con Project
pero nótese que el project_id
es una cadena en lugar de un número entero (que es lo que utilizaríamos normalmente).
script/generate nifty_scaffold task project_id:string name:string completed:boolean --skip-migration
Al igual que con el modelo Project
tenemos que modificar el archivo del modelo para que funcione con MongoMapper, eliminando el código específico de ActiveRecord.
<% title "Task" %> <p> <strong>Project:</strong> <%=h @task.project.name %> </p> <!-- Rest of form -->
Ahora el formulario mostrará el nombre del proyecto asociado, igual que lo haría con un formulario basado en ActiveRecord.
Búsquedas en MongoDB
Vamos a terminar este episodio mostrando algunas técnicas para realizar búsquedas de modelos de Mongo en la consola. Por ejemplo, podemos encontrar todos los proyectos con Project.all
>> Project.all => [#<Project name: "Yardwork", _id: 4b39d8c9a175750357000001, priority: nil>, #<Project name: "Housework", _id: 4b39fbd1a175750357000002, priority: 3>]
También podemos encontrar un proyecto por su id
…
>> Project.find('4b39d8c9a175750357000001') => #<Project name: "Yardwork", _id: 4b39d8c9a175750357000001, priority: nil>
…o pasar opciones para encontrar los registros ordenados de cierta manera.
>> Project.all(:order => "name DESC") => [#<Project name: "Yardwork", _id: 4b39d8c9a175750357000001, priority: nil>, #<Project name: "Housework", _id: 4b39fbd1a175750357000002, priority: 3>]
Al igual que con ActiveRecord podemos pasar condiciones a find
pero la diferencia con ActiveRecord es que las condiciones se pasan inline, o sea que encontraríamos todos los proyectos con una prioridad de 3 con:
>> Project.all(:priority => 3) => [#<Project name: "Housework", _id: 4b39fbd1a175750357000002, priority: 3>]
¿Y con condiciones más complicadas? Dado que Mongo no está basado en SQL no podemos pasar una cadena SQL en las condiciones. En vez de eso Mongo tiene su propio lenguaje para crear consultas más complejas. MongoMapper nos permite hacerlo pasándo un método a un símbolo. Por ejemplo, para recuperar todos los proyectos que tienen una prioridad de dos o más podríamos usar:
>> Project.all(:priority.gte => 2) => [#<Project name: "Housework", _id: 4b39fbd1a175750357000002, priority: 3>]
También podemos usar in
, pasándole un array de valores para encontrar, por ejemplo, los proyectos con una prioridad de 2 o 3.
>> Project.all(:priority.in => [2,3]) => [#<Project name: "Housework", _id: 4b39fbd1a175750357000002, priority: 3>]
La documentación de las condiciones de búsqueda es todavía un poco escasa pero podemos encontrar más información leyendo el fichero de tests en Github.
Eso es todo hoy. Tan sólo hemos cubierto lo más básico de MongoDB y MongoMapper así que tendremos que investigar por nuestra cuenta si queremos ir un poco más lejos. Hay una lista de correo a la que suscribirnos y podemos seguir a MongoDB en Twitter.
La gran pregunta que tenemos que hacernos es si deberíamos usar MongoDB en lugar de una base de datos relacional: nos toca decidir si MongoMapper y MongoDB son lo más adecuado para nuestro proyecto concreto. En todo caso, parece ser que a largo plazo las bases de datos basadas en documentos jugarán un papel muy importante en el desarrollo de aplicaciones Rails.