User has_many Roles through Organizations

71 views
Skip to first unread message

Trey Dockendorf

unread,
Apr 28, 2012, 5:51:39 PM4/28/12
to cant...@googlegroups.com
I'm currently building a voting application that currently needs a complete redesign of the authorization system, currently CanCan.  This app currently uses ActiveAdmin for the backend, and Devise for authentication on both front/back end.

Right now there are 3 static roles which are given their abilities via CanCan.  The issue is right now there's a HABTM relationship between users and organizations.  Each user is assigned a role via the User model, just a string column "role".  I've now run into a problem where some users are a "voter" for organization A but a "vote_counter" for organization B which my current schema doesn't support.  So I likely need to move to storing the user's role assignment in the organizations_users join table.

I've got through the documentation up to testing that Permits work but have not found how to define roles per-user.  Right now the old schema stores the role in an ActiveRecord store (MySQL).  Looking that the 3 listed Role gems I'm unsure which is the easiest to use while still supporting the necessity to store the user's assigned role in the join table.  I've attempted trole, but the initial tests fail.

I have added this to my index.html.erb that's the front page of my site for testing

<%= Ballot.all.inspect if user_can?(:read, Ballot) %>
<%= link_to "Edit", edit_admin_ballot_path(Ballot.first) if user_can?(:edit, Ballot) %>

However nothing shows.  The controller doesn't print to the logs either, unless the debug_permits_registry is supposed to output else where.

class HomeController < ApplicationController
  def index
    # print permits that were registered correctly
    CanTango.debug_permits_registry

    # print all permits that allow/deny that user to perform that action/ability
    CanTango.debug_ability(current_user, :read, Ballot)
  end
end

I have noticed while dropping to console that lots of CanTango information is given, but unsure what it means.

$ ./script/rails console
BaseMany
strategy module: Troles::Strategy::BaseMany
Registering user permit: user of class UserPermit
#<CanTango::Configuration::HashRegistry:0x00000006083cb0 @default=#<Hashie::Mash user=UserPermit>, @registered=#<Hashie::Mash user=UserPermit>>
Registering role permit: voter of class VoterRolePermit
#<CanTango::Configuration::HashRegistry:0x000000060be8b0 @default=#<Hashie::Mash voter=VoterRolePermit>, @registered=#<Hashie::Mash voter=VoterRolePermit>>
Registering role permit: admin of class AdminRolePermit
#<CanTango::Configuration::HashRegistry:0x000000060be8b0 @default=#<Hashie::Mash admin=AdminRolePermit voter=VoterRolePermit>, @registered=#<Hashie::Mash admin=AdminRolePermit voter=VoterRolePermit>>
Registering role permit: vote_counter of class VoteCounterRolePermit
#<CanTango::Configuration::HashRegistry:0x000000060be8b0 @default=#<Hashie::Mash admin=AdminRolePermit vote_counter=VoteCounterRolePermit voter=VoterRolePermit>, @registered=#<Hashie::Mash admin=AdminRolePermit vote_counter=VoteCounterRolePermit voter=VoterRolePermit>>


Any help is greatly appreciated.

Thanks
- Trey

Kristian Mandrup

unread,
Apr 28, 2012, 7:41:42 PM4/28/12
to cant...@googlegroups.com
Hi Trey,

I recommend the following: use simple_roles as the roles system. Troles used to work just fine (maybe around version 0.5) but the latest releases have some issues and were not completed unfortunately.
Also the "old" cantango gem (v 0.9.4.7?) has a lot of features but can be a bit tricky to setup and started to break down a bit after around 0.9.

I currently recommend that you try out the cantango-permits gem instead. I would love to finish the new cantango gems (currently split into multiple smaller gems), but haven't had time to finish them all. Currently I still need to finish
cantango-permit_store and cantango-cache. However, cantango-permits should work just fine if all you need is the permits system and has almost full spec coverage so you should be able to look at the specs and work from there in case the README and wiki doesn't provide enough info. If you want to use roles, please look at cantango-roles, now an add-on to permits. Yes, the debug capabilities should definitely help give you some info on what is going on behind the scenes and help you configure it correctly.

I would love if some of you guys trying to use cantango would help out finishing the cantango gems. That would benefit us all I'm sure and would make me more determined to finish and polish them and improve documentation etc.

The debug information that you see shows that the Permits are correctly registered with CanTango in order that they are available. If you really want to understand the mechanics, I recommend that you fork cantango and then link directly to your local repo using the

gem 'cantango', :path => 'your path to cantango'

and then debug from there… unfortunately cantango is quite a big framework and it became a bit too much for me to manage in the end on my own… which is why I made a total refactoring, splitting it into smaller parts for easier maintenance and to ensure each part as less tightly coupled and worked as plugins/extensions to the core system. With the new cantango-permits it should be much easier to customize it to your special needs, such as roles on organizations or whatever. Check out the "customizing permits" section. I now made a nice API for this specifically.
Good luck!

Kristian

Trey Dockendorf

unread,
Apr 28, 2012, 9:28:29 PM4/28/12
to cant...@googlegroups.com
Thanks for the info.  I was actually working on using the role_active_record gem but ran into issues with the generators.  Is cantango-permit_store intended to replace that gem?  I have some changes to role_active_record as far as the generators, but if it's being replaced I'll shift my focus for the time being.

I like the recommendation, I'll switch to using the newer gems.  Thanks again

- Trey

Kristian Mandrup

unread,
Apr 29, 2012, 2:28:07 AM4/29/12
to cant...@googlegroups.com
You are more than welcome to improve roles_active_record. This is currently the best option unless you use simple_roles, which only has one strategy. Do you have a pull request for it?

Trey Dockendorf

unread,
Apr 29, 2012, 7:05:08 PM4/29/12
to cant...@googlegroups.com
No pull request yet, but shortly will have one.  So far this is a pretty big refactor of the generator, and I'm only able to thoroughly test the many_roles strategy.

In the generated Role class, there is "extend RoleClass::ClassMethods" which always fails with "uninitialized constant Role::RoleClass".  My guess is because the RoleClass module isn't autoloaded by filename?  Since the file name is 'role.rb'.  Is the only functionality that offers is the listing of valid roles?

Thanks
- Trey

Trey Dockendorf

unread,
Apr 30, 2012, 11:07:28 AM4/30/12
to cant...@googlegroups.com
So I think I've hit sort of a wall on the comprehension of how to do this nested "has_many through".  Basically a User will have many roles and organizations.  So right now I have 4 tables, users, roles, organizations and memberships (JOIN with user_id, organization_id and role_id).

So far this is what I have...but am having issue comprehending how to assign say a role to a user and how to correctly call and assign things through the association.  I also feel like I'm missing something in the Organization and Role models.

# app/models/user.rb
  strategy :many_roles, :role_class => 'Role', :user_roles_class => 'Membership'
  # strategy creates these two relationships
  #  has_many :memberships
  #  has_many :many_roles, :through => :memberships, :source => :roles
  valid_roles_are :admin, :vote_counter, :voter
  has_many :organizations, :through => :memberships

# app/models/role.rb
  has_many :memberships
  has_many :users, :through => :memberships

# app/models/organization.rb
  has_many :memberships
  has_many :users, :through => :memberships

# app/models/membership.rb
  belongs_to :organization
  belongs_to :user
  belongs_to :role
  validates_uniqueness_of :user, :scope => [:role, :organization]


Is this still within the range of capabilities of role_active_record, and if so would you happen to have some examples of how to assign / query the role?

I've tried this for example...while it works I have no idea if that's really how it should be done or how to implement that in my controllers from form submissions

1.9.2-p290 :068 > user = User.find(2)
  User Load (0.5ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 2 LIMIT 1
 => #<User id: 2, fullname: "Trey Dockendorf", username: "treydock", role: "admin", email: "trey...@gmail.com", encrypted_password: "", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 25, current_sign_in_at: "2012-04-27 16:11:42", last_sign_in_at: "2012-04-20 19:34:36", current_sign_in_ip: "165.91.11.204", last_sign_in_ip: "128.194.198.102", created_at: "2012-02-06 03:33:12", updated_at: "2012-04-27 16:11:42"> 
1.9.2-p290 :069 > membership = user.memberships.find(:first, :conditions => ['organization_id = ?', 3])
  Membership Load (0.2ms)  SELECT `memberships`.* FROM `memberships` WHERE `memberships`.`user_id` = 2 AND (organization_id = 3) LIMIT 1
 => #<Membership organization_id: 3, user_id: 2, role_id: nil> 
1.9.2-p290 :070 > membership.role
 => nil 
1.9.2-p290 :071 > admin_role = Role.find_by_name('admin')
  Role Load (0.5ms)  SELECT `roles`.* FROM `roles` WHERE `roles`.`name` = 'admin' LIMIT 1
 => #<Role id: 1, name: "admin", created_at: "2012-04-30 00:26:05", updated_at: "2012-04-30 00:26:05"> 
1.9.2-p290 :072 > membership.role = admin_role
 => #<Role id: 1, name: "admin", created_at: "2012-04-30 00:26:05", updated_at: "2012-04-30 00:26:05"> 
1.9.2-p290 :073 > membership.role.save!
   (0.3ms)  BEGIN
   (13.4ms)  SELECT 1 FROM `roles` WHERE (`roles`.`name` = BINARY 'admin' AND `roles`.`id` != 1) LIMIT 1
   (0.1ms)  COMMIT
 => true 
1.9.2-p290 :074 > membership
 => #<Membership organization_id: 3, user_id: 2, role_id: 1> 



Thanks
- Trey

Kristian Mandrup

unread,
Apr 30, 2012, 12:34:57 PM4/30/12
to cant...@googlegroups.com
Which is why I personally try never to use Relational Databases. It quickly gets too complex with all these relations :P
Why not use Mongoid?

Otherwise your models look just fine from that I can see. I would start very simple with two models, test them and make sure they work as they should, then gradually build from there one step at a time. Otherwise you will quickly hit a wall with the complexity.
Also I would recommend a simple role strategy to start with such as :many_strings or similar. Look in the specs of role_active_record to see how to query and assign roles.
Here is an overview of the API:


@admin_user.has_roles?(:admin)
@guest_user.roles = :admin, :guest
@admin_user.add_roles :editor, :guest
@guest_user.exchange_role :guest, :with => :admin
There is currently no built-in support to fx find all Users with a given role or set of roles, but should be easy to add using these helpers

def self.having_role role = :admin
  User.select {|user| user.has_role? role }
end

User.having_role? :guest

class Array
  def having_role role = :admin
    self.select {|user| user.has_role? role }
  end
end


User.where(:organization.name => 'big bad corp').all.having_role? :guest

Something like that ;)

Kris
Reply all
Reply to author
Forward
0 new messages