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?
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