Roles that are User<>Project Based

Hello... Currently I'm using Devise & CanCan which allows me to create Users with Roles using a (Roles_Users) table.

That's nice, but what I want is to Have Projects in my app and for each project for a user to possibly have a role like (Admin, Viewer, etc)

What kind of Model would work for this?

Models Users has_many: projects

Projects ?

Roles ?

Users_Roles_Projects (user_id, project_id, role_id)

What do you think? I'm a newbie and could use the understanding and thinking from you fine experienced folks. Thanks!

with cancan, in your ability model

can :manage , Proyect do |proyect| proyect.user = current_user end

with this only the proyect’s owner is an admin of the proyect

and i suggest you create model with the role model gem, there really is no need for a role table.

Thanks for the reply. In the example above I'm not sure where the Admin variable is located? Is that in the User's table as a boolean?

The reason for a roles table is that there will be multiple roles (Admin, Member, Viewer, Guest) etc...

I tried finding the role model gem but it didn't come up in a Google Search and I'm curious to learn more, do you have a URL?

Thanks

http://github.com/martinrehfeld/role_model

Enjoy!

Daniel Gaytán

role model is a gem that lets you add roles to the user as a method and saves the role into a field on the user table as a bitmask that is , lets say you have and array like this

ROLES = %w[admin moderator author banned]

this gord in your user model and you have a field in the user table called roles_mask

as you see you can map the roles in the array as a series of bit

[admin moderator author banned]

  0 0 0 0

if a user is admin the in his roles_mask field is this

  1000
in binary which translate to 8 in decimal format, if the user is

admin and moderator the value is

1100

and that is a 12

As you can see there is no reason to have a roles table in the proyect unless you have
like 20 or more roles.

role model saves the roles with a virtual attribute, roles that is in you user model and is looks like this

def roles=(roles)
  self.roles_mask = (roles & ROLES).map { |r| 2**ROLES.index(r) }.sum

end

as you can see it maps the array in ROLES to a binary array and saves it in the roles_mask field of the users table

to read a role it does this

def roles

  ROLES.reject do |r|
    ((roles_mask || 0) & 2**ROLES.index(r)).zero?

  end
end

this returns an array of strings when ever it finds a 1 in the binary representation of the string array that is saved on the
roles_mask field

Guys, I appreciate the replies but I'm not trying to assign roles to users.

Users can join projects, and users have roles per project

Examples: User X belong to Project A with an Admin Role User X belong to Project B with an Guest Role User Y belong to Project B with an Observer Role

Does that help clear out why I can't use a bitmask on the user's table?

I understand what you mean, but can't suggest a solution that I've done myself (as I've tended to work around the flat, "user has a single role" model). But take a look at Redmine, as it has the same model that you're after (where a user can be an administrator on one project, but a contributor on another, while having no permissions on a third), and the source code could give you some ideas.

nobosh wrote:

Guys, I appreciate the replies but I'm not trying to assign roles to users.

Users can join projects, and users have roles per project

Examples: User X belong to Project A with an Admin Role User X belong to Project B with an Guest Role User Y belong to Project B with an Observer Role

Does that help clear out why I can't use a bitmask on the user's table?

What I think I would do for this would be along the lines of

class User < AR::B   has_many :permissions   has_many :projects, :through => :permissions end

class Project < AR::B   has_many :permissions   has_many :users, :through => :permissions end

class Role < AR::B   has_many :permissions end

class Permission < AR::B   belongs_to :user   belongs_to :project   belongs_to :role end

...so that Permission is at the center of a Y-shaped relationship. Does that help?

Best,

radhames brito wrote:

role model is a gem that lets you add roles to the user as a method and saves the role into a field on the user table as a bitmask that is , lets say you have and array like this

ROLES = %w[admin moderator author banned]

this gord in your user model and you have a field in the user table called roles_mask

[...]

Wow, what a bad idea. Remind me never to use that gem if that's really the way it works. Storing multiple values in one DB field (which is essentially what the bitmask is doing) is generally not a good thing.

Best,

Hi Marnen, thanks I think that's great. The question is now will CanCan Support that? Are you familiar with CanCan?

Something like... (ability.rb)   def initialize(user)     user ||= User.new     if user.role? :super_admin . .   end

user.rb   def role?(role)     return !!self.permission.find_by_name(role.to_s.camelize)   end

? That's the piece I can't put together.

thxs!

Please quote when replying; otherwise, the discussion is hard to follow.

nobosh wrote:

Hi Marnen, thanks I think that's great. The question is now will CanCan Support that?

I have no idea.

Are you familiar with CanCan?

No. I've generally used rails_authorization or rolled my own permission system.

Best.

Why ? i dont see a problem in the case of roles

Marnen, in your example models above, permissions is the name of the table correct? that I create?

I ended up calling it: projects_users_roles

class ProjectsHaveAndBelongToManyUsersWithRoles < ActiveRecord::Migration   def self.up     create_table :projects_users_roles, :id => false do |t|       t.references :project, :user, :role     end   end

  def self.down     drop_table :projects_users_roles   end end

Just want to make sure I'm understanding how to set something like this up in Rails. Thxs

oh is very easy with cancan and the wiki has eveything you need

i recommend making the “is()” function that says the wiki

there are 2 easy ways to set a permission with cancan

one is the general way

if user.is(:guest) can :read, Article

this means that gues user can read articles, all of them in general

the specific way is this

can :edit, Article do |article| if article.owner == user end

this means that user is allowed to edit if he is the owner

in case of your proyects you can do this

can :manage, Proyect do | proyect| if proyect.user == current_user end if current_user.is(:guest) can :read, Proyect

or

if currenct.is(:guest) can :read, Proyect do | proyect| if proyect.public == true end

nobosh wrote:

Marnen, in your example models above, permissions is the name of the table correct? that I create?

Yes.

I ended up calling it: projects_users_roles

That's not a good name. If your table needs a model (as here), then you should take the time to find a descriptive name for it -- which is why I used Permission.

Best,

Thanks Marnen, that's the kind of info I needed as a newbie. I ended up rolling back and calling it: ProjectPermissions

The last high-level puzzle piece is in CanCan where it determines what permission is granted, with the bitmask method it looks like:

if user.admin_for_project?(project)   can :manage, :all else   can :read, :all end

Given the Y relationship we created, in Rails how to you put that into the IF statement above?

Better way to phrase the question... Given the model above,

Knowing the project_id and user_id, how in Rails using activemodel can you query to obtain the user's role if any, meaning return admin or nil

I'm trying things like in the users.rb: logger.debug self.permissions.find_by_project_id(1)

But that doesn't give back a role... Tricky stuff!!!

nobosh wrote:

Better way to phrase the question... Given the model above,

Knowing the project_id and user_id, how in Rails using activemodel can you query to obtain the user's role if any, meaning return admin or nil

I'm trying things like in the users.rb: logger.debug self.permissions.find_by_project_id(1)

But that doesn't give back a role...

Of course it doesn't. You asked it for a Permission object, and that's what you're getting. You want the Role that that Permission belongs to. So...can you figure it out now?

Tricky stuff!!!

No, actually, this is very simple. If you find it tricky, you need to think more clearly about the structure of your associations and queries.

Best,

user.role_for_project(project) seems to be working nicely. Hopefully that's the right Rails way to do it.

or permissions.find_by_project_id(1).role.name