homeASCIIcasts

238: Mongoid 

(view original Railscast)

Other translations: En Es

Other formats:

Written by Gabriel Feitosa Vilar (cogumm.net)

Há alguns meses atrás no Episódio 194 [ver, ler] covered MongoDB e MongoMapper. MongoMapper é uma gema grande, mas agora existe uma alternativa chamada Mongoid que vale a pena considerar se você está pensando em usar MongoDB como o back-end de uma aplicação Rails. Uma das coisas que faz stand Mongoid fora é o seu site, que parece ótimo e que tem documentação detalhada. Mongoid é um projeto de outros projetos open-source que você pode aprender.

Instalando o MongoDB

Se você ainda não tem instalado em seu sistema o MongoDB a primeira coisa que você precisa fazer é acessar o página de downloads do MongoDB e baixar os arquivos apropriados. Se você estiver usando Mac OS X, então você pode instalar MongoDB atravez do Homebrew. Uma vez instalado, você pode verificar se está funcionando MongoDB visitando http://localhost:28017. Se você ver uma página como a abaixo, em seguida, significa que tudo está funcionando corretamente.

The page we see when MongoDB is running correctly.

Criando uma nova aplicação Rails utilizando o Mongoid

Agora que já temos o MongoDB instalado, vamos criar uma nova aplicação Rails 3 que usa Mongoid. Como é tradição de uma aplicações Rails de exemplo, esta será uma aplicação de blog.

$ rails new blog

A primeira coisa que precisamos fazer é adicionar a gema Mongoid ao Gemfile. No momento da versão escrita é a 2, que é a versão que o Rails 3 suporta, ainda está em beta, então vamos precisar especificar o número da versão.

/Gemfile

source 'http://rubygems.org'

gem 'rails', '3.0.1'
gem 'sqlite3-ruby', :require => 'sqlite3'

gem 'mongoid', '2.0.0.beta.19'
gem 'bson_ext'

Precisamos também de adicionar o bson_ext gem. BSON é uma versão binária do JSON e esta gema fornece algumas extensões C para acelerar a serialização Ruby BSON. Podemos, então, instalar as gema da maneira usual.

$ bundle install

Uma vez que as gemas tenham sido instaladas, precisaremos executar o gerador de configuração do Mongoid para que ele possa criar o arquivo de configuração YAML.

$ rails g mongoid:config

O arquivo padrão é mostrado abaixo. Podemos deixá-lo como ele é enquanto estamos desenvolvendo a nossa aplicação.

/config/mongoid.yml

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'] %>

Tudo está no lugar agora para nós poder começar a desenvolver a nossa aplicação. Vamos começar criando um modelo Artigo com os campos nome e conteúdo, e iremos usar o scaffolding do Rails para criar o controlador associado e visualização de código.

$ rails g scaffold article name:string content:text
    invoke  mongoid
    create    app/models/article.rb

Mongoid fornece geradores para os modelos modo que o gerador Mongoid o modelo é chamado quando um modelo é criado e, portanto, o ActiveRecord não é usado. Se abrirmos o arquivo do modelo, iremos ver que é uma classe simples foi incluida Mongoid::Document.

/app/models/article.rb

class Article
  include Mongoid::Document
  field :name, :type => String
  field :content, :type => String
end

Uma diferença é que esta classe vem a partir de uma classe model do ActiveRecord é que cada campo é explicitamente definido, junto com seu tipo. O tipo padrão é String, para que possamos remover os tipos desta classe, se quisermos.

/app/models/article.rb

class Article
  include Mongoid::Document
  field :name
  field :content
end

Agora a nossa aplicação está pronto para ser executada. Nós não precisamos executar qualquer banco de dados como migração do MongoDB, com o schema-lesse podemos passar todos os campos que preferimos em um documento. Se você visitar a página /articles, iremos ver a página usual gerada pelo scaffold, e podemos criar um novo Artigo, assim como nós, se tivéssemos um ActiveRecord como back-end.

The articles page after a new article has been created.

Adicionando campos

Uma das grandes vantagens de se usar o schema-less como banco de dados, é que é bastante simples de adicionar novos campos ao modelo. Vamos dizer que se esqueceu de acrescentar uma data de publicação do Artigo. Para adicionar um só precisamos modificar a classe do modelo.

/app/models/article.rb

class Article
  include Mongoid::Document
  field :name
  field :content
  field :published_on, :type => Date
end

Para que possamos visualizar e modificar o campo da data de publicação, iremos modificar o código da view e adicionar o novo campo ao formulário.

/app/views/articles/_form.html.erb

 <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>

E também a view para a ação do show.

/app/views/articles/show.html.erb

<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 %>

Agora podemos editar o artigo que acabamos de criar e adicionar uma data de publicação.

Adding a published-on date to an article.

Validações

O Mongoid utiliza o ActiveModel, o que significa que muitas das funcionalidades que usamos no ActiveRecord está disponível para nós aqui também, por exemplo validações, callbacks, dirty tracking, attr_accessible, e assim por diante. Com isto, se torna mais fácil adicionar as validações de modelos como os ActiveRecord do Mongoid.

/app/models/article.rb

class Article
  include Mongoid::Document
  field :name
  field :content
  field :published_on, :type => Date
  validates_presence_of :name
end

Se tentarmos criar um artigo sem um nome agora, iremos começar a ver os erros de validação o mesmo que veríamos por um modelo equivalente ao ActiveRecord.

Validation errors work just as they do with ActiveRecord models.

Associações

Não podemos ter um blog sem comentários, então iremos criar um modelo para Comentário ao longo associação, modo que cada artigo pode ter muitos comentários. Há duas maneiras para definir as associações com Mongoid. A primeira é através de uma associação de referência. Esta se comporta de maneira similar às relações entre tabelas no ActiveRecord e bancos de dados relacionais em que existem dois registros separados que estão relacionados através de uma coluna id. A outra maneira é uma associação incorporado, o que significaria, neste caso, os comentários estão incorporados dentro do mesmo documento que o artigo.

Em quanto você está decidindo qual destas abordagens, é a utilização que você precisa se perguntar se você realmente necessita dos registros associados por ficar por conta própria ou se você vai sempre acessá-los através do seu modelo pai. Neste caso, só vamos sempre estar recebendo comentários através de seu artigo associado por isso vamos usar uma associação incorporado. Nós definimos o relacionamento na classe de Artigo usando o método embeds_many.

/app/models/article.rb

class Article
  include Mongoid::Document
  field :name
  field :content
  field :published_on, :type => Date
  validates_presence_of :name
  embeds_many :comments
end

Em seguida, vamos gerar o modelo de Comentário.

$ rails g model comment name:string content:text

Nesta nova classe Comentário, podemos agora definir a sua relação com o Artigo.

/app/models/comment.rb

class Comment
  include Mongoid::Document
  field :name
  field :content
  embedded_in :article, :inverse_of => :comments
end

Usamos embedded_in para definir a relação de um comentário a um artigo. A opção inverse_of é necessário dizer ao Mongoid que o comentário deve ser incorporado completamente.

A fim de criar comentários para cada artigo e vê-los vamos precisar criar um CommentsController e algumas views, mas antes de fazermos isso iremos precisar alterar o arquivo de rotas. Para as associações incorporadas como esta, geralmente queremos usar rotas aninhadas como o objeto filho é sempre acessado através de seu pai e por isso iremos utilizar o nested do recurso comentários em artigos.

/config/routes.rb

Blog::Application.routes.draw do
  resources :articles do
    resources :comments
  end
end

Em seguida, iremos gerar o controller.

$ rails g controller comments

No controller iremos escrever uma criar uma ação para que possamos criar novas observações para um artigo. Com esta ação irá encontrar um artigo com base no parâmetro article_id, crie um comentário para este artigo e, em seguida redirecionar de volta para a página do artigo.

/app/controllers/comments_controller.rb

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, iremos adicionar algum código na view dos artigos, que irá mostrar os comentários de um artigo e permitir que os comentários sejam adicionados.

/app/views/articles/show.html.erb

<% 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 %>

Ao visitar a página de um artigo agora nós iremos ser capazes de criar um novo comentário e depois de ter submetido será mostrado abaixo do artigo.

Adding a comment to an article.

Se dermos uma olhada no log do desenvolvimento, podemos ver as consultas do MongoDB. Quando criamos um comentário agora, essa consulta foi feita.

MONGODB blog_development['articles'].update({"_id"=>BSON::ObjectId('4cd01fa4a74209eacc000003')}, 
{"$push"=>{"comments"=>{"_id"=>BSON::ObjectId('4cd04c74a74209ecb4000002'), 
  "name"=>"Eifion", "content"=>"I agree."}}})

As atualizações que consultam um modelo de artigo e acrescenta um novo comentário atribuir-lhe que os comentários não são armazenados separadamente. Isto significa que, se abrirmos o console do Rails e contar todos os comentários, teremos um resultado inesperado.

> Comment.count
 => 0

Os comentários que temos são incorporados em objetos e não estão disponíveis a nível global do documento. Para acessá-los, sempre temos que atravessar seu artigo associado.

>   Article.first.comments.count
 => 1

Para chegar aos atributos de um comentário, nós sempre necessitamos começar esse comentário através de uma associação, como esta aplicação, os comentários são incorporados aos registros.

Tipo de associações de referências

Se queremos um registro associado que também esteja disponível como um documento separado, então iremos necessitar de um para criar uma associação de tipo de referência. Nós iremos demonstrar isso modificando o nosso pedido para que possamos associar cada artigo de um autor. Primeiro iremos gerar um scaffold para um modelo novo Autor.

$ rails g scaffold author name:string

Antes de demonstrar associações vamos dar uma rápida olhada em um recurso interessante do Mongoid. Se nós adicionamos um método essencial para Mongoid classe de modelo que a key> será usada como o id para identificar esse modelo. Nós iremos fazer o nome do atributo a chave para o Autor.

/app/models/author.rb

class Author
  include Mongoid::Document
  field :name
  key :name
end

Se criarmos um autor, agora, quando estamos redirecionado para a página do autor, vamos ver o id na URL.

The page for an author showing the name as an id.

Se vamos utilizar esse recurso, então iremos precisar de assegurar o campo que escolhemos para usar como uma chave não editável para que o documento tenha uma seqüência permanente como um id que não vai mudar toda a vida do documento.

Voltar para associações de agora. Na classe Autor usamos references_many para definir o relacionamento com os artigos.

/app/models/author.rb

class Author
  include Mongoid::Document
  field :name
  key :name
  references_many :articles
end

Então, no modelo de Artigo que usamos o referenced_in.

/app/models/article.rb

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

Podemos agora usar essa associação como faríamos no ActiveRecord. Em nosso formulário para editar um artigo, podemos adicionar uma collection_select para que possamos selecionar um autor quando criar ou atualizar um artigo.

/app/views/articles/_form.html.erb

<div class="field">
  <%= f.label :author_id %><br />
  <%= f.collection_select :author_id, Author.all, :id, :name %>
</div>

Se modificarmos nosso artigo agora e selecionar um autor que vai pode ver que o autor do id incorporado no artigo quando examiná-lo no console.

> Article.first
 => #<Article _id: 4cd01fa4a74209eacc000003, name: "Mongoid", content: "it's awesome!", published_on: 2010-11-02 00:00:00 UTC, author_id: "eifion-bedford">

Diferentemente da associação comentários, no entanto, podemos acessar o autor separadamente.

> Author.first
 => #<Author _id: eifion-bedford, name: "Eifion Bedford">

Isso é tudo para este episódio sobre Mongoid. Há muito que não temos tratado aqui, mas a documentação é bastante abrangente e irá dar-lhe quase tudo o que você precisa saber para usar o Mongoid e o MongoDB na sua aplicação Rails.