35
votes

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.

7
What are you trying to do this for? This could end up breaking other ActiveRecord functionality, and there's probably a better way to do what you're tryingGareth

7 Answers

57
votes

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
      super value
    end     
  end
19
votes

If you want to access the model itself in Rails 3.2 you should use proxy_association.owner

Example:

class Author < ActiveRecord::Base
  has_many :books do
    def << (book)
      proxy_association.owner.add_book(book)
    end
  end

  def add_book (book)
    # do your thing here.
  end
end

See documentation

0
votes

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
0
votes

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  
0
votes

The method I use is to extend the association. You can see the way I handle 'quantity' attributes here: https://gist.github.com/1399762

It basically allows you to just do

has_many : tags, :through => : taggings, extend => QuantityAssociation

Without knowing exactly what your hoping to achieve by overriding the methods its difficult to know if you could do the same.

0
votes

This may not be helpful in your case but could be useful for others looking into this.

Association Callbacks: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

Example from the docs:

class Project
  has_and_belongs_to_many :developers, :after_add => :evaluate_velocity

  def evaluate_velocity(developer)
    ...
  end
end

Also see Association Extensions:

class Account < ActiveRecord::Base
  has_many :people do
    def find_or_create_by_name(name)
      first_name, last_name = name.split(" ", 2)
      find_or_create_by_first_name_and_last_name(first_name, last_name)
    end
  end
end

person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson")
person.first_name # => "David"
person.last_name  # => "Heinemeier Hansson"
0
votes

Rails guides documents about overriding the added methods directly.

OP's issue with overriding << probably is the only exception to this, for which follow the top answer. But it wouldn't work for has_one's = assignment method or getter methods.