2

I had to track the dirty objects. And it works fine with the parent doc. But when I change the embedded or referenced in doc, the dirty has to be accessed via the embedded/referenced in doc itself.

How can I track the dirty on the parent doc itself??

millisami
  • 9,931
  • 15
  • 70
  • 112

2 Answers2

2

I have put together mongoid extension to solve this problem https://github.com/versative/mongoid_relations_dirty_tracking. It simply tracks changes on document relations in the similar way as mongoid do it for attributes so the timestamps and versions are updated correctly.

david.sevcik
  • 336
  • 2
  • 4
1

I don't think Mongoid's own dirty attributes feature tracks changes to embedded documents. But it's not hard to implement basic dirty tracking of embedded documents yourself with something like this:

class Bar
  include Mongoid::Document
  embeds_many :foos
  attr_accessor :foos_changed
  alias_method :foos_changed?, :foos_changed
  # ...
end

class Foo
  include Mongoid::Document
  embedded_in :bar, :inverse_of => :foos
  after_save :notify_parent
  def notify_parent
    bar.foos_changed = true
  end
  # ...
end

b = Bar.create
b.foos_changed? # => nil
b.foos.create
b.foos_changed? # => true

If you need more options, like including embedded documents changes in Bar.changes or tracking the specific changes, you'll have to override the changes and changed? methods, which I wouldn't do unless you absolutely need those features.

Here's a way to track what changed in the embedded documents:

class Bar
  include Mongoid::Document
  embeds_many :foos
  attr_writer :embedded_changes
  def embedded_changes
    @embedded_changes ||= begin
      self._children.inject({}) do |memo, child|
        memo.merge( 
         {child.collection_name => {child.id => child.changes}} 
        ) if child.changed?
      end
    end
  end

  def changes
    original_value = super
    if original_value.blank?
      embedded_changes
    else
      original_value.merge(embedded_changes)
    end
  end

  def changed?
    super || self._children.any?(&:changed?)
  end
  # ...
end

class Foo
  include Mongoid::Document
  embedded_in :bar, :inverse_of => :foos
  # ...

  field :hat
end

b = Bar.create
f = b.foos.create
b.changes   # => nil 
b.changed?  # => false 
f.hat = 1
f.changes   # => {"hat"=>[nil, "1"]} 
b.changes   # => {"foos"=>{BSON::ObjectId('4cf...')=>{"hat"=>[nil, "1"]}}}    
b.changed?  # => true
bowsersenior
  • 12,524
  • 2
  • 46
  • 52
  • Thanks for the answer. Its almost I wanted with some extra things. – millisami Nov 27 '10 at 09:03
  • What I'd want is whenever foo changes, I want to know via the bar.changed? and bar.changes to give those values. I think as you said, my need asks to override the changes and changed? methods. But how to do that? – millisami Nov 27 '10 at 09:38
  • See the updated answer above. Be careful though when you override `changes` and `changed?` . With complex documents, strange things could happen. There is a reason why Mongoid only tracks dirty attributes and no dirty associations. – bowsersenior Nov 27 '10 at 18:56
  • Thanx. I'll get back after I try it out. – millisami Nov 29 '10 at 03:45
  • Great, it works. But I've got some other documents too with the references_many and references_one relationship. How can I track those referenced documents too just like the above one via the parent document? Will the above code just works or have to do some tweaks. – millisami Nov 29 '10 at 10:42
  • You will have to tweak and test the code if you want it to work with referenced objects. Instead of `_parent` and `_children`, you would have to use the `associations` method to find the referenced records. – bowsersenior Nov 29 '10 at 16:43
  • I tried with the associations method, but couldn't figure out exactly. Since I'm not so meta-programmer in ruby, couldn't figure out. Here is the gist with the rspec setup. Just got to pass the last spec only. Can you look at it https://gist.github.com/721466 – millisami Nov 30 '10 at 10:18
  • You have a basic problem. You can't just switch `_children` for `associations` and do nothing else. `_children` returns an array of documents but `associations` returns a hash with the association names as key and a `Mongoid::Associations::MetaData` object as the value. The first step is to update the `referenced_changes` method. I forked and updated your gist: https://gist.github.com/721971 . You will likely have to make other adjustments as well. I noticed your `changes` method and `changed?` method are not right either. – bowsersenior Nov 30 '10 at 17:00
  • See comments in my fork of your gist on how to fix `changes` and `changed?` methods: https://gist.github.com/721971 – bowsersenior Nov 30 '10 at 17:07
  • I couldn't still fix it. Can you plz check the comment https://gist.github.com/721971#comments – millisami Dec 01 '10 at 06:50
  • I looked further into tracking changes to referenced docs and I don't think my approach will work. Embedded docs are fine though. The problem is that references work very differently than embedded docs and there are a lot of difficulties in tracking their changes. Sorry, but I can only recommend that you go with the embedded_changes only or find another way of solving the problem. FYI, there is a dirty_associations plugin for ActiveRecord: https://github.com/daphonz/dirty_associations – bowsersenior Dec 01 '10 at 07:17