37

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.

seaneshbaugh
  • 1,448
  • 4
  • 18
  • 26
  • 2
    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 trying – Gareth May 23 '10 at 07:19

7 Answers7

59

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
Community
  • 1
  • 1
Tatjana N.
  • 6,215
  • 32
  • 29
19

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

Paul Oliver
  • 7,531
  • 5
  • 31
  • 34
1

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.

BigFive
  • 236
  • 1
  • 5
0

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"
Rimian
  • 36,864
  • 16
  • 117
  • 117
0

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
  • 19,417
  • 5
  • 40
  • 40
  • 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 May 23 '10 at 07:38
  • 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 May 23 '10 at 07:50
  • 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 May 23 '10 at 08:07
0

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
  • 19,907
  • 21
  • 66
  • 79
0

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.

Community
  • 1
  • 1
lulalala
  • 17,572
  • 15
  • 110
  • 169