Wrapping ActiveRecord methods with additional functionality
I want to enhance the ActiveRecord setters in Rails to ensure only valid values are saved. One such place where this is needed is phone numbers. A user may enter a phone number in a variety of formats such as,
(123) 456-7890
+1 123-456-7890
but I only want to store the digits and discard the rest when it goes to the database. The approach I am using right now is to override the setter methods using alias_method
. Also, I am trying to put this into a module so any model class that contains phone number can include this module, and define the fields that should be cleaned up. The kind of interface I am hoping to use is,
# 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
The only thing I'm doing inside my model classes i开发者_Python百科s to include the PhoneSanitizer
module (which adds a class method - sanitize_phone_field
in the Person
class). That method is now responsible for overriding the setter phone=
method. This is the part I haven't gotten to work.
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
When sanitize_phone
is called, it throws an error saying :phone=
is not defined for the Person
class, and that makes sense. How would I go about aliasing the method for the instances of Person instead?
I think your error was not undefined method alias_method
it was something different and you misinterpreted it(?)
The real problem is that the getter and setter methods on ActiveRecord
are dynamic. The getter and setter methods (i.e. phone
and phone=
) are not created until the actual AR object is loaded from the database. At that point AR enumerates the DB fields and creates the corresponding field methods.
These field methods are not available at the time the class is being defined in your source, so you can't alias_method
a method that does not exist. You could however do something like this instead (not tested):
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
That should accomplish pretty much the same thing as you originally intended:
http://apidock.com/rails/ActiveRecord/AttributeMethods/Write/write_attribute
精彩评论