ActsAsDiffable provides a dead-simple way to compare two instances of a class, including any or all associations, or more complex relationships.

The return is a hash*, suitable for digestion by case-based textualizers, JSON processors, etc., in the form { ‘attribute’ => [from, to] }

  • In the instance of no changes, a nil is returned

Usage

class Foo < ActiveRecord::Base
  acts_as_diffable
end
> Foo.first.diff(Foo.last) => { 'bar' => ['foo', nil] }

Associations

For plural associations, a :diff_key option needs to be added to the association, defining how to relate disparate instances within each parent’s collections.

This can be a single field or a collection of fields in an array, and will be expressed as the key side of a hash with the value being the hash of attribute differences.

For singular association, there is no need to specify a way to organize and compare, so we only need to express which associations to include in the diff, by adding a :diff option to the association that evaluates to true.

class Foo < ActiveRecord::Base
  acts_as_diffable

  has_one :bar, :diff => true
  has_many :fish, :diff_keypattern => :name
  has_and_belongs_to_many :users, :diff_keypattern => [:firstname, :lastname]
end
> Foo.first.diff(Foo.last) 
=> { 'bar'   => { 'attr1' => ['a', 'b'], 
                  'attr2' => [14, nil] },
     'fish   => { 'nemo'   => {'fish_attr1' => [nil, 'zip']   },
                  'goldie' => {'fish_attr1' => ['zap', 'zop'] } },
     'users' => { ['Jane', 'Doe'] => { 'firstname' => 'Jane',
                                       'lastname'  => 'Doe' },
                  ['John', 'Doe'] => { '_deleted' => true   } } }

More complex relationships

In addition to the marked associations, any method on the class that returns an ActiveRecord-ish object can be included in the diff by adding a manual_diff_definiton. For comparing collections of ActiveRecord objects, use the form:

manual_diff_definition :name, :eval => 'instance_eval_code',
                              :diff_key => [:key, :pattern]

For simpler singular comparisons, omit the diff_key option.

Have a lot of fun!