Geek Blog for Little Red String

Geek stuff, amature programming, Linux, Ruby, open source

ActiveRecord Validations using with_options

I recently got some great help from Ben Hughes on refactormycode.com.

I working on adding OpenID authentication to my site TalkDB and after following the great railscast on the subject I was left with some decisions to make regarding how this was going to fit cleanly into my current app. One problem that I ran into was that I started developing a lot of conditions around validations in my user model. Users with an OpenID don’t need email or a password but they could have one if they wanted. Also users with an OpenID don’t need a login name/nickname because it could be supplied by their OpenID provider or could be replaced with their OpenID identity url.

Going off of what restful_authentication had in place for me, I started using the existing conditions (:if => password_required) to indicate when validations should be run. This was total chaos. I refactored this a bit so that I had more descriptive methods that determined when a validation on an attribute should be run.

in models/user.rb

def password_validations_required?
  (crypted_password.blank? && !using_openid?) || !password.blank?
end

In other words, if the user doesn’t have a password and they’re not using an OpenID then we need to run validations. If the user entered a password in a form (caught in the password variable) then we should also validate it. This was somewhat better, but the validation rules were far from DRY.

in models/user.rb

validates_presence_of     :password,                   :if => :password_validations_required?
validates_presence_of     :password_confirmation,      :if => :password_validations_required?
validates_length_of       :password, :within => 4..40, :if => :password_validations_required?
validates_confirmation_of :password,                   :if => :password_validations_required?

Ben pointed out that I should be using with_options to get rid of this nonsense:

with_options :if => :password_validations_required? do |p|
  p.validates_presence_of     :password
  p.validates_presence_of     :password_confirmation
  p.validates_length_of       :password, :within => 4..40
  p.validates_confirmation_of :password
end

Which is exactly what I was hoping for. I eventually found the documentation for with_options. It’s implemented on the Object class so it looks like there are tons of great ways this could clean up code.