#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 é um banco de dados baseado em documento, que funciona de forma diferente dos tradicionais bancos de dados relacionais como o MySQL, este tipo de banco não possui schema (schema-free). Este episódio irá cobrir o básico do MongoDb com a gem MongoMapper para criar uma aplicação simples em Rails. Muitos desenvolvedores Rails ouviram falar pela primeira vez sobre MongoDB após uma postagem de John Nunemaker em seu excelente Blog RailsTips mostrando sete características do MongoMapper e MongoDB que contrastam com os tradicionais bancos de dados relacionais. Este post vale a pena ser lido se você pensa que pode estar interessado em usar o MongoDB.
Uma característica mencionada na postagem do blog e que faz o MangoDB interessante é que você não precisa usar migrações, como ele é basicamente um mecanismo(engine) de banco de dados sem esquema, cada linha é seu próprio documento, que pode ter seu próprio conjunto de atributos diferentes das outras colunas no banco de dados. Como não existe um esquema fixo nós podemos definir um se quisermos.
Instalando o MongoMapper e MongoDB
Para criar sua aplicação vamos precisar primeiro instalar o MongoDB. O download para várias plataformas pode ser encontrado na página de download no site do MongoDB. Se você está usando OS X há um grande artigo de como instalar e configurar MongoDB no site do Cris Kampmeir’s. Este artigo inclui um arquivo plist
que irá permitir que você crie um LauchDemon para iniciar o MongoDB automaticamente ao iniciar o sistema. O artigo faz referencia a uma versão antiga do MongoDB então tenha certeza que você instalou a última versão (1.2.0 no momento que escrevo isso). Uma vez que o MOngoDB esta instalado e configurado você pode visitar http://localhost:28017/ para ver se ele esta funcionando.
Criando uma aplicação Rails com MongoDB
Agora que nós temos o MongoDb instalado e rodando podemos começar a criar uma app Rails que irá trabalhar com ele. Nós iremos criar uma nova app do zero para este episódio chamada todo
.
rails todo
Iremos usar a gem MongoMapper para habilitar à conversa de nossa app com o MongoDB. Para isto nós precisamos adicionar à seguinte linha no bloco config de nosso /config/environment.rb
.
config.gem "mongo_mapper"
Nós também precisamos alguma configuração adicional que fazeremos dentro do arquivo initializer. No diretório /config/initializers
iremos criar um arquivo chamado mongo_config.rb
. Neste arquivo nós apenas precisamos adicionar uma linha para dizer ao MongoMapper qual banco de dados usar.
MongoMapper.database = "todo-#{Rails.env}"
Ao passar o ambiente atual como parte do nome do banco de dados, vamos criar diferentes bancos de dados para nosso desenvolvimento, ambiente de teste e produção. Se fossemos mover esta aplicação para produção não haveria necessidade de fazer autenticação e assim por diante, mas para propósitos de demonstração esta ótima.
O passo final na configuração de nossa aplicação e rodar o seguinte comando para ter certeza que a gem MongoMapper esta instalada.
sudo rake gems:install
Construindo nossa Aplicação
Agora nós podemos começar a construir nossa aplicação. Esta é uma simples aplicação todo list que terá um modelo Project
e cada Project
irá ter várias tarefas(Tasks
). Para escrever mais facilmente nossa aplicação iremos usar o Nifty Gererators do Rayns Bates, mas essa aplicação pode ser escrita sem ele.
A primeira coisa que vamos fazer é criar um layout para nossa aplicação que podemos fazer rodando:
script/generate nifty_layout
A seguir vamos gerar um model e um scaffold para ele. Project
irá ter apenas um campo, nome, e como não estamos gerando um model normal do ActiveRecord iremos passar a opção --skip-migraton
para que nenhum arquivo de migração seja gerado.
script/generate nifty_scaffold project name:string --skip-migration
Isto irá gerar um arquivo de model, controller e view para nós. O modelo Project
gerado irá usar o ActiveRecord então termos que mudar para ele usar o MongoMapper.
class Project < ActiveRecord::Base attr_accessible :name end
Tudo o que temos que fazer aqui é fazer com que a classe pare de herdar de ActiveRecord::Base
e inclua o MongoMapper::Document
.
Para definir atributos do modelo nós usamos o método key
. Passamos o nome do atributo, neste caso, :name
. e tambem o tipo que deve ser uma classe Ruby. Para nosso atributo :name
sera uma String
. Nosso modelo agora se parece com isto.
class Project include MongoMapper::Document key :name, String end
Com essa mudança fizemos no model nós podemos agora rodar nossa aplicação e usar isto para criar, atualizar e listar os projetos como seria com uma aplicacao baseada em um banco de dados relacional e ActiveRecord, apenas ao invés usando MongoMapper e MongoDB.
Em termos de interface o MongoMapper trabalha de maneira similar ao ActiveRecord. Nós podemos realizar finds e creates, update e destroy nos registro como normalmente fazemos. Ele até suporta validações da mesma maneira como o ActiveRecord, então podemos adicionar:
validates_presence_of :name
ao nosso model Project e não permitir mais criar um projeto com o name em branco. Com o MongoMapper há uma maneira melhor para adicionar validações movendo-as para a mesma linha. Para fazer com que o atributo name seja obrigatório podemos adicionar :required =>
true para os paramêtros do método key.
class Project include MongoMapper::Document key :name, String, :required => true end
Adicionando mais Atributos
Como o MongoDB é um banco dados sem esquema podemos facilmente adicionar ou alterar atributos no model sem a necessidade de rodar qualquer migração. Se nós quisermos adicionar um atributo priority
em nosso Project
podemos simplesmente adicionar isto em nosso model.
class Project include MongoMapper::Document key :name, String, :required => true key :priority, Integer end
Nós podemos interar com este novo atribuito simplesmente como seria com o ActiveRecord. Então,na partial form de Project
podemos adicionar um menu select que irá permitir que nós selecionamos a prioridade quando formos criar ou atualizar um projeto.
<% 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 %>
Nós temos que modificar a view show
para que possamos mostrar a prioridade de um projeto.
<% 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 nós visitamos a página New Project agora podemos ver um menu select para a prioridade e quando criamos um novo projeto esta prioridade irá ser mostrada.
Como nós criamos um project antes de adicionarmos o atributo priority
você pode querer saber qual a prioridade que lhe foi atribuída. Se você olhar nesse projeto verá que a prioridade esta em branco. Como o valor de priority não existe para este documento o MongoDB irá dar valor nulo(nil
)
Associações
Em nosso aplicativo Todo nós também queremos ter um model Task
(Tarefas); cada Project
irá ter muitas Tasks
(Tarefas). Iremos gerar um scaffold para isto como fizemos antes para Project
. Note que project_id
é uma string, nós normalmente usamos um integer.
script/generate nifty_scaffold task project_id:string name:string completed:boolean --skip-migration
No model de Project
precisamos modificar o arquivo de modelo para trabalhar com o MongoMapper, substituindo o código especifíco do ActiveRecord
class Task include MongoMapper::Document key :project_id, ObjectId key :name, String key :completed, Boolean belongs_to :project end
Novamente teremos que incluir o MongoMapper::Document
e usar o método key para definir os atributos do modelo. Você pode ter esperado que o project_id
tivesse o tipo Integer
, mas o MongoDB usa ObjectId
para armazenar ids.
Definimos o relacionamento de Taks's com Project como seria com o ActiveRecord usando o belongs_to
. Em Project
você pode esperar usar has_many :tasks
, mas para o MongoMapper usamos many
.
class Project include MongoMapper::Document key :name, String, :required => true key :priority, Integer many :tasks end
Nós podemos agora rodar o aplicativo e usar o controller e view gerados para criar novas tarefas. Entrar com o id do Project no formulário de tasks será complicado, embora o scaffold gerou um campo de texto para project_id
. Isto porque nós definimos ele como uma string quando criamos o scaffold. Vamos modificar a view para que ela use um menu select permitindo à escolha de qualquer Projeto existente. Da mesma forma que seria com um formulário do ActiveRecord podemos usar collection_select
para criar um menu select que vai listar todos os projetos.
<% form_for @task do |f| %> <%= f.error_messages %> <p> <%= f.label :project_id %><br /> <%= f.collection_select :project_id, Project.all, :id, :name %> </p> <!-- Rest of form... -->
Agora tempos um jeito fácil de selecionar um projeto quando estamos criando uma nova tarefa.
Depois que nós termos uma nova tarefa criada, seremos redirecionados para a página dessa tarefa que irá mostrar o id do projeto. Seria melhor se pudéssemos mostrar o nome do Project ao invés do id
. Podemos fazer isto substituindo @task.project_id
na view show por @task.project.name
<% title "Task" %> <p> <strong>Project:</strong> <%=h @task.project.name %> </p> <!-- Rest of form -->
O formulário irá agora mostrar o nome do projeto associado, apenas como seria com o ActiveRecord
Finds no MongoDB
Nós vamos finalizar este episódio mostrando algumas técnicas para usar finds para models Mongo no console. Em alguns casos é muito similar ao ActiveRecord. Por exemplo, podemos procurar todos( find all) projeto com Project.all
>> Project.all => [#<Project name: "Yardwork", _id: 4b39d8c9a175750357000001, priority: nil>, #<Project name: "Housework", _id: 4b39fbd1a175750357000002, priority: 3>]
Podemos também procurar um projeto por id
...
>> Project.find('4b39d8c9a175750357000001') => #<Project name: "Yardwork", _id: 4b39d8c9a175750357000001, priority: nil>
... ou fornecer opções para procurar todos os registros em uma dada ordem.
>> Project.all(:order => "name DESC") => [#<Project name: "Yardwork", _id: 4b39d8c9a175750357000001, priority: nil>, #<Project name: "Housework", _id: 4b39fbd1a175750357000002, priority: 3>]
Como o ActiveRecord nós podemos passar condições para o find
. Este difere do ActiveRecord em que as condições são passadas inline então nós irímos procurar todos os projetos com prioridade 3:
>> Project.all(:priority => 3) => [#<Project name: "Housework", _id: 4b39fbd1a175750357000002, priority: 3>]
E sobre as condições mais complexas? Como o Mongo não é baseado em SQL não podemos simplesmente passar uma string SQL para a condição. Ao invés ele tem sua própria linguagem para criar condições mais complexas. MongoMapper fornece um jeito conveniente para contornar isto passando um método para um símbolo . Por exemplo, para pegar todos os projetos que tenham prioridade de dois ou mais, podemos fazer:
>> Project.all(:priority.gte => 2) => [#<Project name: "Housework", _id: 4b39fbd1a175750357000002, priority: 3>]
Nós podemos também usar in
, passando um array de valores para o find, dizendo, projetos com a prioridade 2 ou 3.
>> Project.all(:priority.in => [2,3]) => [#<Project name: "Housework", _id: 4b39fbd1a175750357000002, priority: 3>]
A Documentação é um pouco escassa sobre condições no momento, mas você pode descobrir mais sobre eles através da leitura do arquivo de test no github. Isto é tudo por esse episódio. Nós apenas cobrimos o básico do MongoDB e MongoMapper então você esta encorajado a fazer sua própria pesquisa se quiser continuar. A uma maillgin list você pode se juntar a ela e você pode seguir o MongoDB no Twiiter.
A grande questão que você precisa fazer para si mesmo é, devo usar MongoDb ao invés de um tradicional bando de dados relacional? Isto realmente cabe a você pensar e decidir se o MongoDB é apropriado para seu Projeto Rails. Mas você esta incentivado a dar uma chance para ver se ele se ajusta ao seu projeto. Em termos mais longos parece que os bancos de dados baseados em documentos irão desempenhar um grande papel na construção de aplicações Rails.