2011-09-01 56 views
0

我想在Rails中增强ActiveRecord设置器以确保只保存有效值。需要这样一个地方是电话号码。用户可以在多种格式,如输入电话号码,用附加功能包装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模块(它增加了一个类的方法 - sanitize_phone_fieldPerson类)。该方法现在负责覆盖设置器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的实例的方法?

+0

在我看来,这将是更清洁和更安全简单在PhoneSanitizer模块中定义实例方法,将模块包含在Person类中,然后从before_save回调中调用方法。 – KenB

+0

其实我非常喜欢这个想法。不知道为什么我以前没有考虑过在验证回调中做这件事。这比使用方法混合玩耍要干净得多。我已经实施了这个解决方案。然而,为了学习和改进我的元编程的业力要点,我仍然有兴趣了解上面我做错了什么。 – Anurag

+0

嗯。我似乎无法重现您的问题:https://gist.github.com/1185316。测试了红宝石1.8和1.9。虽然我有一个想法,你的问题在哪里..我认为你说你得到的错误不是Ruby实际告诉你真正的错误是。 – Casper

回答

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

+0

错误实际上是':phone ='方法未定义,而不是我在我的问题中写的'alias_method'。我不能使用'write_attribute',因为它是专门为'ActiveRecord'定义的,我的一些模型对象是只包含'ActiveModel'的普通对象。但是,您已正确识别问题,因为这些方法是通过检查数据库字段来动态定义的,并且在我的代码运行时不可用。 – Anurag