Forum: Ruby on Rails enforcing special behavior of child rows in HABTM

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
1f1bd7de8337317975b0475be56392d2?d=identicon&s=25 Dan Tenenbaum (dandante)
on 2006-05-08 23:19
Hi,

I have a scenario where a doctor can have one or more specialties.
For each doctor, one and only one of her specialties can be designated
as primary.

So I have tables called doctors, specialties, and doctors_specialties,
the last of which has a boolean is_primary column.

The doctor model class specifies that:
has_and_belongs_to_many :specialties

I want to enforce, at the lowest possible level, the constraint that
a doctor can only have one primary specialty.

So, whenever a member of the association is added or updated with the
value is_primary set to true, code is run to first set all records in
the association to primary = false, thus ensuring that the row about to
be saved is the only one with is_primary = true.

I looked into passing a code block to the HABTM, but couldn't quite get
that to work. Anyone have any suggestions?

This is for a demo to show how one would do this in Rails as opposed to
existing code that does it in .NET. So I'd like the solution to be very
elegant and Rails-esque. Can someone help?

Thanks
E28c35323f624b8b9ed8712e25105454?d=identicon&s=25 Ray Baxter (Guest)
on 2006-05-09 01:05
(Received via mailing list)
Dan Tenenbaum wrote:

> I have a scenario where a doctor can have one or more specialties.
> For each doctor, one and only one of her specialties can be designated
> as primary.

> So I have tables called doctors, specialties, and doctors_specialties,
> the last of which has a boolean is_primary column.
>
> The doctor model class specifies that:
> has_and_belongs_to_many :specialties
>
> I want to enforce, at the lowest possible level, the constraint that
> a doctor can only have one primary specialty.

One way: rename doctors_specialties to something like specializations
and make it a full-fledged model. Then replace your habtm relationship
with a has_many :through relationship. The specialization model can
enforce the uniqueness with either a validation or a before filter.

class doctor < AR::B
   has_many :specializations
   has_many :specialties, :through => :specializations
end

class specialization < AR::B
   belongs_to :doctors
   belongs_to :specialties
   validates_uniqueness_of :primary, :scope => :doctor_id
end

class specialty < AR::B
   has_many :specializations
   has_many :doctors, :through => :specializations
end

> So, whenever a member of the association is added or updated with the
> value is_primary set to true, code is run to first set all records in
> the association to primary = false, thus ensuring that the row about to
> be saved is the only one with is_primary = true.

The above doesn't handle unsetting the other specialties. You would have
to add a before_save filter to do that.

--

Ray
E28c35323f624b8b9ed8712e25105454?d=identicon&s=25 Ray Baxter (Guest)
on 2006-05-09 01:17
(Received via mailing list)
Ray Baxter wrote:

>   validates_uniqueness_of :primary, :scope => :doctor_id

That won't work. There are only two values of primary, true and false.
Under this logic there can only be two specialties. Go with the filter.

--

Ray
1f1bd7de8337317975b0475be56392d2?d=identicon&s=25 Dan Tenenbaum (dandante)
on 2006-05-09 01:41
Ray Baxter wrote:
> Ray Baxter wrote:
>
>>   validates_uniqueness_of :primary, :scope => :doctor_id
>
> That won't work. There are only two values of primary, true and false.
> Under this logic there can only be two specialties. Go with the filter.
>
> --
>
> Ray

This works great. Thanks.
1f1bd7de8337317975b0475be56392d2?d=identicon&s=25 Dan Tenenbaum (dandante)
on 2006-05-09 20:03
Running into a problem...original post (with code) as at bottom, for
context.

The problem is, I am not sure how to elegantly add to a doctor's
"specialties" collection. I can construct a new Specialization and save
it, but that doesn't seem like the Rails way. When I try this:

>> d = Doctor.find 1
=> #<Doctor:0xb77f07b0 @attributes={"name"=>"name", "title"=>"title",
"id"=>"1"}>
>> s = Specialty.find 1
=> #<Specialty:0xb77eaa68 @attributes={"name"=>"neuro", "id"=>"1"}>
>> s.is_primary = true
=> true
>> d.specialties<<s
=> [#<Specialty:0xb77eaa68 @attributes={"name"=>"neuro", "id"=>"1"},
@is_primary=true>]
>> Specialization.find_all
=> []

As you can see, nothing is written to Specializations.

Similarly:
>> d.specializations<<s
ActiveRecord::AssociationTypeMismatch: Specialization expected, got
Specialty

I thought perhaps my before_save method was the culprit, and got rid of
it temporarily, but still got the same behavior. Any thoughts?
Thanks.

Original post:
Ray Baxter wrote:
> Dan Tenenbaum wrote:
>
>> I have a scenario where a doctor can have one or more specialties.
>> For each doctor, one and only one of her specialties can be designated
>> as primary.
>
>> So I have tables called doctors, specialties, and doctors_specialties,
>> the last of which has a boolean is_primary column.
>>
>> The doctor model class specifies that:
>> has_and_belongs_to_many :specialties
>>
>> I want to enforce, at the lowest possible level, the constraint that
>> a doctor can only have one primary specialty.
>
> One way: rename doctors_specialties to something like specializations
> and make it a full-fledged model. Then replace your habtm relationship
> with a has_many :through relationship. The specialization model can
> enforce the uniqueness with either a validation or a before filter.
>
> class Doctor < AR::B
>    has_many :specializations
>    has_many :specialties, :through => :specializations
> end
>
> class Specialization < AR::B
>    belongs_to :doctors
>    belongs_to :specialties
>   # before_save filter which handles enforcement goes here
> end
>
> class Specialty < AR::B
>    has_many :specializations
>    has_many :doctors, :through => :specializations
>    attr_accessor :is_primary
> end
>
>> So, whenever a member of the association is added or updated with the
>> value is_primary set to true, code is run to first set all records in
>> the association to primary = false, thus ensuring that the row about to
>> be saved is the only one with is_primary = true.
61b70c2f195b0e669a8e25000148d9dd?d=identicon&s=25 Eden Brandeis (Guest)
on 2006-05-10 02:34
(Received via mailing list)
Do you need to save the doctor for the connection to be made?

d.save
1f1bd7de8337317975b0475be56392d2?d=identicon&s=25 Dan Tenenbaum (dandante)
on 2006-05-10 03:06
Eden Brandeis wrote:
> Do you need to save the doctor for the connection to be made?
>
> d.save

No, that didn't make a difference......
61b70c2f195b0e669a8e25000148d9dd?d=identicon&s=25 Eden Brandeis (Guest)
on 2006-05-10 03:14
(Received via mailing list)
Dan,

Did you switch to a join model?  If so, then this probably answers your
question:

http://blog.hasmanythrough.com/articles/read/150
1f1bd7de8337317975b0475be56392d2?d=identicon&s=25 Dan Tenenbaum (dandante)
on 2006-05-10 03:22
Eden Brandeis wrote:
> Dan,
>
> Did you switch to a join model?  If so, then this probably answers your
> question:
>
> http://blog.hasmanythrough.com/articles/read/150

Awesome...that answers the question quite thoroughly. I had no idea
there was a whole site devoted to has_many :through.
This topic is locked and can not be replied to.