Workflow for admin/user accounts creation in Rails - ruby-on-rails

I'm building an event registration system in Ruby on Rails. I'll need admin users as well as normal users. Is there any best practice for creating the admin users manually and not letting any random person "Sign Up" as an admin? Also, is there any way to prevent signups in general?
(I'm thinking about using the Devise gem)

Option #1
The simplest way I've found, for most scenarios, is to simply add an admin (boolean) attribute to the user model (I usually use Devise, but this applies to any User model):
# db migration
class AddAdminToUsers < ActiveRecord::Migration
def change
add_column :users, :admin, :boolean, default: false
end
end
Then, thanks to ActiveRecord, you'll automagically have an admin? method on the User model:
<% if current_user.admin? %>
<%= # super-secret admin-only option %>
<% end %>
Then, your signup pages will be the same as they were, and new users will silently default to admin = false. Then, you can implement "promoting" Users to admin = true however you would like...such as in an Admin::UsersController#edit view in an admin area of your app.
Option #2
If, however, you need the admin users to have their own views/routes/logic/etc, it might be worth separating them into their own Admin model (devise supports this as well). This would give you two separate models, each with their own routes, views, and controllers. One downside is that promoting a User to an Admin would mean actually moving the record from one table to the other. This option has a lot of overhead, but can end up being much cleaner if you need separate logic.
Which pattern you use will ultimately depend on your own scenario. If you need separate views/routes/logic for managing admin users, then choose #2. If you can get away with just checking for an admin attribute, then #1 is much simpler.
Devise illustrates both patterns in more detail here.

Related

Who handle the roles of a User in Rails?

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.

How should I structure two types of Roles in a Rails application?

I am working on a Ruby on Rails application that has two kinds of "Roles".
One will be where a user has multiple roles, such as "Admin", "Team Lead", etc. these will be defined in the seeds file and not generated by the user.
The other will be generated by a User and assigned to other users, such as "Chef", "Waiter", etc.
They are both similar in that they only have a name column. The former will be used with an authorization tool such as CanCanCan. I currently plan to allow users and roles to have many of the other using a has_many :through relationship.
class Role
has_many :user_roles
has_many :users, through: :user_roles
end
class User
has_many :user_roles
has_many :roles, through: :user_roles
end
class UserRole
belongs_to :user
belongs_to :role
end
My questions are:
Should the latter ("Chef", "Waiter", etc) be put in the same table? Separate tables?
Should I use some kind of inheritance?
What's a good practice for this situation?
I plan to use the term "Role" in the user interface for the latter, showing what "Roles" a user has. The former I guess is more about what privileges they have within the application.
If you go away from the technical side of roles and authentication, and try to describe the "things" from a more business oriented approach you make the distinction clearer for yourself.
What I understand is: You have a definition for a user of your application that is used to describe what authorization this user has, e.g. an "admin" has more rights than an "editor" or "community manager".
You also want these users of your application to be able to create names that are associated with (other?) users. Theses names have nothing to do with authorization, as I understood.
Maybe these names are more like tags, that people can assign?
I would keep both separated, as it shouldn't be able for a user to create a role, or modify existing roles, that could grant them access to admin features.
If you want to look at a tagging gem, I could recommend this one: https://github.com/mbleigh/acts-as-taggable-on I used this for several years and while it has its drawbacks, it's reliable.
I'd suggest having a look at rolify before rolling your own solution, as it will have solved some things that you'll have to reimplement / discover later (e.g. role queries, avoiding N+1 queries etc). It also integrates well with can?, and should work well for the authorisation part of your question.
Whilst it's not impossible to allow users to create new roles (by throwing an input form on top of Role.create), this starts to get messy, as you need to track which ones are for authorisation and which ones informative (and user created).
Since the two groups of things are for different purposes, I wholeheartedly agree with this other answer that it's cleaner to separate the user-generated entities, and look to implement them as tags. You may display all the "roles" together in certain views, but that doesn't mean that it makes sense to store them within a single table.
Side-note: if you do end up rolling your own solution, consider using HABTM here. The join table will still be created, but you won't have to manage the join table model. E.g.
has_and_belongs_to_many :users, join_table: :users_roles
Since you only have a limited number of roles, you could use a bitmask and store directly on the user model as a simple integer.
See this Railscasts for more information. That would be the most efficient, database and association wise, way to do this although perhaps not the simplest to understand. The only real restriction is that you can't alter the array of values you check against, only append to it.
Good luck!
I would create one more model, Permission, where you can create a list of all the permissions you want to manage under any given role.
Then have a many to many between Permissions and Roles.
From a UserRole instance then you will be able to list the permissions, and in the future you could add additional permissions to roles buy just running inserts in a couple of tables
Roles: Onwer, Chef, Cook, Waiter
Permission: can_cook, can_buy_wine, can_manage, can_charge_custome
Owner: can_manage, can_buy_wine, can_charge_customer
Chef: can_cook, can_manage
Waiter: can_charge_customer
Is would be a good start and you can evolve the role functionality to whatever your needs are without an external dependency.
Also, You can go just using Users table and adding role column as integer and give them a role code in default 0 integer.
#app/helpers/roles_helper.rb
module RolesHelper
#roles = {
'Default' => 0,
'Waiter' => 10,
'Chef' => 20,
'Superadmin' => 30
}
class << self
def list_roles
#roles.map{|k,v| [k,v] }
end
def code(str)
return #roles[str]
end
def value(id)
return #roles.key(id)
end
end
def require_default_users
unless current_user && current_user.role >= RolesHelper.code('Waiter')
redirect_to root_url(host: request.domain)
end
end
def require_superadmin_users
unless current_user && current_user.role >= RolesHelper.code('Superadmin')
redirect_to courses_path
end
end
end
access in controllers
sample:
class Admin::AdminController < ApplicationController
include RolesHelper
def sample_action_method
require_default_users #if non admin user redirect ...
puts "All Roles: #{RolesHelper.list_roles}"
puts "Value: #{RolesHelper.value(30)}"
puts "Code: #{RolesHelper.code('Superuser')}"
end
end

Rails 4 - coding through models

I have a user model, with a separate profile model. Each user has a profile. I then have 8 models for things within a profile (for example, each profile has a dashboard, feedback and publications). The profile belongs_to user and the dashboard etc belongs_to profile.
I am creating a profile view and would like to know how I write the line of code that will collate relevant information from the other models to display in the profile.
For example,the profile will be displayed with the name of its owner (which is stored in the user model). It will also have feedback stored in the feedback model. Is there a way to write that the profile view should display the user.first_name user.last_name, and user.feedback?
You can chain calls through Profile, like so:
#profile.user.first_name
But this violates a principle known as the “Law of Demeter”. There's a complex definition, but suffice to say that when you are accessing one object (User) through another (Profile), you begin to violate this law. It's not a huge deal when you're accessing user properties through the profile, necessarily, but things get messy quickly:
#dashboard.profile.feedback.order(:rating).where(user: #dashboard.profile.user)
Gross. And brittle, too. When you need to compose multiple models into a single view, there's a better pattern known as a Decorator. The job of a decorator is to give you a single object that appropriately collects data from the models for presentation, without tying your view code directly to your models. For example:
class DashboardDecorator
def initialize(dashboard, profile, user)
#dashboard = dashboard
#profile = profile
#user = user
end
def full_name
"#{#user.first_name} #{#user.last_name}"
end
def feedback_count
#profile.feedback.count
end
def days_since_last_post
Date.today - #dashboard.last_login
end
end
# /app/controllers/dashboard_controller.rb
def show
# ...
#dashboard = DashboardDecorator.new(dashboard, profile, current_user)
end
Then your view can access the data through the decorator:
<%= #dashboard.full_name %>
While you can write your own decorators like above, things get pretty tedious pretty quick. If you like to automate some of these parts, you should check out Draper, a handy gem that makes creating decorators a little easier, especially when your decorator methods map 1:1 with model methods.

Rails 4 user roles and permissions

I am writing a rails application for an organization. Every user may have 1 or more roles and can only access certain controller actions depending on those roles.
For example, only admins can create, destroy and update certain fields of Users. Also, there are Teams which each have a team leader, and only the team leader can update certain information about the Team (like the member list, for example). However, Admins are the one who assign the team leader in the first place.
The specific details of my scenario are not important, I merely hope I described the situation where there are many different roles and permissions.
My question is: what gem to use? My first thought was CanCan, but the last commit was almost a year ago and there is no mention of Rails 4 compatibility. Is there a currently maintained alternative?
Your first guess was right, use cancancan and you'll be good with it.
EDIT Jul 24, 2015
I've been using cancancan for a long time now and it was always working great. I've recently started working on a project where Pundit is used for authorization.
It is awesome. It prompts you to define the policy for each resource and it feels more natural than one bloated Ability class.
For bigger projects, I would definitely recommend Pundit.
To control access to actions I'd recommend Action Access, it boils down to this:
class UsersController < ApplicationController
let :admin, :all
let :user, [:index, :show]
# ...
end
This will automatically lock the controller, allowing admins to access every action, users only to show or index users and anyone else will be rejected and redirected with an alert.
If you need more control, you can use not_authorized! inside actions to check and reject access.
It's completely independent of the authentication system and it can work without User models or predefined roles. All you need is to set the clearance level for the current request:
class ApplicationController < ActionController::Base
def current_clearance_level
session[:role] || :guest
end
end
You can return whatever you app needs here, like current_user.role for example.
Although it isn't required, it bundles a set of handy model additions that allow to do things like:
<% if current_user.can? :edit, :team %>
<%= link_to 'Edit team', edit_team_path(#team) %>
<% end %>
Here :team refers to TeamsController, so the link will only be displayed if the current user is authorized to access the edit action in TeamsController. It also supports namespaces.
You can lock controllers by default, customize the redirection path and the alert message, etc.
It's very straightforward and easy, I hope you find it useful.
Something that was suggested to me that we are now using is the petergate gem. Easy to use and very clean looking with a great rails feel.
Works well with devise.
Here is some examples from the readme.
If you're using devise you're in luck, otherwise you'll have to add following methods to your project:
user_signed_in?
current_user
after_sign_in_path_for(current_user)
authenticate_user!
This comes in your User.rb. Adding more roles is as easy as adding them to the array.
petergate(roles: [:admin, :editor], multiple: false)
Instance Methods
user.role => :editor
user.roles => [:editor, :user]
user.roles=(v) #sets roles
user.available_roles => [:admin, :editor]
user.has_roles?(:admin, :editors) # returns true if user is any of roles passed in as params.
Controller access syntax.
access all: [:show, :index], user: {except: [:destroy]}, company_admin: :all

Creating an additional related model with Devise

I've started to implement a new project using Devise, which is pretty fantastic for handling users. However, when a user signs up, they're not just creating a User model, but also need to create a related Account model that represents the company. Additional users will also belongs_to this Account model.
I can't seem to find a hook for this in Devise, though it seems like a pretty common pattern. What's the best practice for this?
I should also mention that there are a couple of fields for the Account that need to be provided on the sign_up form, so just something like this in the User model:
after_create :make_sure_account_exists
def make_sure_account_exists
if self.account.nil?
#account = self.create_account({ :company_name => '???' })
end
.. as I'm not sure how to get the company name.

Resources