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