My previous Rails apps have ended up with bloated user model with a thousand has_many associations and a whole bunch of domain-specific code and attributes in the user model.
In the next app, I am thinking about separating the model that I use for authentication (with devise) from the domain-specific model that I use for the rest of the app.
Something like this:
class User
devise :database_authenticatable, :etc
has_one :domain_user
end
class DomainUser
belongs_to :user
end
Controllers would have an accessor to make access to the domain user easy and most of the app would not care about the authentication model at all.
class ApplicationController
def domain_user
current_user.domain_user
end
end
As well as avoiding the bloat of putting everything into one model, I hope to end up with an authentication sub-system that I can reuse in future apps.
Is this a good idea? Is there a better way to approach this? What pitfalls should I look out for?
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 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
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!
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'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.