views:

648

answers:

2

I see the following code in the attribute_fu plugin:

module AttributeFu
    module Associations #:nodoc:                                                                                                                                                
        def self.included(base) #:nodoc:                                                                                                                                          
            base.class_eval do
                extend ClassMethods
                class << self; alias_method_chain :has_many, :association_option; end

                class_inheritable_accessor  :managed_association_attributes
                write_inheritable_attribute :managed_association_attributes, []

                after_update :save_managed_associations
            end
        end

        ...
    end
end

When I try to replace

class << self; alias_method_chain :has_many, :association_option; end

with: alias_method_chain :has_many, :association_option?

I get the following error

/usr/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/core_ext/module/aliasing.rb:31:in `alias_method': undefined method `has_many' for class `ActiveRecord::Base' (NameError)
        from /usr/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/core_ext/module/aliasing.rb:31:in `alias_method_chain'
        from /home/twong/git/physpace/vendor/plugins/attribute_fu/lib/attribute_fu/associations.rb:9:in `included'

I thought those two lines would do the same thing, but it looks like I'm wrong. Can somebody explain my error?

A: 

In that case, self does not mean the anObject, it's more a sugar construct.

class << self
  ...
end

defines class methods for the enclosing object. The method alias_method_chain is a method, that alias things. In that case, it aliases has_many to has_many_without_association_options and has_many_with_association_options with has_many. In your case it alias class methods, so, you have to use it in the class method scope. It allows extending methods without much hassle.

Class methods are called like, say :

SomeThing.bar_method

whereas instance methods are called on instances of class :

assoc = SomeThing.new
assoc.foo_method

The corresponding code would be :

class SomeThing
  def foo_method
    ...
  end
  class << self
    def bar_method
      ...
    end
  end
end

in your case, you have the module AttributeFu::Associations. When included in class Foo, it runs Foo.class_eval, which defines some instance attributes inside Foo, and launches a method, alias_method_chain inside the class methods scope (class << self).

There also is an extends ClassMethods which must define either :

def self.has_many_with_association_options
    ...
end

or

class << self
  def has_many_with_association_options
    ...
  end
end
mat
+1  A: 
# init.rb
ActiveRecord::Base.class_eval { include AttributeFu::Associations }

module AttributeFu
    module Associations
        def self.included(base)
            # base == ActiveRecord::Base (the class)
            base.class_eval do
                # class_eval makes self == ActiveRecord::Base, and makes def define instance methods.
                extend ClassMethods

                # If has_many were an instance method, we could do this
                #   alias_method_chain :has_many, :association_option; end
                # but it's a class method, so we have to do the alias_method_chain on
                # the meta-class for ActiveRecord::Base, which is what class << self does.
                class << self; alias_method_chain :has_many, :association_option; end
            end
        end
    end
end

Another way to play around with this is to put this into IRB:

class A ; end
A.class_eval { puts self.inspect ; class << self ; puts self.inspect ; end }

See also

Matt Burke