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?

-Dan

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.

Ar,

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?

-Dan

On Mar 28, 12:37 pm, Ar Chron [email protected]

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

Jon

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.

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.

Schema:

create_table “profiles”, :force => true do |t|
t.integer “user_id”
t.string “name”
t.datetime “created_at”
t.datetime “updated_at”
end

create_table “users”, :force => true do |t|
t.string “username”
t.string “first_name”
t.string “last_name”
t.integer “default_profile_id”
t.datetime “created_at”
t.datetime “updated_at”
end

Models:

class Profile < ActiveRecord::Base
belong_to :user
end

class User < ActiveRecord::Base
has_many :profiles
has_one :default_profile, :class_name => “Profile”
end

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!

-Dan

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.

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs