views:

361

answers:

3

Is there a way to override one of the methods provided by an ActiveRecord association?

Say for example I have the following typical polymorphic has_many :through association:

class Story < ActiveRecord::Base
    has_many :taggings, :as => :taggable
    has_many :tags, :through => :taggings, :order => :name
end


class Tag < ActiveRecord::Base
    has_many :taggings, :dependent => :destroy
    has_many :stories, :through => :taggings, :source => :taggable, :source_type => "Story"
end

As you probably know this adds a whole slew of associated methods to the Story model like tags, tags<<, tags=, tags.empty?, etc.

How do I go about overriding one of these methods? Specifically the tags<< method. It's pretty easy to override a normal class methods but I can't seem to find any information on how to override association methods. Doing something like

def tags<< *new_tags
    #do stuff
end

produces a syntax error when it's called so it's obviously not that simple.

A: 

I think you wanted def tags.<<(*new_tags) for the signature, which should work, or the following which is equivalent and a bit cleaner if you need to override multiple methods.

class << tags
  def <<(*new_tags)
    # rawr!
  end
end
x1a4
I don't think either of those will work. It looks like you what you are suggesting is trying to extend the Eigenclass of the value returned by the `tags` method.
Daniel Beardsley
It's defining a method within the eigenclass of whatever is returned by `tags`, which is probably an array. This has the effect of adding a new instance method to the array, which is what I understood the original question to be asking. `extend` has a specific meaning in ruby and that's not what's going on here.
x1a4
You are right, that is exactly what it's doing. I guess I just didn't understand where you were suggesting to put that code. Anyway, i guess I answered pretty much the same thing, just with a little more context.
Daniel Beardsley
A: 

You would have to define the tags method to return an object which has a << method.

You could do it like this, but I really wouldn't recommend it. You'd be much better off just adding a method to your model that does what you want than trying to replace something ActiveRecord uses.

This essentially runs the default tags method adds a << method to the resulting object and returns that object. This may be a bit resource intensive because it creates a new method every time you run it

def tags_with_append
  collection = tags_without_append
  def collection.<< (*arguments)
    ...
  end
  collection
end
# defines the method 'tags' by aliasing 'tags_with_append'
alias_method_chain :tags, :append  
Daniel Beardsley
+3  A: 

You can use block with has_many to extend your association with methods. See comment "Use a block to extend your associations" here.
Overriding existing methods also works, don't know whether it is a good idea however.

  has_many :tags, :through => :taggings, :order => :name do
    def << (value)
      "overriden" #your code here
    end     
  end
Voyta
Of course! Forgot about that one. This is probably the best way to do what you are after.
Daniel Beardsley