larryzhao
larryzhao

Reputation: 3213

mongoid - how to query by embedded object

I have the following model:

class User
  include Mongoid::Document
  store_in :users

  field :full_name,   :type => String
end

class Message
  include Mongoid::Document

  embeds_one :sender, :class_name => "User"

  field :text,        :type => String
end

I would like to store User and Message in separated standalone collections so that they could be queried directly, and I would like to have one copy of user for sender in each Message entry. Is my model correct for this kind of request?

And when I have an instance of User user, how could I query the messages where sender = user?

I've tried: Message.where(:sender => user) Message.where('sender.id' => user.id) both not work.

only Message.where('sender.full_name' => user.full_name) worked, but I don't want to rely on a text field when there's an id field to use.

What's the best way to do that?

How I save Message/User:

user = User.new
user.full_name = 'larry'
user.save

m = Message.new(:text => 'a text message')
m.sender = user
m.save

And it results in the database:

> db.users.find({ 'full_name' : 'larry'})
> db.messages.find({})[0]
{
        "_id" : ObjectId("4f66e5c10364392f7ccd4d74"),
        "text" : "a text message",
        "sender" : {
                "_id" : ObjectId("4f62e0af03642b3fb54f82b0"),
                "full_name" : "larry"
        }
}

Upvotes: 0

Views: 733

Answers (2)

shingara
shingara

Reputation: 46914

Like explain by Jordan Durran ( Mongoid lead developer ) in Google group of Mongoid : http://groups.google.com/group/mongoid/browse_thread/thread/04e06a8582dbeced#

You're going to need a separate model if you want to embed the user data inside the message. When denormalizing like this I generally namespace one of them, and create a module with the common fields to include in both - maybe in your case you can call it Sender?

class Sender 
  include Mongoid::Document 
  include UserProperties 

  class << self 
    def from_user(user) 
      Sender.new(user.attributes) 
    end 
  end 
end 

class User 
  include Mongoid::Document 
  include UserProperties 
end 

module UserProperties 
  extend ActiveSupport::Concern 
  included do 
    field :full_name, type: String 
  end 
end 

class Message 
  include Mongoid::Document 
  embeds_one :sender 
end 

You also don't need the :store_in macro on User - by default it's name would be "users".

Upvotes: 1

shingara
shingara

Reputation: 46914

You can't do what you do.

Your user document is save in his one collection because you use the store_in method. And you try save it on an other document ( Message)

If you really want 2 collections, you need use has_one :user in your Message class.

class Message

  has_one :sender, :class_name => 'User', :foreign_key => 'sender_id'

end

After you can get your message like :

Message.senders to have all of your sender.

Upvotes: 1

Related Questions