Intro to dynamic Rails functions and modules

August 5, 2008 – 1:36 am

Preamble

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

Post a Comment