Rails STI for User model with associations and authentication - ruby-on-rails

I am developing a site where doctors can manage their patients, appointments, etc.
A doctor can have multiple assistants which help them with these activities, and an assistant can belong to multiple doctors.
Both doctors and assistants should be able to sign in from the same login form but for obvious reasons they have different permissions and associations depending on their role, for example patients are associated to the doctors, not to the assistant.
How you suggest would be the best way to approach this? I am contemplating CanCan for the authorization part, and maybe STI for a User model which Doctor and Assistant can inherit from for the authentication part, but can STI handle the HABTM between these 2, plus the other associations each model might have?
Thanks a lot in advance

This should do the trick.
Just make sure you use 'type' to set which the user is. You can then use things like:
current_user.is_doctor?
To determine access to areas.
module User
def is_doctor?
true if self.type == Doctor
end
def is_assistant?
true if self.type == Assistant
end
end
module Doctor < User
has_and_belongs_to_many :assistants, :through => "assistant_assignments"
end
module Assistant < User
has_and_belongs_to_many :doctors, :through => "assistant_assignments"
end
Shout if you need more info.

Related

Rails Design Pattern for Multiple Devise Log-Ins Managing One Set of Data

EDIT: What's the design pattern for an app that does the following: A business sets up an account with the app. The business then creates "employees" in the app that can log in separately and CRUD the business's data, except for what their employer marks as off limits?
I've run into a problem with the hostel app. Here we go:
My app, a hostel management SaaS, has many users. These are hostels. The owner of the hostel signs up for my app via Devise. current_user is always the hostel itself.
Hostels have_many guests and each guest belongs_to a user(the hostel). These are people who call/email and want to spend a night in the hostel. Thanks a ton to everyone for helping me with the availability calendar.
All is fine and dandy now. Normally, the hostel owner or manager logs into my app, books rooms, emails guests, sends invoices, upload financials, you name it. However, many have been requesting the ability to add employees that can log in separately and create reservations, send emails, etc, but NOT view financial info. Enter CanCan.
Here's where I'm stuck. It's easy enough to delegate abilities and authorizations. Devise also gives me the ability to set up multiple devise models. However, I'm stuck with how I can give the employee, once they log in, access to their employer's data. The current_user.id is going to be different than their employer's ID(the business that signed up), so how can I tell Devise to use the ID of their user?
class User
has_many :employees
end
#should I do something like this?
class Employee
belongs_to :user
has_many :guests, :through => users
has_many :reservations, :through => users
has_many :rooms, :through => users
end
I thought about doing something like this below:
current_user.id = current_employee.user.id
The only problem is, it smells. There must be a better way. Once the employee logs in, everything is going to look the exact same as when their boss logs in(show all reservations, all guests, all emails, etc), the employee will just be restricted from certain areas.
The same current_user on multiple models in devise is mostly workarounds and hacks. https://leanpub.com/multi-tenancy-rails seemed kind of in the right direction, but it's a bit too much for what I need. There must be a specific design pattern for this situation, but I can't seem to find it or even get Googling in the right direction.
Basically, how do Rails apps give their users the ability to create sub-users, and let those sub-users view the user's data with restrictions?
Any help getting my thoughts straightened out is much appreciated.
Is there anything wrong with a simple override of the current_user method?
class ApplicationController
def current_user
super || current_employee&.user # safe operator for ruby 2.3+, otherwise `current_employee.try(:user)`
end
end

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 Models: For numerous Roles, multiple User models with additional sub-roles the right design?

I am writing my first Ruby On Rails app - a website for a gated residential community.
The community area consists of Empty plots, with one or more Houses in a built-up plot. Plots will have one owner only, but multiple houses in a plot can each have different Owners. There are elected Office bearers with different Roles (in the residents association) and Property management staff etc. Eventually there will be user-groups (owners only or tenants only or mixed etc).
An Owner and a Tenant tend to be quite different, hence I have kept them as separate classes. Similarly their family members tend to be different too - OwnerFamilyMember and TenantFamilyMember classes.
My first design was to have a single User and a Role and an Assignment model - the roles became too many. Hence I split the User Model as above (I do not want to use STI or polymorphic associations, I would like to get it right first).
Model Classes:
# All classes below inherit from ActiveRecord::Base, removed other attributes for compactness
class Owner
has_many :plots
has_many :houses
has_many :owner_family_members
end
class Tenant
belongs_to :house # declare house_id in table
has_many :tenant_family_members
end
class Staff ...
class Plot
belongs_to :owner # declare owner_id in table
end
class House
belongs_to :owner # declare owner_id in table
end
class OwnerFamilyMember
belongs_to :owner # declare owner_id in table
end
class TenantFamilyMember
belongs_to :tenant # declare tenant_id in table
end
Tenants or Owners reside in houses.
FamilyMembers of Owners or Tenants will participate in the community
but they are dependent on the primary Owner or Tenant for certain
privileged actions
I understand that with this design, the different User models have implicit roles and can have additional sub-roles if required - a Owner can be a Treasurer for the Residents Association, a Tenant may lead a water conservation group etc. I expect the number of roles to evolve further and hence I think it is better to keep the multiple User models.
Am I on the right track? Am I mixing up the wrong things in this recipe? Keen on hearing any feedback, conceptual or implementation specific that can help me understand this better.
I understand db concepts, OO programming but I am a newbie at production level db design or RoR appls. Thanks for reading through this long post.
- Jayawanth
welcome to the Rails community! You will love it here.
First of all, you don't need to shy away from STI or polymorphic associations, they are quite valuable tools in the Rails world. Your User/Role/Assignment (join table) strategy seems reasonable to deal with a good normalized authentication and role-based authorization approach. I would keep the User model fairly separated from the other logic of your application and deal authorization using Ryan Bates' CanCan library and authenticate via Plataforma's Devise library.
Now you have a nice authentication / authorization setup that doesn't depend on a whole lot of different User/Admin/GuyWhoAuthenticates models and the application is much simpler. Ex:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
...
if user.has_role? 'plot_owner'
can :manage, Plot, user_id: user.id
end
...
end
end
class User < ActiveRecord::Base
has_many :assignments
has_many :roles, through: :assignments
...
def has_role? role
roles.where(label: role)
end
...
end
Regarding the other models, a good rule of thumb is this: how much logic do they share? If they have the same attributes, share a good amount of application logic and are conceptually similar (like, being a family member), then I would go with STI / polymorphic associations. If not, you can extract the common logic into a separate module and include it on both models.
Since you are new to the Rails world I would also strongly recommend that you check out Railscasts and Peepcode, two awesome screencast sites that will have you making Rails applications like a boss in no time :D
Cheers!

Devise Cancan registration for Users and Companies

In Rails 3 application I have 2 logical entities - User and Company.
I'd like to have 2 different forms for sign up(for users and for companies). Also, will be great to have one login form for both of them.
What I have now - configured Devise+Cancan for User model with two roles(user, company), so I have now "/users/sign_in" and "/users/sign_up".
I'd like to have following urls in my application:
/login
/users/signup
/companies/signup
One other question is how to organize relationship between User and Company, should the company inherited from User or I shoud use aggregation - User has_one Company ? I prefer second variant and plan to user user.company with cancan user role = "company".
Please help me with it. Thanks.
You can have multiple models in devise. You can add a company as well.
rails generate devise company
This will get you the url you mentioned.
Regarding the relationship between User and Company It's common to use:
class User < ActiveRecord::Base
belongs_to :company
end
class Company < ActiveRecord::Base
has_many :users
end
You have to add a company_id column to the User model in a migration to make this happen. There is no inheritance then. Users and companies are treated completely separate. But you are able to access user.company and company.users.

Handling different user types in Rails

I'm designing an application that has two [three including administrators] user types: Buyers and Sellers. When a user signs up, it's assumed that they're signing up to simply purchase something [a Buyer account]. However, if they wish to become a Seller, there should be an option for them to post their items for sale, [become a Seller]. This becomes important, as users can switch back and forth between account types.
Of course, a Seller can buy items as well.
The problem
The problem I'm facing is that Sellers have profile pages where buyers can go to view their items for sale, whereas Buyers do not. However, both user types have a My Account page that they can use to update their information.
Possible design choices
Single table inheritence
class User < ActiveRecord::Base
has_one :profile
end
class Seller < User
has_many :sale_items
end
class Buyer < User
# nothing here.. I guess this is the "default" user type
end
I thought about this approach because then I could clearly separate the user types. For example, the show page for each user is clearly separated. However, this could lead to repeated code in each controller, and introduce a problem when switching between user types.
Just use declarative_authorization or CanCan to add functionality to the base user type
class User < ActiveRecord::Base
has_one :profile
has_many :sale_items # only for Sellers
# if the user is a seller, allow them to add sale_items
end
I thought of this approach because a Seller is basically a Buyer with additional functionality, such as posting items for sale. This could lead to a lot of view logic, though. For example if #user.role == "seller" render _some_seller_partial. I also don't like the idea of checking for a hard coded string in the view. Well, I guess I could do if #user.seller?
Other design choices?
I'm really interested in hearing how other people would model this application. I've been thinking about this for a couple days now.
I would use the second option, but with declarative_authorization instead of cancan, and I'd use the role_model Gem to store the role in the user model.
class User < ActiveRecord::Base
has_one :profile
has_many :sale_items # only for Sellers
# if the user is a seller, allow them to add sale_items
# t.integer :roles_mask , :default => 0 # for role_model
end
declarative_authorization is a bit more powerful than CanCan, and I found it to scale better once a project needs more / more complex roles..
If you store your roles using the role_model Gem, it stores them as a bitmap in the roles_mask attribute.
This means that a user can have many roles, e.g. can be a Seller and a Buyer at the same time, and an Admin or Moderator if you like.
See:
http://railscasts.com/episodes/188-declarative-authorization
http://railscasts.com/episodes/189-embedded-association
http://railscasts.com/episodes/192-authorization-with-cancan
http://railscasts.com/episodes/193-tableless-model
And:
http://everydayrails.com/2011/10/06/rails-authorization.html
http://www.tonyamoyal.com/2010/07/28/rails-authentication-with-devise-and-cancan-customizing-devise-controllers/
I would pick the cancan option. Is a wonderful gem, well documented, easy to use, actively mantained, and you have lots of tutorials (since it a gem created by Ryan Bates, from Railscasts, you can be sure that you have an episode for it)
I would go with the cancan option, and use the draper gem for the view logic. There are Railscasts for both gems.
http://railscasts.com/episodes/286-draper

Resources