代码之家  ›  专栏  ›  技术社区  ›  Adriano di Lauro

ActiveRecord:使用类方法重写属性编写器

  •  1
  • Adriano di Lauro  · 技术社区  · 6 年前

    我不知道如何正确表达标题,我认为解释这个问题的最好方法就是使用代码示例。

    我的目标

    我想定义这样的元方法(在Rails 5中):

    class Post < ApplicationRecord
      override_this_attribute_writer :some_attribute
    end
    

    这个 override_this_attribute_writer 它遵循一种常见的模式,通过在其上进行一些过滤来覆盖原始编写器。我发现这种覆盖方式非常方便和清晰。

    第一种方法

    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
    

    在执行此操作时,我在 alias_method ,因为很明显,我试图复制的方法还不存在。

    第二种方法

    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
    

    我原以为这样做行不通:如果在运行meta方法时,ActiveRecord还没有创建属性writer, 这意味着它将稍后执行,并重写我刚才定义的方法。

    但令人惊讶的是,它居然奏效了!所以我把手放在ActiveRecord(5.1.5)中以了解更多信息。

    深入了解ActiveRecord 5.1.5

    我想确保我所做的一切是安全的,而不仅仅是偶然发生的:我调查了 the definition of method writer ,并放置 binding.pry 围绕方法。

    这是实验的结果:

    • 对于 我没有覆盖的属性 ,则,
      1. This line 被称为
      2. 然后在内部定义该方法 this module eval call
      3. 最后 newly created writer method 执行时正确调用 object.attribute=
    • 对于 我覆盖的属性 ,则,
      1. 定义了我自己的方法 在其他事情之前 (当ActiveRecord编写器还没有出现时
      2. 然后ActiveRecord调用 the same line 处理编写器创建的,如前一示例所示
      3. 该方法(显然)由ActiveRecord正确创建,因为它再次传递 by this point
      4. 但现在,令人惊讶的是 对象属性= 仍然调用我自己的方法来代替ActiveRecord方法

    所以,这就是我不理解的:如果ActiveRecord似乎覆盖了我的方法,但它没有覆盖,那么是什么阻止它这样做呢?

    我的问题

    最后,我需要知道的是,我所做的修复实际上是一种良好的做法(而且很健壮),还是存在风险,如果将来我们进行升级,它可能会崩溃。

    如果您认为我的解决方案很危险,您能否提出一种不同的方法来实现相同的目标?

    1 回复  |  直到 4 年前
        1
  •  2
  •   mdesantis    6 年前

    使命感 super 更为惯用:

    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