views:

142

answers:

2

Having a bit of difficulty finding out the proper way to mix in code that I put into the lib/ directory for Rails 2.3.5.

I have several models that require phone validation. I had at least three models that used the same code, so I wanted to keep things DRY and moved it out to the lib/ directory. I used to have code like this in each model:

validate :phone_is_valid

Then I'd have a phone_is_valid method in the model:

protected
def phone_is_valid
  # process a bunch of logic
  errors.add_to_base("invalid phone") if validation failed
end 

I moved this code out into lib/phones/ and in lib/phones I have lib/phones/phone_validation.rb, and in there I copy pasted the phone_is_valid method.

My question is, how do I mix this into all of my models now? And does my validate :phone_is_valid method remain the same or does that change? I want to make sure that the errors.add_to_base method continues to function as it did before while keeping everything DRY. obviously the following won't work:

validate :Phones::PhoneValidation::phone_is_valid(number)

I also created another file in lib/phones/ called lib/phones/phone_normalize.rb. Again, many models need the value input by the user to be normalized. Meaning turn (555) 222-1212 to 5552221212 or something similar. Can I invoke that simply by invoking Phones::Phone_Normalize::normalize_method(number)?

I suppose I'm confused on the following:

  • How to use the lib directory for validation of multiple models that need access to a particular validation method
  • How to use the lib directory for commonly shared methods that return values
+2  A: 
class Profile < ActiveRecord::Base
  include Phones::PhoneValidation
  validate_phone_is_valid
end

If you want to use that for many model then you might monkey patch you validations straight into ActiveRecord:

module ActiveRecord
  module Validations
    module ClassMethods
      def validates_photo(*attr_names)
        # ....
      end
    end
  end
end
Thomas R. Koll
Shouldn't it be validate :phone_is_valid? Also, what if the column I'm validating has different names in the different models. That means I'd need to be able to pass it to the validation method as an argument. I prefer not baking it directly into ActiveRecord.
randombits
`validate :phone_is_valid?` would call the method `phone_is_valid?` during the validation process, of course possible but it's outside the naming convention for validations.And for the different attribute names I've put `*attr_names` into my second example of code.
Thomas R. Koll
+1  A: 

A bit of metaprogramming magick?

module Phones

  def self.included(base)
    base.send :extend, ClassMethods
  end

  module ClassMethods
    def validate_phone(*attr_names)
      #setup the config array eg. configuration = atrr.extract_options!
      validates_each(attr_names, configuration) do |record, attr_name, value|
          record.errors.add(attr_name, configuration[:message]) unless #validation
       end
       #setup the phone normalization
       unless configuration[:normalize]
         before_save do
           # normalization code here
         end
       end
    end
  end
end

ActiveRecord::Base.send :include, Phones

Then in your model:

validate_phone :main_phone, :cellphone, :message => "not a valid telephone number"
Jakub Hampl