I recently ran into a problem where I needed to use validates_uniqueness_of with more than one scope. Basically, I have resources that users can either share or keep to themselves. Each resource has a description that should be unique. However, the scope of the uniqueness depends on the situation. In pseudo-code, I wanted something like:
validates_uniqueness_of :description, :scope => :shared
validates_uniqueness_of :description, :scope => :user_id
(That is, if you share, the description should be unique across all the shared resources. Otherwise, it should just be unique within your stuff.)
That seems straightforward, but it’s complicated by the fact that Rails validations are at the class-level rather than the instance-level. To me, it makes more sense to execute validations at the instance-level.
Specifically because they’re not executed at the instance-level, validations like validates_uniqueness_of need “smelly” option keys like :if and :unless. If the validations were executed in the proper context (i.e., in the context of the instance that’s being validated), Rails wouldn’t need any of that hackery. The real if and unless could be used directly.
The Hashrocket folks said something similar in their Rails 3 Blog Club – Week 1 video. As far as I know, the situation is staying the same in Rails 3, even with all the validation refactoring. Even so, I think there’s a strong argument for moving validations to the instance level.
For example, if validations were executed in the context of the model instance, the above would have translated directly to something like this:
class MyModel < ActiveRecord::Base
validate_uniqueness_of :description, :scope => :shared
validate_uniqueness_of :description, :scope => :user_id
(Note for blog skimmers, the above code does NOT work.)