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
Related
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
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
I need some advice on my user model. I came across the STI design and implemented it using devise. This is the basic setup:
class Message < ActiveRecord
has_one :sender
has_one :recipient
# Devise user
class User < ActiveRecord
class Sender < User
belongs_to :message
class Recipient < User
belongs_to :message
My conundrum is that the same user can be both sender and recipient in different scenarios. I originally set up this domain model such that a message record had a sender_id and recipient_id both of which were simply user_ids without any Railsy relationships defined or devise extras.
My previous solution seemed more flexible but the STI design seems more elegant and if possible I'd like to make it work. As I understand it the convention is that the type field discerns which user is returned. Is there a common solution for this using STI?
Broadly speaking, every time I've ended up implementing multiple types of user model, I've ended up regretting it. As you've already noticed, very quickly you end up with people who want to be both types of user, and suddenly you've got to manage different accounts, repeat logins, duplicate emails, etc.
I recommend instead setting up a single type of user that has_and_belongs_to_many roles. For simple cases, you can simply create your own role models and logic (which is what I've usually done), but it looks like this gem is pretty well supported as well: https://github.com/RolifyCommunity/rolify
Rails newbie: I currently have a basic app where Customers(users) have many points (customer model and points model). And I want an admin user (new model) to have the ability to add points to the customer.
-The customer enters their phone number (#index route).
-If the customer is not found, they will be brought to a signup page (#new/#create route).
-If customer is found in the database, their profile will show (#show route).
Now on this page, I want to be able to have an admin passcode, which once entered, gives access to adding points. I also want to keep track of which admin user, gives which customer points on a different page.
How would the schema look like with the admin user? How would I give it access certain access to features like adding points. (I assume I'm going to have to create a helper method for checking if admin is logged in, and keep track of that somehow, maybe with sessions?)
class Customer < ActiveRecord::Base
has_many :points
end
class Point < ActiveRecord::Base
belongs_to :customer
end
class Admin < ActiveRecord::Base
#??? (my best guess is has_many :points, has_many :customers)
end
www.loyalty-app.herokuapp.com
I think the best way is to not have a special admin class and just make admins a type of customer (or user). Then use a gem like cancan or access granted (I prefer access granted) to handle what the different types of users can do.
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.