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”

On Sep 23, 11:27 pm, MaggotChild [email protected] wrote:

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

On Sep 24, 12:34 am, Frederick C. [email protected]
wrote:

at the time alias_method was called, for example:
“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

On Sep 24, 5:40 pm, MaggotChild [email protected] wrote:

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.

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 –

On Thu, 24 Sep 2009, Frederick C. wrote:

write_attribute on AttributeMethods but it is too late - the aliasing
that occured in Dirty is pointing at the previous implementation.

This thread seems to be going on both here and in ruby-talk. See my
most recent answer in ruby-talk; there is, indeed an
alias_method_chain call involving write_attribute (but not
read_attribute) that happens before the initializer is executed.

David


David A. Black, Director
Ruby Power and Light, LLC (http://www.rubypal.com)
Ruby/Rails training, consulting, mentoring, code review
Book: The Well-Grounded Rubyist (The Well-Grounded Rubyist)

Hi –

On Thu, 24 Sep 2009, MaggotChild wrote:

included in ActiveRecord::Base, write_attribute is aliased &
 alias_method :old_implementation, :to_be_aliased

apposed to including it via a module) -which is possible.

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


David A. Black, Director
Ruby Power and Light, LLC (http://www.rubypal.com)
Ruby/Rails training, consulting, mentoring, code review
Book: The Well-Grounded Rubyist (The Well-Grounded Rubyist)