Uniqueness constraint on a boolean?

I'm defining a table of profiles for a user. The user can have many profiles but only one of them can be flagged as "default". Right now, I just have a "is_default" boolean on the profiles table.

I can see how:

class Profile < ActiveRecord::Base     validates_uniqueness_of :is_default, :scope => :user_id end

would kinda work... but since it's a boolean, I need one Profile record (per user!) to be true and the rest to be false. So I don't think "validates_uniqueness_of" will work in this case.

Also, is there a way to enforce this such that when a different profile is set as the default, the old default profile is updated automatically so it's "is_default" field is set to false?

Thoughts? Ideally, there'd be a plugin for this that would give a different slant on the uniqueness validator... maybe a "validates_singularity_of :is_default, :scope => :user_id" or something like that?


Let me pose a question:

Since a User can have many Profiles, and only one can be the Default Profile for a User at the moment, who (i.e., which model) should know that?

A Profile doesn't care whether it's the current default for a User, the User does. I'd move the indicator for the default profile up to the User model, and make it a default_profile_id.


Thanks for your reply... one way I'm using this is by defining Payment Profiles for users. In my case, there are debitable profiles and creditable profiles (i.e. one that you can take money from and one that you can pay money to). A user could have multiple of each but they would define one of them as the default. Then, I could do stuff like:

user = User.find :first user.credit_profiles # => [PaymentProfile,...] user.default_credit_profile # => PaymentProfile (with the "is_default_credit" flag set to true)

or even:

user.payment_profiles # => [PaymentProfile,...] (debit and credits)

Part of what makes this interesting is that a single payment profile can be both a debit profile and a credit profile. Otherwise, I would have just made each of them a model. Instead, I have PaymentProfile as a model and define "is_debit", "is_credit", "is_default_debit" and "is_default_credit" as booleans. Then, I use conditions on my has_one and has_many associations.

Bottom line is: all of this works great (I even throw in polymorphism so things other than Users can have payment profiles). But, I have to manually manage the defaults. I.e. if the User sets PaymentProfile 5 to be the default debit, then I need to make sure that all other payment profiles for that user have the default debit set to false.

It's not much code, but I just wonder if there is already a validation for this thing. I would think it would be common enough that there would be... i.e. it's a customized version of "validates_uniqueness_of"

Does that help?


I would handle this with a before_update filter. Basically you need to observe the field changing and unset all of the other profiles.

I do this with an attribute accessor on the model:

before_update :reset_is_default_if_changed

attr_accessor :is_default_was

def reset_is_default_if_changed   if is_default and is_default != is_default_was     user.profiles.select{|profile| profile.is_default==true}.each do | profile>       profile.is_default=false     end   end end

In the view you need a hidden field with the old is_default value named is_default_was


Here is another option to consider. With this approach you wouldn't need to keep track of a boolean value at all. Just let Rails associations keep track of the default user profile.


Incidentally this approach also works for multiple defaults. Just add as many foreign keys to User as needed. Give them unique names such as default_credit_id, default_debt_id, etc.

Robert and Jon,

Thank you both!

It's funny, I was leaning towards Jon's idea because I was using polymorphism on the models that have profiles, but I think Robert's approach may be simpler. It means I have to define a FK for each table that has a profile, but that will be fine. The cool thing is I can do both... I'm using polymorphism for some has_many references... i.e. a User has_many profiles but also a User has_one default_profile.

Man, I love Rails! Woo!


And now I've waffled back. I found the Recipe #29 in Advanced Rails Recipes: Create Meaningful Relationships Through Proxies.

So now I've defined on the User class:

  has_many :profiles, :as => :profilable do     def default(reload=false)       @default_profile = nil if reload       @default_profile ||= find_by_is_default(true)     end   end

And this lets me do stuff like:

User.find(:first).profiles.default # => a Profile object that is flagged with the :is_default boolean. (which is nicely similar to: User.find(:first).profiles.first and other such calls)

The main reason is that I'm using polymorphism on the objects that have profiles, so I don't have to define a FK on each table. I like the cleanness of this although it means I'll need to do Jon's logic to ensure that when a new default is set, the old is unset. But as the number of profiles per user should be very small I'm not worried.

So functionally, it really does come back to: a) do I put a FK on each table that has a default Profile and use that for referencing the default Profile object? - upside: simple association assigment. Nice, neat. - downside: additional field on table, loses some functionality? b) do I put a boolean in the Profiles table and use the proxy idea for referencing the object? - upside: make for a nice call: i.e. like .first I can now do .default (for an association of Profiles). I can use polymorphism fully, so the User table doesn't have anything referencing Profiles and it's all handled in the models. - downside: need to handle "singularity" of the is_default field. Performance issues?

Ultimately, I realize that either will work very well. so I dunno. I don't want to think about it too much anymore. I still think that my original idea is interesting: building a "validates_singularity_of" helper. Then, it would just to Jon's code, and could include scoping constraints, i.e. something like:

validates_singularity_of :is_default, :scope => :user_id

or in my case, where I'm using polymorphism I'd have: validates_singularity_of :is_default, :scope => :profilable_id

and maybe a :force => true option that instead of returning a validation error would enforce the singularity, i.e. set the object and unset all the others within the scope.

Hmmm... choices choices

I guess I'll build out my controllers and views for this and see if that guides it one way or the other.