# Enforcing special behavior of child rows in HABTM

**URL:** <https://www.ruby-forum.com/t/enforcing-special-behavior-of-child-rows-in-habtm/59956>\
**Category:** Rails\
**Created:** [May 8, 2006, 11:19pm UTC](https://www.ruby-forum.com/t/enforcing-special-behavior-of-child-rows-in-habtm/59956 "2006-05-08T23:19:29Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![dandante](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/9fc29f/32.png) [@dandante](https://www.ruby-forum.com/u/dandante)\
**Post date:** [May 8, 2006, 11:19pm UTC](https://www.ruby-forum.com/t/enforcing-special-behavior-of-child-rows-in-habtm/59956/1 "2006-05-08T23:19:29Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![dandante](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/9fc29f/32.png) [@dandante](https://www.ruby-forum.com/u/dandante)\
**Post date:** [May 9, 2006, 1:17am UTC](https://www.ruby-forum.com/t/enforcing-special-behavior-of-child-rows-in-habtm/59956/2 "2006-05-09T01:17:40Z")

</div>

Ray B. 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

---

<div class="post-metadata">

**Author:** ![dandante](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/9fc29f/32.png) [@dandante](https://www.ruby-forum.com/u/dandante)\
**Post date:** [May 9, 2006, 1:05am UTC](https://www.ruby-forum.com/t/enforcing-special-behavior-of-child-rows-in-habtm/59956/3 "2006-05-09T01:05:33Z")

</div>

Dan T. 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

---

<div class="post-metadata">

**Author:** ![dandante](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/9fc29f/32.png) [@dandante](https://www.ruby-forum.com/u/dandante)\
**Post date:** [May 9, 2006, 1:41am UTC](https://www.ruby-forum.com/t/enforcing-special-behavior-of-child-rows-in-habtm/59956/4 "2006-05-09T01:41:04Z")

</div>

Ray B. wrote:

> Ray B. 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.

---

<div class="post-metadata">

**Author:** ![dandante](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/9fc29f/32.png) [@dandante](https://www.ruby-forum.com/u/dandante)\
**Post date:** [May 9, 2006, 8:03pm UTC](https://www.ruby-forum.com/t/enforcing-special-behavior-of-child-rows-in-habtm/59956/5 "2006-05-09T20:03:38Z")

</div>

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 B. wrote:

> Dan T. 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.

---

<div class="post-metadata">

**Author:** ![dandante](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/9fc29f/32.png) [@dandante](https://www.ruby-forum.com/u/dandante)\
**Post date:** [May 10, 2006, 2:34am UTC](https://www.ruby-forum.com/t/enforcing-special-behavior-of-child-rows-in-habtm/59956/6 "2006-05-10T02:34:57Z")

</div>

Do you need to save the doctor for the connection to be made?

d.save

---

<div class="post-metadata">

**Author:** ![dandante](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/9fc29f/32.png) [@dandante](https://www.ruby-forum.com/u/dandante)\
**Post date:** [May 10, 2006, 3:06am UTC](https://www.ruby-forum.com/t/enforcing-special-behavior-of-child-rows-in-habtm/59956/7 "2006-05-10T03:06:50Z")

</div>

Eden B. wrote:

> Do you need to save the doctor for the connection to be made?
> 
> d.save

No, that didn’t make a difference…

---

<div class="post-metadata">

**Author:** ![dandante](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/9fc29f/32.png) [@dandante](https://www.ruby-forum.com/u/dandante)\
**Post date:** [May 10, 2006, 3:14am UTC](https://www.ruby-forum.com/t/enforcing-special-behavior-of-child-rows-in-habtm/59956/8 "2006-05-10T03:14:54Z")

</div>

Dan,

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

[http://blog.hasmanythrough.com/articles/read/150](http://blog.hasmanythrough.com/articles/read/150)

---

<div class="post-metadata">

**Author:** ![dandante](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/9fc29f/32.png) [@dandante](https://www.ruby-forum.com/u/dandante)\
**Post date:** [May 10, 2006, 3:22am UTC](https://www.ruby-forum.com/t/enforcing-special-behavior-of-child-rows-in-habtm/59956/9 "2006-05-10T03:22:22Z")

</div>

Eden B. wrote:

> Dan,
> 
> Did you switch to a join model? If so, then this probably answers your  
> question:
> 
> [http://blog.hasmanythrough.com/articles/read/150](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.
