Mongoid: Suppressing _id for embeds_one? - mongoid

I'm looking for a way to suppress the generation of the _id field in the child class of an embeds_one association, but can't find a way to do it.
Anyone know how to accomplish this?
If you create an embeds_one association for a child class, then the _id field is not required, since there will only ever be one child embedded in the parent.
Mongoid 7.0, MongoDB 4.2.
This is easy when using Mongoose in the NodeJS/Javascript ecosystem. You just specify
{ _id: false }
in the options for the Mongoose schema. Looking for a similar thing for Mongoid in the Ruby on Rails world.
Thanks!

Mongoid internally defines the _id field on each document with a default value, but you can redefine this field to not have a default value.
class Foo
include Mongoid::Document
embeds_one :bar
end
class Bar
include Mongoid::Document
embedded_in :foo
field :_id, type: String
end

Related

how to run code when embedded document is created

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

Mongoid embeds_many: push document without save in order to preserve dirty state

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.

Mongoid embeds_many build nested object twice?

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)

Mongoid save fails silently without embedded_in relation

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 )

Why can't I push duplicate records using has_many in Mongoid?

Why can't I push a record multiple times when using has_many?
class Template
include Mongoid::Document
has_and_belongs_to_many :widgets, inverse_of: nil
Attempt to add duplicates:
(rdb:387) self.widgets.push(Widget.first)
[BSON::ObjectId('4f7096776c51c8135000000d')]
(rdb:387) self.widgets.push(Widget.first)
[BSON::ObjectId('4f7096776c51c8135000000d')]
(rdb:387) self.widgets.count
1
Internaly, Mongoid use the key $addToSet to put your new document associate. So this keyword avoid duplication of element in the list. So you can't have several times the same document associate with has_and_belongs_to_many.
Do a pull request or a feature request if you want this behavior. But I think it can be an option to allow this.

Resources