mongoid-history
mongoid-history copied to clipboard
Changes to embedded documents not tracked in parent history
I'm trying to track all changes to a model object and its embedded documents, as described in the "Include embedded objects attributes in parent audit" section of the README.
My models roughly looks like this:
class ParentModel
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::History::Trackable
include Mongoid::Userstamp
mongoid_userstamp user_model: 'User'
field :foo
embeds_one :child
accepts_nested_attributes_for :child
track_history on: [:fields, :updated_by_id, :embedded_relations],
modifier_field: :modifier,
modifier_field_inverse_of: :nil,
version_field: :version,
track_create: true,
track_update: true,
track_destroy: false
end
class ChildModel
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Userstamp
mongoid_userstamp user_model: 'User'
field :bar
embedded_in :parent, inverse_of: :child
end
Changes to fields of the parent are correctly tracked in the parent's history, but not changes to fields of the child.
> p = Parent.all().first
> p.name = "test"
> p.save
> p.history_tracks.reverse.first
### shows the change to the name in the parent document
> p.child.bar = "test"
> p.save
> p.history_tracks.reverse.first
### no additional audit trail entry recorded, this shows the change to the name of the parent from above
Am I misunderstanding how the tracking of embedded relations is supposed to work? Does it only track a change to the reference itself, for example if I change p.child
to a completely different child object?
Is there a way for me to accomplish what I'm looking for here, that is to have a single audit trail of parent document that shows all changes to all its fields, and all the fields of its embedded relations?
I was expecting #150 to provide this functionality.
The changes with embedded_relations
should have worked.
First I would change save
to save!
and see whether there're errors. Then I would look at what's actually being written in MongoDB with logger at DEBUG
level and make sure it's doing what we think it should be doing.
Finally, try to build a short repro in a spec. One thing that comes to mind - is ChildModel
declared after or before?
I tried with save!
as well, no errors. The child object is correctly saved in Mongo, and I can even reload the Parent object (with relations) and get the updated values as expected. It's just the audit trail that's incomplete.
In my application, ParentModel
and ChildModel
are declared into two distinct files (as you might expect in a Rails app). I don't know in what order those are loaded. From trying to write a spec for it, it does appear that I need to declare the ChildModel before. I'm sending a PR with the spec.
@mpetazzoni i tried to replicate the behavior by setting up a minimal example. mongoid-history
was not able to catch the changes in embedded documents. It basically works on the parent_object.changes
hash and if an embedded object attribute is changed, the changes
hash remains empty (that is the default behavior of mongoid
). So, no history record is not created in that case (which is a bug in mongoid-history
).
There is a gem mongoid_relations_dirty_tracking to see the embedded documents changes in parent object's changes
hash. I tried to use that in my example, but it turns out the gem does not support rails 5 and mongoid 6. If you are using a lower version of rails and mongoid, you can give it a try.
Another solution on SO.
It might take some more time to try these solutions on my end. I will let you know if i find a fix. Please let us know, if any of the above solutions work for you.
I have patched for support of Rails5/Mongoid6 mongoid_relations_dirty_tracking here. Problem is that test is still failing even when examples from mongoid_relations_dirty_tracking are fine.
Any news about it? How you solved it?
https://github.com/mongoid/mongoid-history/pull/191 seems like it's almost there, but not ready to merge
How it should work actually? For example in case embeds_many
, that parent is Post
and children are comments
. And one comment was changed, should be in original
and modified
all comments? Or changed comment only?
Edit: After investigation, I think, that there should be all embedded objects, not changed only.
My temporary solution is:
# app/models/concerns/mongoid/embedded_dirty_tracking.rb: module Mongoid module EmbeddedDirtyTracking extend ActiveSupport::Concern included do after_initialize :store_embedded_shadow after_save :store_embedded_shadow end def store_embedded_shadow @embedded_shadow = {} self.class.tracked_embedded.each do |rel_name| @embedded_shadow[rel_name] = tracked_embedded_attributes(rel_name).dup end end def embedded_changes changes = {} @embedded_shadow.each do |rel_name, shadow_values| current_values = tracked_embedded_attributes(rel_name) if current_values != shadow_values changes[rel_name] = [shadow_values, current_values] end end changes end def embedded_changed? !embedded_changes.empty? end def changed_with_embedded? changed? or embedded_changed? end def changes_with_embedded (changes || {}).merge(embedded_changes) end def tracked_embedded_attributes(rel_name) values = nil case relations[rel_name].macro when :embeds_one values = send(rel_name)&.attributes&.clone || {} when :embeds_many values = Array.new values += send(rel_name).map { |child| child.attributes.clone } end values end module ClassMethods def tracked_embedded relations.select {|_, rel| %i[embeds_one embeds_many].include?(rel.macro) }.keys end end end end
# app/models/concerns/trackable.rb module Trackable extend ActiveSupport::Concern included do include Mongoid::EmbeddedDirtyTracking include Mongoid::History::Trackable track_history on: [:fields, :embedded_relations], except: [:_keywords], changes_method: :changes_with_embedded end end
# app/models/city.rb class City include Mongoid::Document include Trackable field :shortcut, type: String field :name, type: String embeds_many :excursions, cascade_callbacks: true accepts_nested_attributes_for :excursions, allow_destroy: true end
# app/models/excursion.rb class Excursion include Mongoid::Document field :name, type: String embedded_in :city end
Inspired from https://github.com/Polarion/mongoid_relations_dirty_tracking
Partial fix in https://github.com/mongoid/mongoid-history/pull/232, please try HEAD!
No joy with HEAD. This seems an issue for mongoid - Dirty should track changes when embedded documents are changed via the <relation>_attributes=
path. I have not been able to find anything about this in the Mongoid JIRA issues history yet.
Failing example:
parent.posts_attributes= {"0": {subject: "test"}}
parent.save
parent.changes
=> {}
the result from parent.changes
should include the embedded document changes. Created this issue in Mongoid's JIRA: https://jira.mongodb.org/browse/MONGOID-4896