From: Josh Cheek Date: 2013-06-18T07:11:34+09:00 Subject: Re: Comparing objects --089e01493e2e0748d304df60e185 Content-Type: text/plain; charset=ISO-8859-1 On Mon, Jun 17, 2013 at 4:25 PM, Graham Menhennitt wrote: > On 18/06/2013 2:06 AM, Josh Cheek wrote: > > On Mon, Jun 17, 2013 at 10:52 AM, Thom T. wrote: > >> How do I compare two objects in Ruby, considering only attributes >> values? >> >> For example: >> >> class Foo >> attr_accessor :bar, :baz >> end >> >> lorem = Foo.new >> ipsum = Foo.new >> >> lorem.bar = 1 >> lorem.baz = 2 >> >> ipsum.bar = 1 >> ipsum.baz = 2 >> >> puts lorem == ipsum # false >> >> Both objects belongs to the same class and also have the same attributes >> values. >> >> Thanks. >> >> > I'd do it like this > > class Foo > attr_accessor :bar, :baz > def ==(foo) > foo.kind_of?(self.class) && bar == foo.bar && baz == foo.baz > end > end > > Only define <=> if your foos are ordered. > > I suspect that the OP is looking for something a bit more generalised. > > def ==(rhs) > return false unless rhs.is_a?(self.class) > instance_variables.each do |var| > return false unless instance_variable_get(var) == > rhs.instance_variable_get(var) > end > return true > end > > Note that the first line of this can be changed according to taste: > - as above, two objects will compare as equal if the class of the right > hand side is the same as or a sub-class of the left hand side > - remove it completely in which case you get "duck typing" equality (no > relationship needed between classes of object) > - change it to "return false unless rhs.class == lhs.class" in which case > the two objects must be of the same class > In the first case, a == b will not necessarily return the same as b == a. > > Also, note that this code allows the right hand side to have extra > instance variables that the left hand side does not, but they can still > compare as equal. That may or may not be desirable. If not, you need to > test for it as well. Again, the way I've written it, a == b is not the same > as b == a. > > Perhaps, but I think this is not a good approach. It's too magical: * You give up control of what constitutes equality (these kind of implicit assumptions seem to always break down) * You store something in a var and all of a sudden your objects aren't showing up equal. It really sucks when making some change that doesn't matter suddenly causes everything to break for no obvious reason. * You can't look at the behaviour and figure out what it's doing, b/c it's violating encapsulation. It's suddenly state based instead of value based: * Say you have a method that lazily initializes some var: `def users() @users ||= [] end` and someone called `obj.users.any?` on the one, but not the other. Now your objects aren't equal. * It doesn't matter if bank1 has @pennies=100@dollars=0 and bank2 has @pennies=0@dollars=1, these have the same value, but it doesn't matter, b/c the state representation is different, * You can't pass a mock in, b/c it's no longer about interfaces, it's now about the state of these variables. -Josh --089e01493e2e0748d304df60e185 Content-Type: text/html; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable On Mon, Jun 17, 2013 at 4:25 PM, Graham Menhennitt <graham@menhenni= tt.com.au> wrote:
=20 =20 =20
On 18/06/2013 2:06 AM, Josh Cheek wrote:
On Mon, Jun 17, 2013 at 10:52 AM, Thom T. <lists@ruby-forum.com> wrote:
How do I compare two objects in Ruby, considering only attributes
values?

For example:

=A0 class Foo
=A0 =A0 =A0 attr_accessor :bar, :baz
=A0 end

=A0 lorem =3D Foo.new
=A0 ipsum =3D Foo.new

=A0 lorem.bar =3D 1
=A0 lorem.baz =3D 2

=A0 ipsum.bar =3D 1
=A0 ipsum.baz =3D 2

=A0 puts lorem =3D=3D ipsum # false

Both objects belongs to the same class and also have the same attributes
values.

Thanks.


I'd do it like this

class Foo
=A0 attr_accessor :bar, :baz
=A0 def =3D=3D(foo)
=A0 =A0 foo.kind_of?(self.class) && bar =3D=3D foo.bar && baz =3D=3D foo.baz
=A0 end
end

Only define <=3D> if your foos are ordered.
I suspect that the OP is looking for something a bit more generalised.

=A0=A0=A0=A0=A0=A0=A0 def =3D=3D(rhs)
=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0 return false unless rhs.is_a?(self.cl= ass)
=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0 instance_variables.each do |var|
=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0 return false unless insta= nce_variable_get(var) =3D=3D rhs.instance_variable_get(var)
=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0 end
=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0=A0 return true
=A0=A0=A0=A0=A0=A0=A0 end

Note that the first line of this can be changed according to taste:
- as above, two objects will compare as equal if the class of the right hand side is the same as or a sub-class of the left hand side
- remove it completely in which case you get "duck typing" eq= uality (no relationship needed between classes of object)
- change it to "return false unless rhs.class =3D=3D lhs.class&quo= t; in which case the two objects must be of the same class
In the first case, a =3D=3D b will not necessarily return the same as b =3D=3D a.

Also, note that this code allows the right hand side to have extra instance variables that the left hand side does not, but they can still compare as equal. That may or may not be desirable. If not, you need to test for it as well. Again, the way I've written it, a =3D=3D b is not the same as b =3D=3D a.


Perhaps, but I think this is= not a good approach.

It's too magical:
<= div>* You give up control of what constitutes equality (these kind of impli= cit assumptions seem to always break down)
* You store something in a var and all of a sudden your objects aren&#= 39;t showing up equal. It really sucks when making some change that doesn&#= 39;t matter suddenly causes everything to break for no obvious reason.
* You can't look at the behaviour and figure out what it's doi= ng, b/c it's violating encapsulation.

It's= suddenly state based instead of value based:
* Say you have a me= thod that lazily initializes some var: `def users() @users ||=3D [] end` an= d someone called `obj.users.any?` on the one, but not the other. Now your o= bjects aren't equal.
* It doesn't matter if bank1 has @pennies=3D100@dollars=3D0 and ba= nk2 has @pennies=3D0@dollars=3D1, these have the same value, but it doesn&#= 39;t matter, b/c the state representation is different,=A0
* You = can't pass a mock in, b/c it's no longer about interfaces, it's= now about the state of these variables.

-Josh
--089e01493e2e0748d304df60e185--