I've modified my devise table, User, to have a clearance column. This column is a number (1-3) that represents a user's permissions (read, read/write, full control). Unlike all the examples I'm reading, my Clearance (Role in the examples) is not a separate table with a relationship but is its own column in the Devise table (User). Its default value is 1.
My ability.rb looks like this:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.clearance.equal?("2")
can :create, Post
can :manage, Post, :user_id => user.id
else
can :read, :all
end
end
end
I've written this off of other examples I've seen (I'm really new to Rails but trying to not ask for help unless absolutely necessary) with the intent that users with a clearance value of 2 can create posts and manage only their posts. I've also included that, because I haven't written the code for 1 and 3 yet, that all other clearance numbers can read everything.
Apperantly, however, Cancan thinks that my user (confirmed clearance level of 2) falls under the "else" provision and therefore I can only read posts. I get a You are not authorized to access this page message if I try to make a new one.
I'm lost. Help?
May this be that the clearance column is an integer? in such a case you should write user.clearance == 2 and not as you wrote.
Related
I'm making a Rails 6 application where I'm using Devise for authentication, Pundit for authorization and I added Active-Admin because I need a dashboard where admin users manage the content of the app.
Other than admin, I have a couple of more roles president, manager, guest. An admin can be president or manager.
I'm little confuse on what to use to implement the roles, with devise? pundit? I do it by hand?
Is it better to unite the User and the AdminUser model active-admin created? Because this way UserAdmin users can't log in to the application, only to the dashboard and that is not what I want.
I have seen tutorials where people add an admin:boolean column to the users, should I do something like that?
Is it better to unite the User and the AdminUser model active-admin created? Because this way UserAdmin users can't log in to the application, only to the dashboard and that is not what I want.
That depends more on your business logic. It may be a good idea to keep your users and admin_users tables separated; The users table will probably need to have a lot of associations with other tables, that will not be necessarily needed by admin_users, right?
I'm little confuse on what to use to implement the roles, with devise? pundit? I do it by hand?
You may define a role column in your admin_users table, and use that column in pundit policies, for example:
class ResourcePolicy
# ...
# ...
# ...
def update?
user.admin? || user.president?
end
end
in AdminUser, you can do the following:
class AdminUser < ActiveRecord::Base
def admin?
role == 'admin'
end
def president?
role == 'president'
end
end
There are many other ways to implement that, and they all depend on what you need to achieve.
Let's say I have model User.
I want to #user to be 3 types : admin, regular and pro.
If I create column type:String, of course I can pass string, when creating new #user, 'admin'/'regular'/'pro'.
And then each time I operate with #user check something like
if #user.type == 'admin'
for my purposes.
But I feel that this is not how it is made by professional developers. (Am I right here?)
I want User model to have column which can only contain 3 specific values and not just any string.
Use an enum
The column should be in integer,with a default value (probably 0 in the following example). And in your model define it as:
enum type: { regular: 0, pro: 1, admin: 2 }
for example.
Now you automatically have these methods:
#user.regular?
#user.pro?
#user.admin?
Also, you can call #user.type and you will get a nice string representation:
> #user.type
=> "admin"
You can also query with a symbol like:
User.where(type: [:pro, :regular])
or
User.where.not(type: :admin)
etc.
This approach also works perfectly well with the CanCan gem.
If you want to assign roles in your application , you can simply use the Role-Based specific gems like Cancan and Rollify etc. The link to which is here. I will explain below how easy it would be to assign roles to users and yes how the professional developers do it.
In your User model , there will be a method as :
ROLES = %w[admin regular pro]
def role?(base_role)
ROLES.index(base_role.to_s) <= ROLES.index(role)
end
Now in your UsersController you can check whether :
if user.role? :admin
can :manage, Post
end
if user.role? :regular
can :manage, ForumThread
end
if user.role? :pro
can :manage, Forum
end
It's that easy ..!!!.
I hope this helped ..!!! :)
I have a rails application where I have set up a table: users another table: roles and finally a join table: user_roles where a user may have many roles, but a role belong_to_and_has_many :users
This has allowed me to create new roles and then, assuming thee user is an admin, on the user edit page, switch their role.
This is great, how ever currently no role has capabilities. What I was thinking was doing:
role_permissions table
permissions: has_and_belongs_to_many :roles
Setting up a set of checkboxes on the roles edit page to assign a set of capabilities
to said role, that can then be applied to said user, that can then be used by capybara to determine if a user has the appropriate action or not.
While you can create roles, you cannot create capabillities. so you would have a predetermined list of capabilities. Also some roles, such as administrator or member could not be destroyed or edited. (already done.)
I can set up the table and the relationship to do this, what I cannot figure out how to do is to integrate this concept with cancan. Because can can does something like:
can? :destroy #project
If I assign, say:
Role: Editor (String name)
Capabilities: Read, Write, Destroy, Update, Preview (These are just string names)
How could I then say:
can? user.role? Editor read Post - seudo code.
First of all, for capabilities, if it's a fixed list of capabilities you're working with, you're probably better off with having a number of booleans on the roles table, e.g. can_create_projects, can_create_users, etc., which encode the abilities of each role.
Then your CanCan Ability class might have something like the following,
class Ability
include CanCan::Ability
def initialize(user)
can(:create, Project) do |project|
user.roles.any?(&:can_create_projects)
end
end
end
I`m looking to create two models: trainer & client.
When signing up those two types of models share the basic auth info, such as email & password.
Thus I would like to use Sorcery to do the authentication for me, the gem creates a User model by default.
Searching through StackOverflow I understand I could use Single Table Inheritance, which most people find problematic.
Is there a better/simpler solution for those two types of users to share the basic auth info but be separate models which would contain their role specific data?
I`m sorry if I mixed things up.
What kind of "role specific data" do your two users have?
I was in a very similar situation as you are in an app that I'm still developing. I chose to use a role based approach using CanCan.
class User < ActiveRecord::Base
has_one :client_profile # or whatever a client has here
has_one :trainer_profile # or whatever a trainer has here
end
Then, you would define your abilities
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # anonymous user
if user.is? :trainer
can :create, TrainerProfile
# some other trainer specific roles here, like editing his/her profile
elseif user.is? :client
can :create, ClientProfile
# some other client specific roles here, like editing his/her profile
end
end
end
Of course, the above code assumes an is? method on the User class to check the user role.
More info on CanCan can be found on the CanCan wiki, and also the Railscast on CanCan.
I want to make a record management system. The system will have 4 different user roles: Admin, Viewer, Editor and Reviewer.
While the first two are easy to implement using gems such as cancan and declarative authorization, the other two are not so simple.
Basically each new record is created by an Admin (only an Admin can create new records), and should have its own separate Editor and Reviewer roles. That is, a user can be assigned many different roles on different records but not others, so a user might be assigned Editor roles for Record A and C but not B etc.
Editor: can make changes to the record, and will have access to specific methods in the controller such as edit etc.
Reviewer: will be able to review (view the changes) made to the record and either approve it or submit comments and reject.
Viewer: Can only view the most recent approved version of each record.
Are there any ways of handling such record-specific user roles?
This can be accomplished without too much effort with the cancan gem and a block condition. A block condition checks for authorization against an instance. Assuming your Record class had an editors method that returns an array of authorized editors the cancan ability for updating a Record might look something like this:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
...
can :update, Record do |record|
record.editors.include?(user)
end
...
end
end
See "Block Conditions" on the CanCan wiki:
https://github.com/ryanb/cancan/wiki/Defining-Abilities
Update
Storing which users have which access to which records could be done many ways depending on your specific needs. One way might be to create a model like this to store role assignments:
class UserRecordRoles < ActiveRecord::Base
# Has three fields: role, user_id, record_id
attr_accessible :role, :user_id, :record_id
belongs_to :user_id
belongs_to :record_id
end
Now create a has_many association in the User and Record models so that all role assignments can be easily queried. An editors method might look like this:
class Record < ActiveRecord::Base
...
has_many :user_record_roles
def editors
# This is rather messy and requires lot's of DB calls...
user_record_roles.where(:role => 'editor').collect {|a| a.user}
# This would be a single DB call but I'm not sure this would work. Maybe someone else can chime in? Would look cleaner with a scope probably.
User.joins(:user_record_roles).where('user_record_roles.role = ?' => 'editor')
end
...
end
Of course there are many many ways to do this and it varies wildly depending on your needs. The idea is that CanCan can talk to your model when determining authorization which means any logic you can dream up can be represented. Hope this helps!