# Enforce mutual exclusivity

**URL:** <https://www.ruby-forum.com/t/enforce-mutual-exclusivity/114110>\
**Category:** Rails\
**Created:** [September 8, 2007, 12:29am UTC](https://www.ruby-forum.com/t/enforce-mutual-exclusivity/114110 "2007-09-08T00:29:57Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![Toby\_OSRourke](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/ac91a4/32.png) [@Toby\_OSRourke](https://www.ruby-forum.com/u/Toby_OSRourke)\
**Post date:** [September 8, 2007, 12:29am UTC](https://www.ruby-forum.com/t/enforce-mutual-exclusivity/114110/1 "2007-09-08T00:29:57Z")

</div>

Hi,

First post, please be gentle! I couldn’t find any mention of this  
already

I’m using ActiveRecord and I want to enforce mutual exclusivity on a  
has\_and\_belongs\_to\_many.

A concrete example: I have a User which can have one or more Roles  
(student, tutor, headmaster, administrator etc). However if a user is  
a  
student they cannot hold any other role. I was hoping to find  
something  
like validate\_exclusive but that doesn’t appear to exist. Can anyone  
suggest how I can implement this. Otherwise, is there a better  
pattern  
I could use to model this?

Many thanks,

Toby.

---

<div class="post-metadata">

**Author:** ![Toby\_OSRourke](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/ac91a4/32.png) [@Toby\_OSRourke](https://www.ruby-forum.com/u/Toby_OSRourke)\
**Post date:** [September 8, 2007, 12:31am UTC](https://www.ruby-forum.com/t/enforce-mutual-exclusivity/114110/2 "2007-09-08T00:31:47Z")

</div>

if a User can only have one of these you can just use

has\_one ?

PS: Did you look at the options you have for has\_and\_belongsto\_many ?

---

<div class="post-metadata">

**Author:** ![Toby\_OSRourke](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/ac91a4/32.png) [@Toby\_OSRourke](https://www.ruby-forum.com/u/Toby_OSRourke)\
**Post date:** [September 8, 2007, 12:39am UTC](https://www.ruby-forum.com/t/enforce-mutual-exclusivity/114110/3 "2007-09-08T00:39:57Z")

</div>

Jamal,

Sorry, perhaps I wasn’t clear. a user only has\_one if that one is  
“student” otherwise it is valid that a user could have multiple roles.

I’m very much a newbie with rails, what do the options offer?

Thanks,

Toby.

On Sep 7, 11:31 pm, Jamal S. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)

---

<div class="post-metadata">

**Author:** ![Toby\_OSRourke](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/ac91a4/32.png) [@Toby\_OSRourke](https://www.ruby-forum.com/u/Toby_OSRourke)\
**Post date:** [September 8, 2007, 12:46am UTC](https://www.ruby-forum.com/t/enforce-mutual-exclusivity/114110/4 "2007-09-08T00:46:32Z")

</div>

Toby O’Rourke wrote:

> Jamal,
> 
> Sorry, perhaps I wasn’t clear. a user only has\_one if that one is  
> “student” otherwise it is valid that a user could have multiple roles.
> 
> I’m very much a newbie with rails, what do the options offer?
> 
> Thanks,
> 
> Toby.
> 
> On Sep 7, 11:31 pm, Jamal S. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)

You might want to use the condition which check if the user is a student  
or not, and then you can allow the user to have more roles then the one  
he have. 🙂

> **[ActiveRecord::Associations::ClassMethods](https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html)**
>
> Associations are a set of macro-like class methods for tying objects together through foreign keys.

---

<div class="post-metadata">

**Author:** ![Toby\_OSRourke](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/ac91a4/32.png) [@Toby\_OSRourke](https://www.ruby-forum.com/u/Toby_OSRourke)\
**Post date:** [September 9, 2007, 12:31am UTC](https://www.ruby-forum.com/t/enforce-mutual-exclusivity/114110/5 "2007-09-09T00:31:09Z")

</div>

Keep in mind that Rails DB restrictions are not guaranteed in the case  
multiple applications access the database together.

If you can define these restrictions in the database level, e.g. as  
unique indexes, you’re much safer.

Amir

---

<div class="post-metadata">

**Author:** ![Toby\_OSRourke](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/ac91a4/32.png) [@Toby\_OSRourke](https://www.ruby-forum.com/u/Toby_OSRourke)\
**Post date:** [September 8, 2007, 8:49pm UTC](https://www.ruby-forum.com/t/enforce-mutual-exclusivity/114110/6 "2007-09-08T20:49:45Z")

</div>

Toby,

I think you have a couple of options.

If you want to continue with has\_and\_belongs\_to\_many, then there is a  
before\_add callback available:

class User \< AR::Base  
has\_and\_belongs\_to\_many :roles, :before\_add  
=\> :check\_student\_exclusive  
def check\_student\_exclusive  
# do your checks here for exclusiveness and raise if problem  
end  
end

The trick with the before\_add, after\_add, before\_remove and  
after\_remove callbacks which you can use with has\_many and  
has\_and\_belongs\_to\_many is that is is only called when adding an  
object to the collection with \<\<. It appears that someone has  
submitted a patch to trac to get it working in other cases though:  
[http://dev.rubyonrails.org/ticket/8854](http://dev.rubyonrails.org/ticket/8854)

Another way with has\_and\_belongs\_to\_many would be to just add the  
roles with a custom method in the User model:

class User \< AR::Base  
has\_and\_belongs\_to\_many :roles

def give\_role( r ) # r would be a string perhaps  
# do your checks here for exclusiveness and return false if  
problem

```
# add the role to users roles if checks pass
roles << r
return true

```

end  
end

What you want leads me to think that using a join model instead (with  
has\_many :through) is the better solution. You use a join model when  
you need more control over the relationship or you need to save  
information about the relationship which would be unique to it.

class User \< AR::Base  
has\_many :assignments  
has\_many :roles, :through =\> :assignments  
end

class Role \< AR::Base  
has\_many :assignments  
has\_many :users, :through =\> :assignments  
end

class Assignment \< AR::Base  
belongs\_to :user  
belongs\_to :role  
before\_save :check\_student\_exclusive

def check\_student\_exclusive  
# do your checks here for exclusiveness and return false if  
problem  
# else return true  
end  
end
