Set Devise Role By Route - ruby-on-rails

My app has 3 roles for users: member, handicapper, admin
I'm trying to set the role by the route, example:
get '/members/sign_up' => 'devise/registrations#create', :role => 'member'
Then in my controller, which I use to override devise, I do something like this:
def create
super do
resource.role = params[:role]
resource.save
end
end
Please give some direction as to what I'm doing wrong, or what I else I need to be doing.
Also, I currently don't have a way of checking whether that parameter is even being passed to the action, so if you could suggest ways to do that, it would be greatly appreciated.
Note: I have gone through several tutorials and documentation pages, as well as dozens of SO questions, but have yet to find anything this specific.
Thanks in advance.
EDIT: I should add I have already added the field to the migration file, and ran rake db:migrate.
EDIT 2: User.last in rails console shows role: nil

For the role column in the users table I would suggest using enum.
add_column :users, :role, :integer, default: 0
Then in your user model add:
enum role: {member: 0, handicapper:1, admin: 2}
Now by default a user will always have a role of member. Don't set a users role via a route - that sounds like a security hole.
If your letting users self register, let them do that and they will be automatically set to member, and then in an admin area you can set their role to something else if need be.

Related

limit user only to specific data

I have three types of users "Admin," "Manager," and "Employee." Also, I have an accounts model. All of them can show these accounts. I want to limit employees' access to these accounts. I put the Ids of these accounts in an array and did a limit like the code below. My question is, I had 15 endpoints related to the accounts, Is the best way to do it like this, or may there be a solution that does it without editing all these endpoints?
def index
if #current_user.user_type == 'Admin'
#accounts = Accounts.all
#accounts = optional_paginate(#accounts)
elsif #current_user.user_type == 'Employee'
#accounts = Account.where(id: ACCOUNTS_IDS)
#accounts = optional_paginate(#business_accounts)
else
#business_accounts = optional_paginate(Account.all.includes(:account_managers).exclude_pending)
end
end
The ACCOUNTS_IDS is an array of accounts ids that the employees can access to them only.
Your strategy works, but I think you're sensing that it's not ideal.
You had to hard-code an array of Account IDs from your database, which is never a good idea. If you ever needed to migrate or seed or recreate the database, those IDs could change.
You have this code repeated 15 times(so it's not DRY)
If an account with an id in ACCOUNTS_IDS is ever deleted, imagine what will happen to each of these controller actions.
Adding to or removing from the accounts (ACCOUNTS_IDS) that Employees can see requires you to change your codebase and push those changes.
I'd suggest doing a few things differently:
1. Use an authorization gem
I personally love pundit, but there are other popular options
2. Don't hard-code database information
As mentioned before, the IDs can change and code that relies on a specific record having a specific ID (or relying on that specific record to exist) is brittle.
It's much better to add a column to your table to control this.
Here's an example migration:
add_column :accounts, :restricted, :boolean, default: true, null: false
ACCOUNTS_IDS = [1, 2, 3, 4] #just an e.g.
Account.where(id: ACCOUNTS_IDS).update_column(restricted: false)
:restricted could also be something easier to understand, e.g. :visible_to_employees
Now you can make scopes in your Account model:
# /models/account.rb
class Account < ApplicationRecord
scope :restricted, -> { where(restricted: true) }
scope :unrestricted, -> { where(restricted: false) }
...
end
This allows you to do Account.all to get everything, Account.restricted to get some, and Account.unrestricted to get the others.
This also allows you to add a view form where accounts could be made visible to Employees without having to change the code of your application.
3. Do this in the model, not in the controller
Lean towards having more code in your models and less code in your controllers.
(this will also help you when you implement a Pundit scope)
# models/user.rb
class User < ApplicationRecord
...
def authorized_accounts
case user_type
when 'Admin'
Account.all
when 'Employee'
Account.unrestricted
else
Account.all.includes(:account_managers).exclude_pending
end
end
Now your controller can be greatly simplified:
def index
#accounts = optional_paginate(#current_user.authorized_accounts)
end

manage roles in rails

I want create roles in my project. Each user can be: admin, registered or demo. Each role see different things.
How can I do that? What is the best gem to do roles?
This is a example in 'bad programming" of what I want:
def index
if current_user.role[:name] == 'admin'
#installations = Installation.all
elsif current_user.role[:name] == 'registered'
#installations = current_user.installations
elsif current_user.role[:name] == 'demo'
#installations = current_user.installations.first
else
end
end
Some gems that might be interesting for you :
rolify
role_model
If you decide to implement it yourself, then within some page you might want to change the content, for that you might want to do something like this :
Add a role to the user model using a migration :
class AddRoleToUsers < ActiveRecord::Migration
def change
add_column :users, :role, :string, default: :demo
end
end
Then in your app you can use it as follows:
def index
case current_user.role
when :admin
#installations = Installation.all
when :registered
#installations = current_user.installations
else
#installations = current_user.installations.first
end
end
You can also simply create a boolean admin for instance.
What you might want to do also is create some methods in your model so that you can call current_user.admin? or current_user.registered? . You can do that by doing (if you chose to use a string to store the role):
class User < ActiveRecord::Base
def admin?
self.role == "admin"
end
def registered?
self.role == "registered"
end
end
One advantage I see of having a role stored in a string is that if you have 5 roles for instance then you do not have 4 booleans (as when you store admin in a boolean) but only one string. On the long run you might want to store actually a role_id instead of a string and have a separate role model.
An excellent alternative pointed out by Jorge de Los Santos (another answer) is to use enum :
class User < ActiveRecord::Base
enum role: [:demo, :admin, :registered]
end
It is an excellent alternative because it will automagically add the methods described above such as current_user.admin? without hard coding them.
With your roles, you might want to do some authorization (admins can have access to specific pages, demo users are restricted to only a subset of pages, etc.). For this, you can use the gem called cancancan. You can look at this railscast to learn more about it. Also, you can have some infos here : How to use cancancan? .
There are plenty of solutions available to you.
Starting by gems:
https://github.com/RolifyCommunity/rolify
https://github.com/martinrehfeld/role_model
By using Devise architecture (in case you use it):
https://github.com/plataformatec/devise/wiki/How-To:-Add-a-default-role-to-a-User
By using enums in rails 4:
class AddRolesToUser < ActiveRecord::Migration
#add_column 'role', :integer, default: 0 to the users table
end
class User < ActiveRecord::Base
enum role: [:demo, :admin, :registered]
end
That will enable role methods.
user = User.find(1)
user.role #:demo
user.admin? #false
user.registered? #false
And consequently:
if user.admin?
#somethig
elsif user.registered?
#another something
else
#another another something.
And last but not least, what you are searching is not the manage roles solution, is the manage permissions solutions:
https://github.com/ryanb/cancan
Add a boolean, :admin to your User model.
class AddAdminToUsers < ActiveRecord::Migration
def change
add_column :users, :admin, :boolean, deafult: false
end
end
Create a method for a registered user to separate them from demo users, such as verifying their email, providing a home address and phone number, filling out a profile, etc. This is up to you though, first you need to decide how a registered and demo user should be different.
The CanCan gem adds authorization to your project, and is especially useful if you want to implement multiple roles with differing abilities. When used with an authentication system like devise, you get a full suite of capability for your site.
You're in full control of what roles you want to define and what abilities they have. CanCan manages tracking, assignment, and querying of roles, and then gets out of your way to let you build what you need.
You can find the CanCan gem in Github: https://github.com/ryanb/cancan
It's simple to use, and the documentation is straightforward and easy to follow.

cancan how can we create roles dropdown in ROR

I am using cancan for role management in my app. I have different roles in my app like Super Admin, Admin, developer, tester. How can I display roles dropdown from Roles array without including Super Admin and Admin. Please look at my code
class User < ActiveRecord::Base
Roles = [:super_admin, :admin, :developer, :tester]
end
I need a select box containing developer and tester, and tried to create a array using following code but null item is getting in dropdown.
User::Roles.map{|r|
next if %w(super_admin admin).include?(r.to_s)
r.to_s.humanize
}
Please help
A quick one-liner:
(User::Roles - [:super_admin, :admin]).map { |r| r.to_s.humanize }
roles = User::Roles.dup
roles.delete :super_admin
roles.delete :admin
The roles array will have all roles except super_admin and admin
[EDIT]
Actually I think this way is better
ADMIN_ROLES = [:admin, :super_admin]
roles = User::Roles.select {|r| !ADMIN_ROLES.include? r}

Devise add authentication to creating a role

I'm using devise and cancan as an authentication solution.
For devise I have added a role attribute, and created a constant ROLES:
#Migration for adding roles
class AddRoleToUsers < ActiveRecord::Migration
def change
add_column :users, :role, :string
end
end
#Users.rb
ROLES = %w[user staff]
I want to add a function to the signup page, such that for a person to create a user with the role "staff" he has to enter a secret key as well (probably like a secret code like "staffsecretkey" in a input text box)
Anyone know of a way?
Thanks in advance.
You'll have to do two things:
1) Override devise controllers
2) Customise your views
Check devise wiki in github. You should be able to find all the info you need right there. I'm answering you from my BlackBerry so I could no give you more details right now. But if you can't solve this with the answer I gave you. I'll give you a hand later.

Rails Enumerated Types or Alternatives

I'm just learning ruby on rails and I have a table of user roles (Owner, Admin, and User). There are going to be places in the code where I need to check the user's role and show different options. Does anyone know how to do this without resorting to magic numbers or other ugly methods?
In ASP.Net web apps I've worked on I've seen this done through the use of enumerated types:
public enum UserRole { Owner = 1, Admin = 2, User = 3 }
// ...
if (user.Role == UserRole.Admin)
// Show special admin options
Each different role in the database is reflected as an enumerated type with a value set to the ID of that role in the database. That doesn't seem like a very good solution because it depends on knowledge of database that may change. Even if it is the proper way to handle something like this, I don't know how to use enumerated types in rails.
I would appreciate any insight into this matter.
Ruby itself does not have an enumerated type, but this site shows a method http://www.rubyfleebie.com/enumerations-and-ruby/
You could make something like this in your User model:
#constants
OWNER = 1
ADMIN = 2
USER = 3
def is_owner?
self.role == OWNER
end
def is_admin?
self.role == ADMIN
end
def is_user?
self.role == USER
end
Could the functionality added in Rails 4.1, be what you are looking for ?
http://coherence.io/blog/2013/12/17/whats-new-in-rails-4-1.html
Copy from blog post:
class Bug < ActiveRecord::Base
# Relevant schema change looks like this:
#
# create_table :bugs do |t|
# t.column :status, :integer, default: 0 # defaults to the first value (i.e. :unverified)
# end
enum status: [ :unverified, :confirmed, :assigned, :in_progress, :resolved, :rejected, :reopened ]
...
Bug.resolved # => a scope to find all resolved bugs
bug.resolved? # => check if bug has the status resolved
bug.resolved! # => update! the bug with status set to resolved
bug.status # => a string describing the bug's status
bug.status = :resolved # => set the bug's status to resolved
This seems like a really good case for using my classy_enum gem. It essentially allows you to define a fixed set of options, where each one is a class with behavior and properties specific to it. It helps cut down on all the conditional logic that tends to get scattered throughout the application.
For example, If you are doing things like this:
class User < ActiveRecord::Base
def options
if user.is_admin?
[...admin options...]
else
[...non admin options...]
end
end
end
Then calling as: user.options somewhere else...
classy_enum allows you to move that logic to a separate place and have the same functionality with no conditional logic:
class User < ActiveRecord::Base
classy_enum_attr :role
delegate :options, :to => :role
end
The README has a working example and describes the gem in detail.
I prefer to use the aptly named Authorization plugin for situations like this.
This will let you
permit "role"
to restrict access to roles, and
permit? "role"
to simply test for access. Both of these delegate to User#has_role?(role).
Don't feel like you have to use their ObjectRoles implementation. You can use the Hardwired roles and then implement your own User#has_role?(role) method to use your existing schema.
Just starting to learn Rails (from C#), and had this exact same question. It seems that Rails doesn't really have enums because the philosophy is different. I would use tons of enums to try to organize all the details in a C# project, but maybe since Rails handles so much for you they aren't that important. It's not really an answer, just an observation.
There's a enum plugin on rubyforge so you do:
t.column :severity, :enum, :limit => [:low, :medium, :high, :critical]
It's pretty ugly to use :limit attribute to pass parameters, but it's a more standardized way.
To install just do:
script/plugin install svn://rubyforge.org/var/svn/enum-column/plugins/enum-column
it currenctly works with Rails 2.2.2 or later.
Rubyforge link: www.rubyforge.org/projects/enum-column/

Resources