Friday, July 29, 2011

Threaded/Recursive Objects in Rails 3.0.x & Mongoid 2.0.x

So I am building a useful internal app for work in Rails 3 (soon to be 3.1), Ruby 1.9.2 & MongoDB. For those of you that know me, it's a bit of stepping out of my comfort zone of experience with .NET. However, I'm really digging the power of the Ruby language and Rails environment so it's all good.


So with that said, I'm learning how to do some things with MongoDB & Mongoid that I had run into some trouble with. I'm planning to do a few posts coming up that will be just knowledge dumps and would hopefully be useful to some folks that may be running into the same problems as I did.


One of the things I wanted to do was provide the ability to have threaded comments in the system. Due to the schemaless design of MongoDB, this should be relatively easy. Some of you may have seen this gist then you have encountered the older way to handle this kind of situation where a Comment object would be able to be embedded within a Comment object.


Let's start with a plain Comment Mongoid document class in Ruby.
require 'mongoid'

class Comment
    include Mongoid::Document
    include Mongoid::Timestamps

    field :title, type: String
    field :body, type: String

    validates_presence_of :body
    
    attr_accessible :title, :body

end
Now, Mongoid 2.0.x now supports recursive models instead of using the syntax seen in the gist. This just requires one line to be added to the class:
recursively_embeds_many
Adding in this will now provide your Comment Mongoid model with two new methods: parent_comment & child_comments. These are dynamically created based on the name of your class (parent_# & child_#s) and they allow you to traverse the hierarchy.


The Comment model would now look something like:
require 'mongoid'

class Comment
    include Mongoid::Document
    include Mongoid::Timestamps

    field :title, type: String
    field :body, type: String

    #comment.parent_comment # Gets the parent.
    #comment.child_comments # Gets the children.

    recursively_embeds_many
    validates_presence_of :body
    
    attr_accessible :title, :body
end


That's it! Not so bad once I read the upgrade page that this was changed.

4 comments:

  1. Have you seen the 'mongoid-ancestry' Gem?

    ReplyDelete
  2. No, I haven't checked that out before. For what I'm doing, the built-in recursive embedding will probably be fine, however thanks for the heads up as I have a different app I can use that gem on.

    ReplyDelete
  3. hi eric, i tried the code in my rails 3 app for the User model but it doesn't work. the functionality that i require is let a user follow other users. i m using mongodb 2.2.1 and mongoid gem.

    irb(main):073:0* User.count
    => 4
    irb(main):074:0> u = User.last
    => #
    irb(main):075:0> u.child_users
    => []
    irb(main):076:0> u.child_users < [#]
    irb(main):077:0> u.save
    => true
    irb(main):078:0> u = User.last
    => #
    irb(main):079:0> u.child_users
    => []
    irb(main):080:0>

    ReplyDelete
  4. Prasad, sorry for the late reply but that's definitely not going to work with embeds_many. This is because when adding a model to an existing model, the model added must not have been persisted yet. This is because the new model is literally embedded in the document of the parent model.

    You would need to do something along the lines of references_many where the id of users following are stored as an array in the parent user model.

    ReplyDelete