Overriding the belongs_to macro method


#1

I want to extend the functionality of ActiveRecord’s `belongs_to’ macro
method so that, in addition to performing its normal tasks and creating
the default association methods, I can have it also create a couple
extra custom association methods. I’ll post my code of how I tried
(and failed) to do this below. Anyone know if/how this can be done?

Specifically, what I’m doing is trying to have `belongs_to’ add two more
methods to an AR class if the association is polymorphic
(options[:polymorphic] == true). If the association is polymorphic, I
try to add the following two custom association methods:

CODE
def association_string
“#{association.id}:#{association.class.base_class}”
end

def association_string=(ref)
i, t = ref.split(’:’)
association = eval(t).find(i.to_i)
end
CODE

I try to override belongs_to by using alias_method_chain' so I can call the original implementation, followed by my own logic. The code is in a file in myconfig/initializers’ directory so it will get picked up when
the rails env loads.

The error I get is: alias_method':NameError: undefined methodbelongs_to’ for class `ActiveRecord::Base’

Indeed, if I load a script/console session and do:

ActiveRecord::Base.methods.find {|m| m =~ /belongs_to/}

I get no match for belongs_to' (thoughhas_and_belongs_to_many’ is
found interestingly enough).

I see that in the docs `belongs_to’ is in module
ActiveRecord::Associations::ClassMethods which, I’d assume, gets
included (or extended) into AR::Base, making the method available.
Either way, the method I need to override isn’t defined/part-of
AR::Base. Anyone know how/why/what/where or how to better accomplish the
above?

Here’s the full code listing of what I’m trying to do:

FILE: config/initializers/polymorphic_references.rb

==========CODE==========

module PolymorphicReferences
##########

Extensions to ActiveRecord

module ActiveRecord
def self.included(base)
base.send :extend, ClassMethods
base.send :include, InstanceMethods
base.send :alias_method_chain, :belongs_to, :polymorphism
end
##########
#
# ActiveRecord::Base class methods
#
module ClassMethods
#
# Load object polymorphically
#
def polymorphic(class_or_str, record_id = nil)
i = t = nil
if class_or_str.is_a?(String) and record_id.nil?
i, t = class_or_str.split(’:’) rescue [nil, nil]
raise ArgumentError, “…message…” unless
i.is_a?(String) and t.is_a?(String)
i = i.to_i rescue nil
t = eval(t) rescue nil
elsif class_or_str.is_a?(String) and
record_id.is_a?(Integer)
i = record_id
t = eval(class_or_str) rescue nil
elsif class_or_str.is_a?(Class) and
record_id.is_a?(Integer)
i = record_id
t = class_or_str
else
raise ArgumentError, “…message…”
end
raise ArgumentError, “…message…” unless i
raise ArgumentError, “…message…” unless t
raise ArgumentError, “…message…” unless i > 0 and
t.respond_to?(:descends_from_active_record?) and
t != ::ActiveRecord::Base
t.base_class.find(i)
end
#
# Hook into belongs_to to add association_string methods:
#
def belongs_to_with_polymorphism(association_id, options = {})
add_poly = options[:polymorphic]
belongs_to_without_polymorphism(association_id, options)
if add_poly
string_reader = “#{association_id}_string”
string_writer = “#{association_id}_string=”
object_reader = association_id.dup
object_writer = “#{association_id}=”
define_method(string_reader) do
ref = self.send(object_reader)
ref.polymorphic rescue nil
end
define_method(string_writer) do |reference|
obj = self.class.polymorphic(reference) if reference
self.send(object_writer, obj)
end
end
end
end
##########
#
# ActiveRecord::Base instance methods
#
module InstanceMethods
#
# This returns a polymorphic “reference” string.
#
def polymorphic
return nil if new_record?
“#{id}:#{self.class.base_class}”
end
end
end
end

##########

Plug the system into rails

ActiveRecord::Base.send :include, PolymorphicReferences::ActiveRecord

========== END OF CODE ==========

Thanks in advance for any help/responses/insights/condolences.


#2

So, I’m posting a reply to my own original post. I fixed my problem and
thought I’d follow up for posterity.

The pertinent broken code was:

def self.included(base)
base.send :extend, ClassMethods
base.send :include, InstanceMethods
base.send :alias_method_chain, :belongs_to, :polymorphism
end

The fixed code is:

def self.included(base)
base.send :extend, ClassMethods
base.send :include, InstanceMethods
class << base
alias_method_chain :belongs_to, :polymorphism
end
end

Calling alias_method_chain (or just alias_method for that matter) on the
class from “outside” (aka by klass.send :alias_method, …) runs in the
wrong context. It wants to alias regular instance methods of the klass.
Instead, alias_method needs to happen in (still don’t know proper ruby
terms for this stuff, sorry) class scope (???) so that it will look
for class methods to alias.

As an aside, I was quite retarded when, in my previous post, I ran the
following command in an attempt to see if the belongs_to method was in
fact defined:

ActiveRecord::Base.methods.find {|m| m =~ /belongs_to/}

If, of course, instead finds the first method that matches which was
`has_and_belongs_to_many’ in my case. I should’ve been using
find_all instead. Well, yeah, just an FYI.

I originally wrote:

http://www.ruby-forum.com/topic/187419
(there, just saved a ton of space and a whole lotta money on
my ___ _________ by switching to _____)