I have one document embedded in another in Mongoid.
class A < B
include Mongoid::Document
embeds_one :shipping_address, class_name: 'Address'
I have, in my case, omitted the inverse relation:
class Address
# embedded_in :A
Why is it, that although the API works fine and completely as expected:
address = A.address
address.zip = 1234
a.changed? #true
address.save
a.changed? #false
The document is not actually saved?
If i return the embedded_in statement, the save actually works fine.
My understanding of the Mongoid source is not the best so don't kick me too hard mods.
I assume that Mongoid is similar to ActiveRecord in this regard. With ActiveRecord, defining a :has_many does not change the parent object but includes methods for accessing the child. belongs_to on the other hand pulls methods for managing foreign keys.
Looking at the source code for Mongoid it seems that persistence is called from the embedded class to the parent and not the other way around (source). Removing the embedded_in would remove the additional methods for inserting the child into the parent.
Feel free to correct me if I am way off :)
While you can gain a lot when you choose to embed documents in MongoDB, you do give up the ability to query everything outside of the context of the parent. If you want to be able to work with Address documents independently, outside of the context of the parent document, you should link documents with has_many instead of embedding with embeds_many. This comes with it's own set of pros and cons.
If you choose to embed documents, you do specify embedded_in in the model and you access the embedded documents like this:
a = A.new # Parent document
a.addresses # Embedded Address documents
( Documentation Reference )
Related
class Customer
embeds_many :addresses
#....
end
class Address
embedded_in :customer
belongs_to :region
#...
end
I don't care about ever finding all the embedded addresses that reference a region, but I want to be able to use the region= and region_id accessors that would be generated by belongs_to.
The documentation says both sides of the relationship must be defined UNLESS one of them is embedded, but when I leave out the Region-side association, I get:
NoMethodError: undefined method `[]' for nil:NilClass
from /[...]/accessors.rb:113:in `needs_no_database_query?'
this is wrong structure... the embedded documents cannot be referenced in any other model than the parent documents.
as the documentation says
Embedded 1-n:
One to many relationships where the children are embedded in the parent document.
Referneced 1-n:
One to many relationships where the children are stored in a separate collection from the parent document
so there is no way that an embedded doc ( stored as an attribute in some document ) to be referenced as a separate collection ( like the Referenced 1-n relations)..
you can look into altering the models definitions to suite your purposes...
take a look at this question which discuss the same problem
I have the following models:
class User
include Mongoid::Document
embeds_one :courier, class_name: "Users::Courier"
validates_associated :courier
accepts_nested_attributes_for :courier
end
module Users
class Courier
include Mongoid::Document
embedded_in :user
after_create :foo
def foo
puts "courier created"
end
end
but this callback is only run if i call save directly on the courier object, not when i save the parent object.
Thus having a nested form and a controller that creates the user including the courier does not run the create callback of the courier.
The mongoid documentation says that this is by design:
Callbacks are available on any document, whether it is embedded within another document or not. Note that to be efficient, Mongoid only fires the callback of the document that the persistence action was executed on. This is that Mongoid aims to support large hierarchies and to handle optimized atomic updates callbacks can't be firing all over the document hierarchy.
But how can i write code that gets executed whenever a courier is created? In my case i cannot run the code in the user's after_create callback, because there are users that do not have the embedded document courier. But when a courier gets added i want to run a callback.
Whats the best option to do so?
found the answer:
embeds_one :courier, class_name: "Users::Courier", cascade_callbacks: true
In Mongoid, pushing a document into an embeds_many relation automatically persists the document to the database. Normally, this is fine, but I run into problems when I need to track changes to the embedded document.
Say you have two models:
class List
include Mongoid::Document
embeds_many :items
field :title
end
class Item
include Mongoid::Document
embedded_in :list
field :name
end
This happens to the .changes attribute:
list = List.new(title: 'List title')
list.save #list is now persisted
item = Item.new(name: 'Item name')
item.changes #returns Hash with {'name' => [nil, 'Item name']}
list.items << item #saves item to database under the hood
item.changes #returns empty Hash, because item was autosaved with list
I could use item.previous_changes to inspect the changes that were made before pushing the item into the list, but in my specific case, this would give me all kinds of troubles to keep things manageable.
What I would like to achieve, is to be able to initialize an Item document and then add it to list (via << or push) without persisting it immediately.
I'm aware that Mongoid does provide an option to set up embeds_many relations without persisting (see http://mongoid.org/en/mongoid/docs/relations.html#embeds_many):
list.items.build(name: 'Another item')
The problem there is that Mongoid creates the Item instance for you. In my case, the documents in the embeds_many relation may be subclasses of Item (e.g. SpecialItem < Item), which wouldn't work well with build. But if anyone knows of a way to get around this limitation, I'd also be happy to accept it as an answer.
To answer my own question: the problem is solved by assigning the parent document to the child, instead of adding the child to the list of children.
Continuing on the example above, you should do
item.list = list #no database query
instead of
list.items << item #automatic database insert
to set the parent - child reference without autosaving anything to the database.
To follow up on the "building a subclass" issue using your example, you can:
list.items.build({
name: "Another Item"
}, SpecialItem)
To specify the (sub)class that you want Mongoid to build for you.
My models are:
class Node
include Mongoid::Document
end
class PhysicalServer < Node
embeds_many :network_interfaces
end
class NetworkInterface
include Mongoid::Document
embedded_in :physical_server
end
If I do:
server.network_interfaces.build()
server.save!
when I check database, I will see 2 NetworkInterface embedded documents with duplicate ids.
However if I do:
server.network_interfaces.create()
it'll work correctly (only 1 embedded document created).
Is it a bug in Mongoid, or there is something wrong with my code?
I'm using Ruby1.9.3 + Rails 3.2.9 + Mongoid 3.0.13
Not quite sure what the issue is "yet", but I was having the same issue which I have a workaround for the time being.
By doing a forced new lookup in my controller's update action, I was able to get rid of the '$pushAll' creating a duplicate on each call to update. I have a feeling it has something to do with Mongoid's build-up of atomic operations; using a new object simply just removes the 'build' action.
I've created a gist of the issue (hopefully making it possible to re-create both the failing scenario and a workaround: https://gist.github.com/jsmestad/d0103ba0197df9f4505b)
So this one has me a bit confused, and it may be unjustified but I would like some input regardless.
I have a rails 3.1.3 application with users who can view media files. I want to keep a history of their activities with regards to their media viewing. The goal is to be able to record a history such that I can track it from the user or from the media e.g. get data for user.history() and media.history() to show both all of the media accessed by a user and all of the users who accessed a particular piece of media, along with other data regarding each transaction.
So far the only way I have been able to come up with that reasonably satisfies this is to create a single history object for each transaction, sort of like this:
class history_item
belongs_to: media_object
belongs_to: user
(other transaction-specific data)
end
What concerns me about this is the number of database entries will quickly skyrocket with the typical use of this application. Is there a better way to approach this problem that I have not thought of? I am new to rails and large web application development in general, and I'm not entirely sure about how this sort of thing is normally done.
I would create such a model as a Viewing class as a HABTM model between User and MediaObject. This makes the actual structure more obvious than just having a relation called history. You can of course add methods for User#history and MediaObject#history as well, if you want that exact interface that you described.
class User
has_many :viewings
has_many :viewed_media_objects, :through => :viewings
end
class MediaObject
has_many :viewings
has_many :viewers, :through => :viewings
end
class Viewing
belongs_to :user
belongs_to :media_object
end
When querying these objects, for example given a user = User.first, if you know that you want to iterate over the user's viewed_media_objects in an action, you could use #user = User.include(:viewed_media_objects).find(params[:id]) to make sure rails fetches the related viewings and media objects at the same time as the user. This way, you won't get a whole bunch of database calls.