Intro to dynamic Rails functions and modules
August 5, 2008 – 1:36 amPreamble
First of all, let me go on record as saying this may not be the proper “Rails” way to do this. This example is a solution that worked well for me, and I’m going to be posting about what I learned from the experience. If anyone has better ways of handling situations like this, please post in the comments. Thanks!
Problem
I wanted to create a Rails module that I could include in various models that would do the same thing. In my case, I wanted to be able to identify fields on that model that, when stored, should strip all non-digits. The practical use of this is storing a phone number. This module would allow the user to enter “(123) 456-7890″ into a form and ActiveRecord to store “1234567890″ as an integer.
The end state should look like:
include NondigitStrip class Contact < ActiveRecord::Base strips_nondigits :phone, :fax end
Solution
A module is a file that gets mixed into models (or other files, I suppose) so that you don’t have to re-write the same code for multiple models that should share the same functions. In this case, I created nondigit_strip.rb and stuck it in my ./lib folder. I’m going to paste the text below, and then explain what it’s doing.
module NondigitStrip def strips_nondigits(*attr_names) attr_names.each do |attr_name| create_strip_method(attr_name) do |org_method, args, block| org_method.call(*args, &block) end end end def create_strip_method(param) define_method param.to_s+"=" do |input| super(input.gsub(/[^0-9]/,"")) end end end
There are two methods above. The first one is directly included in the model with strips_nondigits. It takes in a dynamically sized list of attributes (in this case two: :phone and :fax). It then calls the second method which actually creates the digit-stripping method for each attribute.
The second method simply defines a method called param.to_s+”=” (which ends up in the form of phone=) and uses the gsub method of a string to eliminate non-digits. That gsub line can obviously be modified to alter the inputs however you’d need them be. This results in meta-methods (not quite sure what the term is) that look like:
def phone=(input) super(input.gsub(/[^0-9]/,"")) end def fax=(input) super(input.gsub(/[^0-9]/,"")) end
And… voila! Now you can do things like:
>> c = Contact.new(:phone => "(123) 456-7890").phone => 1234567890