Mongoid find embedded document - ruby-on-rails

I'm trying to search for an embedded document by its id, and return it. This is possible, but only, as far as I see, by using mongo to find the document which embeds it, and then searching that document in ruby for the embedded document I'm after. Like this:
# commenter.rb
def post
# todo: find syntax do avoid double query
if user = User.any_of({'posts.commenter_ids' => self.id}).last
user.posts.where('commenter_ids' => self.id).last
end
end
Seems simple, but I haven't found anything I like obviously on google/SO search.
Thoughts?

I override find method on my embedded documents using this gist: https://gist.github.com/cblavier/7889042
It's especially convenient when I want to use DelayedJob to delay embedded document methods (because DJ worker will use find(id) to deserialize the job)

class Order
embeds_many Products
end
class Product
embedded_in Order
end
prod_id = "1234" # the embedded doc's _id you request
o = Order.find(product_ids: prod_id)
p = o.products.find(prod_id)
See also Does querying Mongoid embedded documents hit the database server after loading a parent document

Right now I am including the following functionality into my embedded documents. It requires that you set the inverse_of option on the nested relationship.
# Returns the parent "embedded_in" relationship for this document
# #return [Mongoid::Relations::Metadata]
def self.parent_relationship
#parent_relationship ||= relations.values.find do |relation|
relation.macro == :embedded_in
end
end
# finds the document off of a root document. This method currently only works correctly if
# you explicitly set the inverse_of value on the embedded_in relationship
# #param [string | Moped::BSON::ObjectId] id
def self.find_using_parent(id)
id = id.is_a?(Moped::BSON::ObjectId) ? id : Moped::BSON::ObjectId(id)
parent = parent_relationship.class_name.to_const.where("#{parent_relationship.inverse_of}._id" => id).first
if parent
parent.__send__(parent_relationship.inverse_of).find(id)
end
end

You can't find a resource without the document it embeds. If you just want a relationship between the two instead of embedding it, you should use has_many instead of embeds_many http://mongoid.org/en/mongoid/docs/relations.html#has_many.
You can then find the document without it's related document.

Related

Mongoid: finding documents that are referencing another one (referenced 1-N)

I have a MongoDB database that uses mongoid referencing according to the documentation. Here are my models:
class Mydoc
include Mongoid::Document
# ...
has_and_belongs_to_many :editors, class_name: 'User', inverse_of: nil
# ...
end
and
class User
# actually, it's based on devise with some changes
# ...
# it does not reference back to Mydoc, see inverse_of there!
end
Now if I make a reference to a user (to grant her/him editor role), mongoid creates an array field called editor_ids that contains object ids of the documents in the users table. Works nice.
I'd like to have a query that shows all Mydoc documents where a certain user is editor. Can't figure out how to do that in mongoid. It's very simple in the mongo console:
db.mydocs.find({ editor_ids: { $in: [ ObjectId("52c97e58b878bcf156000001") ] } })
It works like charm. But I need this in rails, not in the console. Based on mongoid docs, I've tried this (and a hell lot of variations of it) but it does not work:
#docs_where_editor = Mydoc.where(:editors.in => [#user._id])
the result is an empty dataset. I would appreciate any hint.
I suppose it's not the where method, since in mongoid, find only accepts object ids you want to find.
If you have an :editor_ids array and you're only looking for a single value then you should be able to use multi-keys thusly:
Mydoc.where(:editor_ids => #user.id)
and if you have several editor IDs to search for then:
Mydoc.where(:editors_ids.in => array_of_user_ids)
would be the transliteration of your MongoDB console query to Mongoid.

mongoid return object contains references

so i'm using Mongoid and i want to return an object with it's refference. lets say i have:
class User
include Mongoid::Document
has_many :pictures
end
class Picture
include Mongoid::Document
belongs_to :user
end
the problem is with this code:
users = User.all #goes to the db
users.each do |user|
pic = user.pictures.first # <--- bad! hitting the db again here
end
so, how can return an object (user) that contains it's refference so i wont need to hit the db again?
This would normally be done with joining in ActiveRecord. Something like this:
User.includes(:pictures).each do |user|
But since MongoDB doesn't support joins, there's no way to load parent document and its referenced documents in one go. If this becomes a problem, you should consider embedding pictures in a user document (this, however, may cause more serious performance penalties).
Update
Mongoid has a way to eagerly load (search the page for "Eager Loading") referenced relations, but still separate queries are made. Database won't be hit on subsequent access of a relation.

Lazy loading the has_and_belongs_to_many field while using mongoid gem

I have the following models:
class Foo
has_and_belongs_to_many :bars
end
class Bar
has_and_belongs_to_many :foos
end
Each document representing the Foo instance has an array field called bar_ids. When ever a Foo object is retrieved from the DB, Mongoid retrieves the contents of the bar_ids also. When the document has 1000s of ids this can cause performance issues, i.e.
> Foo.first
#<Foo _id: 4fed60aa2bdf061c2c000001, bar_ids: [1, 2, 3.... 5000]>
In most cases I don't need to load the bar_ids. Is there way I can instruct the model to lazy load the bar_ids?
I asked the same question in the mongoid group and got an answer. Essentially, one need to read the documentation of three gems to understand mongoid 3 ( mongoid, moped, and origin).
Here is the relevant documentation section of origin gem. The without or only clause can be used to filter the resulting document structure.
Loading selectively:
Foo.without(:bar_ids).first
Reloading selectively:
def reload_fields(*fields)
return self if fields.empty? or new_record?
# unscope and prevent loading from indentity map
fresh = Mongoid.unit_of_work(disable: :current) do
self.class.unscoped.only(*fields).find(id)
end
fields.each do |attr|
write_attribute(attr, fresh.read_attribute(attr))
remove_change(attr) # unset the dirty flag for the reloaded field
end
self
end

How to obtain BEFORE and AFTER state of my object's related attributes?

In my application I have the following relationship:
Document has_and_belongs_to_many Users
User has_and_belongs_to_many Documents
What I am trying to figure out is how to perform the following:
Let's say a document has 3 users which belong to it. If after an update they become for ex. 4, I would like to send an email
message (document_updated) to the first 3 and a different email message (document_assigned) to the 4th.
So I have to know the users belonging to my Document BEFORE and AFTER the Document update occurs.
My approach so far has been to create an Observer like this:
class DocumentObserver < ActiveRecord::Observer
def after_update(document)
# this works because of ActiveModel::Dirty
# #old_subject=document.subject_was #subject is a Document attribute (string)
# this is not working - I get an 'undefined method' error
#old_users=document.users_was
#new_users=document.users.all.dup
# perform calculations to find out who the new users are and send emails....
end
end
I knew that my chances of #old_users taking a valid value were slim because I guess it is populated dynamically by rails via the has_and_belongs_to_many relation.
So my question is:
How do I get all my related users before an update occurs?
(some other things I've tried so far:)
A. Obtaining document.users.all inside DocumentController::edit. This returns a valid array, however I do not know how to pass this array to
DocumentObserver.after_update in order to perform the calculations (just setting an instance variable inside DocumentController is of course not working)
B. Trying to save document.users inside DocumentObserver::before_update. This is not working either. I still get the new user values
Thanks in advance
George
Ruby 1.9.2p320
Rails 3.1.0
You could use a before_add callback
class Document
has_and_belongs_to_many :users, :before_add => :do_stuff
def do_stuff(user)
end
end
When you add a user to a document that callback will be called and at that point self.users will still it yet contain the user you are adding.
If you need something more complicated it might be simpler to have a set_users method on document
def set_users(new_user_set)
existing = users
new_users = users - new_user_set
# send your emails
self.users = new_user_set
end

Is there a way for mongoid to use integer(number) as a default id rather than long hash value?

I just want to have default characteristic of ActiveRecord which uses incremental integers as id to reduce the length of the url.
For example, first article created will have url like "app.com/articles/1" which is default in ActiveRecord.
Is there any gem that supports this in mongoid?
You could always generate shorter, unique tokens to identify each of your records (as an alternative to slugging), since your goal is just to reduce the length of the URL.
I've recently (today) written a gem - mongoid_token which should take any of the hard work out of creating unique tokens for your mongoid documents. It won't generate them sequentially, but it should help you with your problem (i hope!).
You can try something like this:
class Article
include Mongoid::Document
identity :type => Integer
before_create :assign_id
def to_param
id.to_s
end
private
def assign_id
self.id = Sequence.generate_id(:article)
end
end
class Sequence
include Mongoid::Document
field :object
field :last_id, type => Integer
def self.generate_id(object)
#seq=where(:object => object).first || create(:object => object)
#seq.inc(:last_id,1)
end
end
I didn't try such approach exactly (using it with internal ids), but I'm pretty sure it should work. Look at my application here:
https://github.com/daekrist/Mongologue
I added "visible" id called pid to my post and comment models. Also I'm using text id for Tag model.
AFAIK it's not possible by design:
http://groups.google.com/group/mongoid/browse_thread/thread/b4edab1801ac75be
So the approach taken by the community is to use slugs:
https://github.com/crowdint/slugoid

Resources