Gotchas with Mongoid `default` and Nested, Embedded Collection Saving

Specifying Mongoid's default option on another field causes weirdness with the changes method

We had something like this:

class Offer
    include Mongoid::Document

    field :offer_id
    field :_id, default: -> :offer_id
end

To explain, basically we had some data coming in via an XML file that we were going to map to our Mongo model. We wanted to make use of the incoming XML IDs, and figured we could just swap out the Mongo generated ones with the ones in our XML file using the default: attribute.

It was all fine until I started noticing some strange behavior: While debugging our sync routine, I was checking Mongoid's offer.changes, which prints out a hash of attributes that have had their values changed since the object was last loaded from the DB. Along the way, it seemed that the output of this method would say the offer._id attribute (the value of which is equal to offer.offer_id, as specified by the default: option above) changed every time, regardless of whether it actually had or not.

There might have been a perfectly logical explanation for this, but one we could not find after a long search. Scared by this, and thinking we should probably not be messing with _id in this way, we decided to just maintain two different sets of IDs: our XML IDs and Mongoid's own generated IDs.

Nested, embedded collection saving: doesn't seem to be a thing?

Take these snippets:

class Offer
    include Mongoid::Document

    embeds_one :itinerary
    embeds_many :prices
end

class Itinerary
    include Mongoid:Document

    embeds_many :itinerary_days
end

Making a long story short here, we had an import that would add itinerary_days to offer.itinerary.itinerary_days and then save the offer by doing offer.save. You'd expect any attributes we set on the offer to be saved (none shown here) as well as any itinerary_days we added to offer.itinerary.itinerary_days, right?

Not a chance! prices saved as expected, but having our itinerary_days collection not embedded directly on the offer, but on a child of it (itinerary) makes it a different story. We chalked up our inability to get this to work, or find anything on it, to the nature of how embedded documents work. In our case, the solution was to explicitly save both the itinerary and the offer as such:

offer.save
offer.itinerary.save

Are we just noobs?