John Patrick Given

Ruby, Rails, Javascript, Basketball, & Randomness

Rails 3, MongoDB, Mongoid, & Embedded Documents

So, I've been diving head first into MongoDB lately. It's awesome and quickly becoming my go-to database set up.

One of the nice things about a document database like MongoDB, is the notion of embedded documents. Yesterday I wanted to achieve modeling embedded documents within my rails application and found the documentation a little sparse. After succeeding I figured I should write a little post.

If you're unclear about what exactly embedded documents are, keep reading, it *should* be come clear as long as I don't write like a 4 year old.

For this example I will be assuming a that you have a working knowledge of Ruby on Rails. It's also important to note that I am using Mongoid as my MongoDB connector.

Let's begin and for this tutorial pretend we are making a site about dogs. One thing we *should* all be able to agree on is that a dog can have many breeds (think mutt). This means that a dog can have or has many breeds.

Let's start with the models



# Dog Model - dog.rb
class Dog
  include Mongoid::Document
  field :name, :type => String
  field :weight, :type => String
  field :height, :type => String
  
  embeds_many :breeds
  accepts_nested_attributes_for :breeds
end


Notice the embeds_many :breeds. That tells the dog model that it will accept an array of hashes that will represent breeds.



# Breed Model - breed.rb
class Breed
  include Mongoid::Document
  field :breed_name, :type => String
  field :breed_origin, :type => String
  field :breed_personality, :type => String
  
  embedded_in :dog, :inverse_of => :needs 
end


The big thing to note in the breed.rb file is the embedded_in :dog macro.  It's required for the relationship to work properly.



# Dog Controller - dog_controller.rb
def new
  @dog = Dog.new
  @dog.breeds.build

  respond_to do |format|
    format.html # new.html.erb
    format.xml  { render :xml => @dog }
  end
end


It's subtle, it's easy, but it's also easy to miss. Notice the @dog.breeds.build statement? That simple line prepares the controller for the association.



<%= form_for(@dog) do |f| %>
<% if @dog.errors.any? %>
<div id="error_explanation">
    <h2><%= pluralize(@dog.errors.count, "error") %> prohibited this dog from being saved:</h2>

    <ul>
        <% @dog.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
        <% end %>
    </ul>
</div>
<% end %>

<div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name %>
</div>
<div class="field">
    <%= f.label :weight %><br />
    <%= f.text_field :weight %>
</div>
<div class="field">
    <%= f.label :height %><br />
    <%= f.text_field :height %>
</div>
<%= 
    # Don't miss the subtleties here.
    # This builds the part of the form that embeds breeds.
    f.fields_for :breeds do |b| 
%>
    <div class="field">
        <%= b.label :name  %><br />
        <%= b.text_field :name %>
    </div>
    <div class="field">
        <%= b.label :weight  %><br />
        <%= b.text_field :weight %>
    </div>
    <div class="field">
        <%= b.label :breed_personality  %><br />
        <%= b.text_field :breed_personality %>
    </div>
<% end %>
<div class="actions">
    <%= f.submit %>
</div>
<% end %>


Above is a standard rails form used in creating and updating objects. The important part of this file is f.fields_for : breeds loop. That builds out the relevant form elements for embedding a breed (or two) inside the dog object.

After submitting the form above (with various data) you could see MongoDB return the following:



{ "_id" : ObjectId("4d5302e28b21a75180000009"), "name" : "Bailey", "weight" : "180lbs", "height" : "36\"", "_type" : "Dog", "breeds" : [
    {
        "name" : "German Shepherd",
        "breed_origin" : "Germany",
        "breed_personality" : "Loyal, Protective, Intelligent",
        "_id" : ObjectId("4d5302e28b21a75180000007"),
        "_type" : "Breed"
    },
    {
        "name" : "Labrador",
        "breed_origin" : "Unknown",
        "breed_personality" : "Loving",
        "_id" : ObjectId("4d5302e28b21a75180000008"),
        "_type" : "Breed"
    }
] }




And there you have it - two breeds embedded inside of the dog object. I hope this helps someone out there. Once you implement embeds_many using macros like embeds_one is simple.

The Mongoid web site has good documentation on associations, however they are by no means complete. I should mention though, without them, I wouldn't have been able to figure this out as quickly as I did.

Comments (1)

Oct 01, 2011
dan stadler said...
would like to try this out - have been poking around for a working example like this. Couple of questions - what version of rails did you write this with, and do you have an example of adding additional breeds to dogs while editing a dog record?

thanks -

Leave a comment...

The Fine Print

Hi there. My name is J.P. and I write web based software for a living. My real web site is located here. I tweet about stuff here. I scrobble music occassionally here.

This is my Posterous. I'm a terrible writer. Don't judge me.