Comparing instance variables in Ruby

Posted by Jonas Elfström Mon, 02 Nov 2009 21:50:00 GMT

Say you have two objects of the same class and you want to know what differs between them. Well actually you just want to know the instance variables in object b that differs from the ones in object a.

To begin with, we need a class. I like cheese.

1
2
3
4
5
6
class Cheese
  attr_accessor :name, :weight, :expire_date
  def initialize(name, weight, expire_date)
    @name, @weight, @expire_date = name, weight, expire_date
  end
end


Then we need some cheese objects.

1
2
stilton=Cheese.new('Stilton', 250, Date.parse("2009-11-02"))
gorgonzola=Cheese.new('Gorgonzola', 250, Date.parse("2009-11-17"))


With only name, weight and an expiration date it would be easy to compare those but imagine that these two objects has 42 properties. It does not stop there, you are being asked to compare 24 different classes in this way. Are you cringing yet?

Object#instance_variables to the rescue! Well, that and a small hack by me. Below I add a new method called instance_variables_compare to Object. The long method name is because I wanted to follow the naming already in place. Usually I prefer to do these kind of things as a module and then include them where appropriate but in this case I find that a monkey patch will do.

1
2
3
4
5
6
7
class Object
  def instance_variables_compare(o)
    Hash[*self.instance_variables.map {|v|
      self.instance_variable_get(v)!=o.instance_variable_get(v) ? 
      [v,o.instance_variable_get(v)] : []}.flatten]
  end
end


It returns the instance variables that differs as a hash because it's handy and because I like it that way.

1
2
3
4
5
6
7
8
9
10
>> stilton.instance_variables_compare(gorgonzola)
=> {"@name"=>"Gorgonzola", "@expire_date"=>#<Date: 4910305/2,0,2299161>}
>> gorgonzola.instance_variables_compare(stilton)
=> {"@name"=>"Stilton", "@expire_date"=>#<Date: 4910275/2,0,2299161>}
>> stilton.expire_date=gorgonzola.expire_date
=> #<Date: 4910305/2,0,2299161>
>> stilton.instance_variables_compare(gorgonzola)
=> {"@name"=>"Gorgonzola"}
>> stilton.instance_variables_compare(stilton)
=> {}


If you ever think of using this code you should be aware of two things.

  1. This code is very untested and comes with no guarantees.
  2. Since instance variables spring into life the first time they are assigned to you either have to work with objects that always initialize everything or you have to change instance_variables_compare to handle this.

Posted in Ruby | no comments

Comments

Comments are closed