Ruby on Rails – Skipping validations based on where object is created

I ran into an issue today where I wanted to skip validations based on where an object was created. To be more specific, I wanted to validate strictly if a “venue” was created through my web interface but I want to skip validations when importing venues. The reasoning is pretty simple, I want to require that a user provides a zip code, state, etc. if they use my web interface. However, if I import venues from a website with CRON at some frequency, I want to gather as much venue data as possible and ask users to update it later if needed.

This is not very straightforward in Rails. After all, I do not want to skip validations entirely. I ended up creating a virtual attribute for validations I wanted to skip and checking the attribute before validating:

validates_presence_of :name
validates_presence_of :country_id, :unless => "!validations_to_skip.nil? and validations_to_skip.include?('country')"
validates_presence_of :region_id, :unless => "!validations_to_skip.nil? and validations_to_skip.include?('region')"
validates_presence_of :city, :unless => "!validations_to_skip.nil? and validations_to_skip.include?('city')"
validates_presence_of :zip, :unless => "!validations_to_skip.nil? and validations_to_skip.include?('zip')"
 
validates_existence_of :user
 
# array of validations to skip
attr_accessor :validations_to_skip
attr_protected :validations_to_skip

You can put something like that at the top of your model file. Be sure to protect the virtual attribute so an attacker cannot set validations_to_skip from a web request in cases where you mass assign the model attributes.

To skip validations just do something like this when you create a venue:

v = Venue.new
v.validations_to_skip = ["country","region","city","zip"]
v.name = "A Venue"
v.user = some_user
v.save

What do you think? Anyone have a better way?

No TweetBacks yet. (Be the first to Tweet this post)
Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Google
  • MySpace
  • Slashdot
  • StumbleUpon
  • Technorati
  • TwitThis

If you enjoyed this post, make sure you subscribe to my RSS feed!

This entry was posted in Software and tagged , , , , . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

4 Comments

  1. Posted December 14, 2009 at 1:59 pm | Permalink

    That’s pretty nice, I’ve also had a need to do something similar in the past. It would be nice to make a plugin for this which DRYed up the syntax a bit, so you could just mark a validation as skippable:

    validates_presence_of :name
    validates_presence_of :country_id, :skippable => true
    validates_presence_of :region_id, :skippable => true
    validates_presence_of :city, :skippable => true
    validates_presence_of :zip, :skippable => true

    Maybe a project for Open Source Hack Night?

  2. Posted December 14, 2009 at 2:13 pm | Permalink

    @paul I agree we could DRY this up with a plugin. How would AR know to check if it should skip a validation? Saying a validation is “skippable” is not enough, we need to be able to set a flag in our code as well so we know when to skip the validation. From a security standpoint, I definitely agree that we should have to white list the attributes that are even possible to skip which is where your code above makes great sense.

  3. Posted December 15, 2009 at 7:39 am | Permalink

    I think defining which validations are skippable per object, similar to what you have, makes sense. The plugin could take care of defining that accessor.

    v = Venue.new
    v.validations_to_skip! :country, :region, :city, :zip
    v.name = "A Venue"
    v.user = some_user
    v.save
  4. Posted March 9, 2010 at 1:09 pm | Permalink

    awesome, exactly what I was looking for :) cheers

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">