2

Rails' ActiveRecord::Base class defines an == method that returns true if the objects are identical or they have the same ID.

I've overridden == in a couple of my Rails models to allow for more meaningful equality conditions. These work when I compare objects directly (e.g., through script/console), but if I do something like my_array_of_models.include? other_model, include? always returns false. even if the array contains an object that is "equal" (according to my definition).

I've fixed this by doing, e.g., my_array_of_models.any? { |el| el.attr == other_model.attr } (which I think is the way you're encouraged to do comparisons, anyway), but I'm wondering: is it meaningful to override == in ActiveRecord models, or does ActiveRecord do something at a high level that renders such an overridden method useless (or worse, dangerous)?

Source

Here're my implementations of my overridden methods. There are two classes, User and Contact. Users have unique email addresses, so == returns true if the email addresses are the same. Contact is a bridge between Users (like a "friend" relationship in social networking), and should return true if they have the same user_id.

class User < ActiveRecord::Base
  def ==(other)
    other.respond_to?(:email) and self.email == other.email
  end
end

class Contact < ActiveRecord::Base
  def ==(other)
    if other.class == self.class
      self.user == other.user
    elsif other.kind_of? User
      self.user == other
    else
      false
    end
  end
end

As I noted, it works when comparing directly (e.g., one_object == another_object), but my_array_of_objs.include? included_obj always returns false.

mipadi
  • 398,885
  • 90
  • 523
  • 479
  • When you mention comparing objects directly I assume you mean as in model1 == model2. Does my_array_of_models.include? other_model work as expected when you try that through script/console? – mikej Jun 24 '09 at 16:29
  • No, Array.include? always returned false in my tests, no matter what -- even through script/console. – mipadi Jun 24 '09 at 16:36
  • Hmm.. there must be something else going on here as a quick test through script/console where I override == on an ActiveRecord subclass to always return true leads to Array.include? always returning true as long as the array contains 1 model of the type with the overridden ==. Can you post your specific == implementation? – mikej Jun 24 '09 at 16:43
  • The results of .include? may vary, depending on whether its `a == b` or `b == a` if only one of these objects have the method overridden. I suggest you do a `my_arrays_of_objs.map(&:email).include? obj.email` kind of things. Overriding a method in a way so different from expected behavior will be very bad for readability of your code. – Arsen7 Nov 16 '10 at 10:09

3 Answers3

0

Perhaps its instructive to read this piece of documentation:

http://ruby-doc.org/core/classes/Object.html#M000341

array_of_models.include?(my_object) perhaps doesn't work because == is not used for testing whether collection has the object.It uses equal?.

EDIT

The OP's assertion that even when he overrides == method for his models, array.include?(obj) returns false is not true. Lets see this:

class Event < ActiveRecord::Base
  def ==(that)
    if(that.eventname == self.eventname)
      true
    else
      false
   end
 end
end
>> a << Event.find(1)
=> [#<Event id: 1, eventname: "hemant", foo: nil, created_at: "2009-06-24 21:33:00", updated_at: "2009-06-24 21:33:00">]

>> b = Event.find(2)
=> #<Event id: 2, eventname: "hemant", foo: nil, created_at: "2009-06-24 21:33:04", updated_at: "2009-06-24 21:33:04">

>> a.include?(b)
=> true

Clearly thats not true. But anyways, I think since AR defines == in a particular way, its not a good idea to override it. It may lead to hard to detect bugs.

Hemant Kumar
  • 1,988
  • 2
  • 13
  • 21
  • 1
    The documentation for Array.include? says that it uses "==": http://www.ruby-doc.org/core/classes/Array.html#M002226 – mipadi Jun 24 '09 at 15:36
  • Indeed, it does. I am wondering why then your array of model objects return false (that led to somewhat wrong conclusion on my part). – Hemant Kumar Jun 24 '09 at 15:57
  • Array.include? _does_ return false, even when overriding == on the appropriate classes. It's not like I'm lying in my post. ;) – mipadi Jun 24 '09 at 16:11
  • Not lying friend. But just some other magic is going on, perhaps one of your plugins or something. – Hemant Kumar Jun 24 '09 at 16:13
  • I'm not using any Rails plugins. – mipadi Jun 24 '09 at 16:36
  • Its quite possible that the logic that you have put in implementation of `==` method could be cause of the problem. Additionally I wouldn't advise you to override `==` in such a fashion, it *will* cause hard to debug errors. – Hemant Kumar Jun 24 '09 at 17:01
0

Another way to state your question might be "is the == method considered 'final'?". In Java, you can use the 'final' keyword to prevent a method from being overridden in subclasses (you can also apply final to classes, so they can't be subclassed at all). This can be a handy thing in OO design, because some classes or methods are not designed to be overridden. Ruby, of course, does not have 'final', but it's possible that the same concept applies here (maybe you can even implement it using meta-programming).

I remember reading some good articles and/or SO posts on the use of 'final' in OO. I'll edit those in if I find them.

allyourcode
  • 21,871
  • 18
  • 78
  • 106
0

ActiveRecord model is an entity object. By design it represents a row in a database and id column is the primary key uniquely identifying it on a database level.

As Hemant Kumar pointed out, you can override it, but by doing so objects with different ids but same attribute(s) are considered equal, which is not true.

Also with this change the design shifts from ActiveRecord entity object to pure value object.

Possible solution for your problem is:

Rails deliberately delegates equality checks to the identity column. If you want to know if two AR objects contain the same stuff, compare the result of calling #attributes on both.

https://stackoverflow.com/a/4738485/2987689

This question also has another good approach (https://stackoverflow.com/a/7098377/2987689)

Artur INTECH
  • 6,024
  • 2
  • 37
  • 34