Overriding AR read/write_attribute - Overridden write_attribute Is Never Called

Could someone explain this?

#config/initializers/ar_attributes.rb

module ActiveRecord   module AttributeMethods

      alias_method :ar_read_attribute, :read_attribute       def read_attribute(attr_name)         p "read_override"         ar_read_attribute(attr_name)       end

      alias_method :ar_write_attribute, :write_attribute       def write_attribute(attr_name, value)         raise 'You made it!'       end   end end

In the Rails console:

person.read_attribute :name

"read_override" => "Joe"

person.write_attribute :name, "Bilal"

=> "Bilal"

person.read_attribute :name

"read_override" => "Bilal"

Could someone explain this?

At a quick glance it is probably because after AttributeMethods is included in ActiveRecord::Base, write_attribute is aliased & overwridden (eg the change tracking module). You then change write_attribute on AttributeMethods but it is too late - the aliasing that occured in Dirty is pointing at the previous implementation. When you alias a method ruby does keep track of what the aliased method was at the time alias_method was called, for example:

class Foo   def to_be_aliased     "implementation 1"   end   alias_method :old_implementation, :to_be_aliased end

class Foo   def to_be_aliased      "implementation 2"   end end

Foo.new.old_implementation #=> "implementation 1"

Fred

Hi Fred,

Thanks for your response. I think you're on to something and I'll have to take a look a the Dirty module (amongst others). But, even if the module was aliasing the original write_attribute, I don't see how this would interfere with my redefinition.

write_attribute is at the top of the call stack so I'd think it's going to use the most recent definition. Unless some other module redefined write_attribute within the scope of active AR::Base (as apposed to including it via a module) -which is possible.

Using your example, I believe the case is more like the following:

class Foo   def to_be_aliased     p "implementation 1"   end   alias_method :old_implementation, :to_be_aliased end

class Foo   def to_be_aliased     p "implementation 2"   end end

Foo.new.to_be_aliased #implementation 2

Hi --

Yes, this is what happens, the alias is performed in a class_eval block giving it presidency over my attempted override in the module.

Hi --

Could someone explain this?

At a quick glance it is probably because after AttributeMethods is included in ActiveRecord::Base, write_attribute is aliased & overwridden (eg the change tracking module). You then change write_attribute on AttributeMethods but it is too late - the aliasing that occured in Dirty is pointing at the previous implementation. When you alias a method ruby does keep track of what the aliased method was at the time alias_method was called, for example:

class Foo def to_be_aliased "implementation 1" end alias_method :old_implementation, :to_be_aliased end

class Foo def to_be_aliased "implementation 2" end end

Foo.new.old_implementation #=> "implementation 1"

Hi Fred,

Thanks for your response. I think you're on to something and I'll have to take a look a the Dirty module (amongst others). But, even if the module was aliasing the original write_attribute, I don't see how this would interfere with my redefinition.

write_attribute is at the top of the call stack so I'd think it's going to use the most recent definition. Unless some other module redefined write_attribute within the scope of active AR::Base (as apposed to including it via a module) -which is possible.

Using your example, I believe the case is more like the following:

class Foo def to_be_aliased    p "implementation 1" end alias_method :old_implementation, :to_be_aliased end

class Foo def to_be_aliased    p "implementation 2" end end

Foo.new.to_be_aliased #implementation 2

It involves alias_method_chain, so it's somewhat like this:

module AR    module M      def x; puts "M#x"; end      def self.included(base)        base.alias_method_chain(:x, :y)      end    end

   class Base      def x_with_y; puts "x_with_y"; end      p Base.instance_methods(false).sort # does not include "x"      include M      p Base.instance_methods(false).sort # does include "x"    end end

AR::Base.new.x

After alias_method_chain, AR::Base has an "x" method, so overriding the one in AR::M won't affect what happens when you call x.

I stripped it down to the bare bones (and then some, perhaps :slight_smile: but I think this represents what happens. See the file dirty.rb, which is where the alias_method_chain call is.

David