Enforce mutual exclusivity

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.

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 ?

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. [email protected]

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. [email protected]

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. :slight_smile:

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

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

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