Skip to content

Instantly share code, notes, and snippets.

@bowsersenior
Forked from millisami/gist:721466
Created November 30, 2010 16:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bowsersenior/721971 to your computer and use it in GitHub Desktop.
Save bowsersenior/721971 to your computer and use it in GitHub Desktop.
%w(rubygems mongoid rspec).each do |gem|
require gem
end
Mongoid.configure do |config|
name = "dirty_track_test"
host = "localhost"
config.master = Mongo::Connection.new.db(name)
config.persist_in_safe_mode = false
end
RSpec.configure do |config|
config.before(:each) do
Mongoid.master.collections.reject { |c| c.name == 'system.indexes'}.each(&:drop)
end
end
class Person
include Mongoid::Document
field :name
embeds_one :address
embeds_many :phones
references_many :projects
# dirty tracking of embedded documents
attr_writer :embedded_changes, :referenced_changes
def referenced_changes
puts "SELF: #{(self.methods - Object.methods).sort.to_yaml}"
puts "CHILDREN: #{((self.associations)).inspect}"
@referenced_changes ||= begin
self.associations.keys.each do |assoc_name|
self.send(assoc_name).inject({}) do |memo, obj|
memo.merge(obj.each{|c| c.send(:changes) if c.send(:changed?)})
end
end
end
end
def embedded_changes
@embedded_changes ||= begin
self._children.inject({}) do |memo, child|
memo.merge(child.changes) if child.changed?
end
end
end
def changes
original_value = super
if original_value.blank?
embedded_changes # need to merge these two hashes
referenced_changes # but make sure to detect nil values
else
original_value.merge(embedded_changes).merge(referenced_changes)
end
end
def changed?
super || self._children.any?(&:changed?) # what about changes to referenced docs?
end
end
class Address
include Mongoid::Document
field :city
embedded_in :person, :inverse_of => :address
end
class Phone
include Mongoid::Document
field :number, :type => Integer
embedded_in :person, :inverse_of => :phones
end
class Project
include Mongoid::Document
field :title
referenced_in :person, :inverse_of => :projects
end
describe Person do
context "parent document" do
it "should detect the changes" do
@person = Person.new(:name => "Sachin")
@person.save!
@person.name = "Sagar"
@person.changed.should be_true
end
end
context "parent document with embedded one document" do
it "should detect the changes of the embedded one address document when the address city is changed" do
@person = Person.new(:name => "Sachin")
@person.create_address(:city => "Kathmandu")
@person.changed?.should be_false
@person.address.city = "Lalitpur"
@person.changed?.should be_true
@person.changes["city"].should include("Lalitpur")
end
end
context "parent document with embedded many document" do
it "should detect the changes of the embedded many phones document when the phone number is changed" do
@person = Person.new(:name => "Sachin")
@person.phones.create!(:number => 980)
@person.phones.first.number.should == 980
@person.changed?.should be_false
@person.phones.first.number = 400
@person.changed?.should be_true
@person.changes["number"].should include(400)
end
end
context "parent document with references many documents" do
it "should detect the changes of the referenced many project document when the project title is changed" do
@person = Person.create!(:title => "Sachin")
@project = Project.new(:name => "Gliding")
@person.projects << @project
@person.changes
@person.changed?.should be_true
# @person.changes["title"].should include("Gliding")
end
end
end
@millisami
Copy link

This is the diff patch you made.
diff --git a/dirty-track.rb b/dirty-track.rb
index f461b97..598c3b7 100644
--- a/dirty-track.rb
+++ b/dirty-track.rb
@@ -30,8 +30,10 @@ class Person
puts "CHILDREN: #{((self.associations)).inspect}"

     @referenced_changes ||= begin
-      self.associations.inject({}) do |memo, child|
-        memo.merge(child.each{|c| c.send(:changes) if c.send(:changed?)})
+      self.associations.keys.each do |assoc_name|
+        self.send(assoc_name).inject({}) do |memo, obj|
+          memo.merge(obj.each{|c| c.send(:changes) if c.send(:changed?)})
+        end
       end
     end
   end
@@ -47,15 +49,15 @@ class Person
   def changes
     original_value = super
     if original_value.blank?
-      embedded_changes
-      referenced_changes
+      embedded_changes   # need to merge these two hashes
+      referenced_changes # but make sure to detect nil values
     else
       original_value.merge(embedded_changes).merge(referenced_changes)
     end
   end

   def changed?
-    super || self._children.any?(&:changed?)
+    super || self._children.any?(&:changed?) # what about changes to referenced docs?
   end

 end

But still it doesn't work. Yup, I know that you don't have time. But can you plz look at it and just run ruby dirty-track.rb to make the spec green!!

@bowsersenior
Copy link
Author

I looked further into this and I don't think it will work. 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

@millisami
Copy link

@bowsersenior, that dirty_associations plugin is for ActiveRecord and it blows out when I install it.

@bowsersenior
Copy link
Author

Yes, I know dirty_associations is for activerecord. If you look at the code for dirty_associations, you can get some ideas on how to do something similar for mongoid. But I think you'll see that it won't be easy! Also, dirty_associations doesn't record changes to an associated record, only the addition or removal of an associated record.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment