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?
endIn 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
endWhich 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.