Adriano di Lauro
Adriano di Lauro

Reputation: 589

ActiveRecord: override attribute writers by using a class method

I don't know how to correctly phrase the title, I think the best way to explain this issue is just with code samples.

My goal

I want to define a meta method like this (in Rails 5):

class Post < ApplicationRecord
  override_this_attribute_writer :some_attribute
end

The override_this_attribute_writer follows a common pattern, it overrides the original writer by doing some filtering on top of it. I find this way of overriding very convenient and clear.

First approach

module MyCommonModule
  extend ActiveSupport::Concern

  module ClassMethods
    def override_this_attribute_writer(attribute_name)
    alias_method :"#{attribute_name}_old=", :"#{attribute_name}="
    define_method :"#{attribute_name}=" do |a_value|
      # Do my stuff
      send(:"#{attribute_name}_old=", a_value)
    end
  end
end

When doing this, I was getting an exception at the call of alias_method, because, apparently, the method I was trying to copy didn't exist (yet).

Second approach

module MyCommonModule
  extend ActiveSupport::Concern

  module ClassMethods
    def override_this_attribute_writer(attribute_name)
    define_method :"#{attribute_name}=" do |a_value|
      # Do my stuff
      send(:write_attribute, attribute_name, a_value)
    end
  end
end

I was expecting this not to work: if, when running the meta method, ActiveRecord hasn't created the attribute writer yet, this means that it will do it later and override the method that I just defined.

But surprisingly it worked! So I put my hands inside ActiveRecord (5.1.5) to find out more.

Dig into ActiveRecord 5.1.5

I wanted to ensure that what I did was safe and it wasn't just working by accident: I looked into the definition of method writer, and put binding.pry around the method.

This is the result of the experiment:

So, this is what I don't understand: if ActiveRecord seems to be overriding my method but it doesn't, what prevents it from doing it?

My questions

What in the end I need to know is whether the fix I have done is actually a good practice (and robust) or it's at risk and it might break if in the future we do upgrades.

If you think that my fix is dangerous, would you be able to suggest a different way to achieve the same goal?

Upvotes: 2

Views: 1123

Answers (1)

mdesantis
mdesantis

Reputation: 8517

Calling super is even more idiomatic:

module MyCommonModule
  extend ActiveSupport::Concern

  module ClassMethods
    def override_this_attribute_writer(attribute_name)
      define_method :"#{attribute_name}=" do |value|
        # do some stuff
        super value
      end
    end
  end
end

Upvotes: 4

Related Questions