使用其他功能包装ActiveRecord方法

时间:2011-09-01 01:12:14

标签: ruby-on-rails ruby metaprogramming

我想在Rails中增强ActiveRecord setter以确保只保存有效值。需要这样的一个地方是电话号码。用户可以输入各种格式的电话号码,例如

(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模块(在sanitize_phone_field类中添加类方法 - Person)。该方法现在负责覆盖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的实例的方法进行别名?

1 个答案:

答案 0 :(得分:1)

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

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

这些字段方法在您的源中定义类时不可用,因此您不能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