【问题标题】:Wrapping ActiveRecord methods with additional functionality用附加功能包装 ActiveRecord 方法
【发布时间】:2011-09-01 01:12:13
【问题描述】:

我想增强 Rails 中的 ActiveRecord 设置器以确保只保存有效值。需要这样做的地方之一就是电话号码。用户可以输入多种格式的电话号码,例如,

(123) 456-7890
+1 123-456-7890

但我只想存储数字并在进入数据库时​​丢弃其余数字。我现在使用的方法是使用alias_method 覆盖setter 方法。另外,我正在尝试将其放入一个模块中,以便任何包含电话号码的模型类都可以包含此模块,并定义应清理的字段。我希望使用的界面是,

# Person has a "phone" attribute to store phone numbers
class Person < ActiveRecord::Base
  # first include this module
  include PhoneSanitizer

  # then call the class method and tell it which 
  # fields need need to be sanitized
  sanitize_phone_field :phone
end

我在模型类中唯一要做的就是包含PhoneSanitizer 模块(它在Person 类中添加了一个类方法-sanitize_phone_field)。该方法现在负责覆盖 setter phone= 方法。这是我还没开始工作的部分。

module PhoneSanitizer

  module ClassMethods
    # wrap each of the passed-in fields with setters that
    # clean up the phone number value of non-digits.
    def sanitize_phone(*fields)
      fields.each do |field|
        new_method = "original_#{field}=".to_sym
        original_method = "#{field}=".to_sym
        alias_method new_method, original_method
        define_method(original_method) do |value|
          self.send(new_method, phone_to_number(value))
        end
      end
    end
  end

  def self.included(base)
    base.extend(ClassMethods)
  end

  def phone_to_number(number)
    number.gsub(/[^\d]/, '')
  end

end

当调用sanitize_phone 时,它会抛出一个错误,指出:phone= 没有为Person 类定义,这是有道理的。我将如何为 Person 实例的方法设置别名?

【问题讨论】:

  • 在我看来,简单地在 PhoneSanitizer 模块中定义实例方法,将模块包含在您的 Person 类中,然后从 before_save 回调中调用这些方法会更干净、更安全。
  • 其实我很喜欢这个主意。不知道为什么我之前没有想过在验证回调中这样做。这比玩弄方法调配要干净得多。我已经将其作为解决方案实施。但是,为了学习和提高我的元编程业力点,我仍然有兴趣找出我在上面做错了什么。
  • 嗯。我似乎无法重现您的问题:gist.github.com/1185316。在 ruby​​ 1.8 和 1.9 上测试。虽然我知道你的问题出在哪里..我认为你所说的错误并不是 Ruby 实际告诉你的真正错误。
  • @Casper - 很抱歉造成混乱。我的意思是写:person=方法是未定义的,而不是alias_method

标签: ruby-on-rails ruby metaprogramming


【解决方案1】:

我认为你的错误不是 undefined method alias_method 它是不同的东西,你误解了它(?)

真正的问题是ActiveRecord 上的getter 和setter 方法是动态的。在从数据库加载实际的 AR 对象之前,不会创建 getter 和 setter 方法(即 phonephone=)。此时,AR 会枚举 DB 字段并创建相应的字段方法。

在您的源代码中定义该类时,这些字段方法不可用,因此您不能alias_method 一个不存在的方法。但是,您可以这样做(未经测试):

module PhoneSanitizer
  module ClassMethods
    def sanitize_phone(*fields)
      fields.each do |field|
        original_method = "#{field}=".to_sym
        define_method(original_method) do |value|
          self.write_attribute(field, phone_to_number(value))
        end
      end
    end
  end

  ...
end

这应该与您最初的预期完成几乎相同的事情:
http://apidock.com/rails/ActiveRecord/AttributeMethods/Write/write_attribute

【讨论】:

  • 错误实际上是 :phone= 方法未定义,而不是 alias_method 我在我的问题中写的。我不能使用write_attribute,因为它是专门为ActiveRecord 定义的,而且我的一些模型对象是仅包含ActiveModel 片段的普通对象。但是,您已经正确识别了问题,因为这些方法是通过检查数据库字段动态定义的,并且在我的代码运行时不可用。
猜你喜欢
  • 2019-08-20
  • 2017-06-18
  • 1970-01-01
  • 2022-07-16
  • 1970-01-01
  • 2020-11-25
  • 2015-12-31
  • 2016-09-23
  • 1970-01-01
相关资源
最近更新 更多