I have a rails application that has three different types of users and I need them all to share the same common profile information. However, each different user also has unique attributes themselves. I'm not sure how to separate out the different fields.
- Admin (site wide admin)
- Owner (of a store/etc)
- Member (such as a member of a co-op)
I'm using devise for authentication and cancan for authorization. Therefore I have a User model with a set of roles that can be applied to the user. This class looks this:
class User < ActiveRecord::Base
# ... devise stuff omitted for brevity ...
# Roles association
has_many :assignments
has_many :roles, :through => :assignments
# For cancan: https://github.com/ryanb/cancan/wiki/Separate-Role-Model
def has_role?(role_sym)
roles.any? { |r| r.name.underscore.to_sym == role_sym }
end
end
Each user has a profile that includes:
- First & Last Name
- Address Info (city/st/zip/etc)
- Phone
I do not want to pollute the User model with this info so I'm throwing it into a Profile model. This part is fairly simple. This turns the User model into something like this:
class User < ActiveRecord::Base
# ... devise stuff omitted for brevity ...
# ... cancan stuff omitted for brevity ...
has_one :profile
end
The additional fields is where I have some uneasy feelings about how to model are ...
If a user is an admin, they'll have unique fields such as:
- admin_field_a:string
- admin_field_b:string
- etc
If a user is a Owner they'll have unique fields ...
- stripe_api_key:string
- stripe_test_api_key:string
- stripe_account_number:string
- has_one :store # AR Refence to another model that Admin and Member do not have.
If a user is a member they'll have a few additional fields as such:
- stripe_account_number:string
- belongs_to :store # the store that they are a member of
- has_many :note
...
and a Store model will contain a has_many on the members so we work the the members of the store.
The issue is around the additional fields. Do I set these up as different classes? Put them into a different I've currently tried a few different ways to set this up:
One way is to set up the User Model as aggregate root
class User < ActiveRecord::Base
# ...
# Roles association
has_many :assignments
has_many :roles, :through => :assignments
# Profile and other object types
has_one :profile
has_one :admin
has_one :owner
has_one :member
# ...
end
The benefit of this approach is the User model is the root and can access everything. The downfall is that if the user is a "owner" then the "admin" and "member" references will be nil (and the cartesian of the other possibilities - admin but not owner or member, etc).
The other option I was thinking of was to have each type of user inherit from the User model as such:
class User < ActiveRecord::Base
# ... other code removed for brevity
has_one :profile
end
class Admin < User
# admin fields
end
class Owner < User
# owner fields
end
class Member < User
# member fields
end
Problem with this is that I'm polluting the User object with all kinds of nil's in the table where one type doesn't need the values from another type/etc. Just seems kind of messy, but I'm not sure.
The other option was to create each account type as the root, but have the user as a child object as shown below.
class Admin
has_one :user
# admin fields go here.
end
class Owner
has_one :user
# owner fields go here.
end
class Member
has_one :user
# member fields go here.
end
The problem with the above is I'm not sure how to load up the proper class once the user logs in. I'll have their user_id and I'll be able to tell which role they are (because of the role association on the user model), but I'm not sure how to go from user UP to a root object. Methods? other?
Conclusion I have a few different ways to do this, but I'm not sure what the correct "rails" approach is. What is the correct way to model this in rails AR? (MySQL backend). If there is not a "right" approach, whats the best of the above (I'm also open to other ideas).
Thanks!