Ruby On Rails Hierarchical Relationship Modeling - ruby-on-rails

I have a base table called users which holds all common information about a user such as name, address, phone number...etc
I have another table called clients which holds specific information about a client (such as the client's company name and their url) and inherits user information from the users table. A client has a foreign key user_id which maps back to the information about a user.
I have another table called client_admins which hold specific information about client_admins and also has a user_id field AND a client_id field (which links to the clients table).
I have another table called super_admins which links to the users table and has specific information about a Super admin.
I know I could probably get away with Single Table Inheritance as there is not a lot of different data between each of the types, just different functionality and privileges.
What is the best way to model this in Rails 3?

Inside your user model:
has_one :client
has_one :client_admin
has_one :super_admin
Inside your client model:
belongs_to :user
has_one :client_admin
Inside your client_admin model:
belongs_to :user
belongs_to :client
Inside your super_admin model:
belongs_to :user

I am unsure of whether this is exactly what you were aiming for, but with my citier gem you could do this:
class User < ActiveRecord::Base
acts_as_citier
# User methods and validation, inherited by all children (so client & super admins)
# Useful for things like validating user permissions etc
end
class Admin < User
acts_as_citier
# Admin specific methods and validation, inherited by all children
end
class Client < ActiveRecord::Base
end
class ClientAdmin < Admin
acts_as_citier
belongs_to :client
# Client admin specific methods and validation
end
class SuperAdmin < Admin
acts_as_citier
# Super admin specific methods and validation
end
Allows each model to have unique fields so better than STI, but also shares methods and properties like normal inheritance.
Check it out - http://peterhamilton.github.com/citier/

Related

Rails 5, Concerns - can they reference a database table?

I'm trying to learn about concerns.
Most of the blog posts and examples I can find discuss them in the context of moving class methods defined in a model to a common repository. I understand that part.
I don't understand whether concerns can be used to reduce the setup for associated models. For example, I have a user model and an organisation model. Each of user and organisation will have an address.
If address is a model, it will be polymorphic and belong to addressable. Then user and organisation will each have one address.
I am trying to understand whether I can make address a concern and then include it in my user and organisation models. If so, can I still have a table in the database called address? It's not clear to me whether I can have the db table if I don't have a model called address (which I wouldn't need if I used concerns subfolder in the models folder to define address).
Yes, you can certainly do this, and it's pretty common. You'll still need an Address model and addresses table in the database.
It would look something like this:
# your user model (backed by users table)
class User < ApplicationRecord
include Addressable
end
# your organisation model (backed by organisations table)
class Organisation < ApplicationRecord
include Addressable
end
# your address model (backed by addresses table)
class Address < ApplicationRecord
belongs_to :addressable, polymorphic: true, touch: true
end
# the concern to DRY up shared relation that both user adn organisation have
module Addressable
extend ActiveSupport::Concern
included do
has_one :address, as: :addressable, dependent: :destroy
end
end

RoR modelling for different users, Devise, Active Admin

I have a single users table through Devise. Branching off this table are 3 other models (author.rb, seller.rb and buyer.rb) with each having a one to one relationship with the main Users table.
The reason for this is each have some unique attributes and I want to keep the main Users table tidy. I am using active admin and want to avoid redundant fields when registering a new user.
Currently I am using enums to assign user roles:
enum role: [:author, :sellers, :buyers]
The problem is when I set a role it works in the sense that I can restrict what a user sees based on that role however there is a big issue I have below.
The problem:
I want to be able to register an Author. Everything good so far. But I also want to be able to register a Buyer and then associate that buyer with the author as two different users. At the moment a user is becoming both at the same time through nested forms in active admin I used which is not what I want. I want a user to be a buyer and the other user to be an author.
Maybe I don't have my relationships set up correctly for this? Or it could be a problem in active admin?
class Author < ActiveRecord::Base
belongs_to :user
end
class Buyer < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_one :author
has_one :buyer
end
Basically I want to be able to register two different users and after that associate them and I don't know how to do this. Any advice much appreciated.

User owns a model and other users can join this model

I want to creat an application where a User can create a Room and is the only owner of it. Other users should be able to join a Room, just one at the time and only they should be able to see what is happening in this Room.
So i created a controller rooms_controller and the model Room.
Btw I'm using devise to handle all the Userstuff.
So what should i put into the user.rb file? has_one :room? belongs_to :rooms?
How can users join a model?
Does a User have an one.to-one relationship with Room? In that case User has_one Room. Or can a User create many Rooms? A User can join a Room, if, for example, there is a Rooms_Visitors table where the RoomId and the UserId identify a row.
Its hard to answer without knowing your broad use case.
I advice that you study a bit on SQL relations, then the answer for your use case will become clear to you.
You can design the models this way. You can actually have two types of users: Owner and User and they can inherit from the BaseUser where BaseUser being the class with all the common user attributes.
And, your model associations can look like this:
class BaseUser < ActiveRecord::Base
# all the common user attributes here
end
class Owner < User
has_one :room
end
class User < BaseUser
has_many :rooms
end
class Room < ActiveRecord::Base
belongs_to :owner
belongs_to :user
end
owner has one room and room belongs to owner.
user has many rooms and room belongs to user.
You have to add owner_id and user_id in your rooms table.
You can also have a user_type column in your base_users table to indicate what type of user is this (i.e. owner or user).
Also, take a look at the cancancan gem which is an authorization Gem for Ruby on Rails application. You will need to have different types of abilities for example: owner will be able to create the room but user can't create the room, they can only join. You need to handle these things using authorization.

Ruby on Rails 3.2 Sorcery authentication with sub users

I'm using Ruby on Rails 3.2.8 with Sorcery authentication.
What is the best way to create sub-users accounts?
Ideally, I would like to invite members by email and have them click on a link and they choose their username/password. Is it possible to do this within the same user table so they all can login from the same login page?
To wrap your head around what I'm trying to do is... Employer can add/invite employees to join the system and any data input to the system will belong to the employer.
Personally I would handle this as a single table inheritance model with a same-table association column.
Put a field called boss_id on your users table. You'll have the main User model, that has the Sorcery authentication stuff and any common logic. Underneath it you have Employer and Employee. They have a method called boss: for the employer, this returns self or nil (whichever you find makes most sense), whereas for employees, this is an actual association method, something like this:
class Employee < User
belongs_to :boss, class_name: 'User'
end
When an employer sends out an invitation, direct the invitees to a URL for invitations specific to the employer, like:
http://yoursite.com/employer/3/invitation
When the user creates their account you associate them to their owning employer.
When an employer views their data, ensure that you pull their child employees' data as well, using the users table as a join table:
class Employer < User
has_many :employees, class_name: 'User', foreign_key: 'boss_id'
has_many :contacts # Or whatever your application-specific stuff is
# that you want employers to see through their employees
has_many :employee_contacts, through: :employees, source: :contacts
end
If you need to assign ownership to employers in the database, I'd use an observer to watch for saves on the owned models, and if they are saved by someone with a boss, set an additional column to that boss' ID.

Rails Active Record - How to model a user/profile scenario

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!
My answer assumes that a given user can only be one type of user - e.g. ONLY an Admin or ONLY a Member. If so, this seems like a perfect job for ActiveRecord's Polymorphic association.
class User < ActiveRecord::Base
# ...
belongs_to :privilege, :polymorphic => true
end
This association gives User an accessor called 'privilege' (for lack of a better term and to avoid naming confusion which will become apparent later). Because it is polymorphic, it can return a variety of classes. The polymorphic relationship requires two columns on the corresponding table - one (accessor)_type and (accessor)_id. In my example, the User table would gain two fields: privilege_type and privilege_id which ActiveRecord combines to find the associated entry during lookups.
Your Admin, Owner and Member classes look like this:
class Admin
has_one :user, :as => :privilege
# admin fields go here.
end
class Owner
has_one :user, :as => :privilege
# owner fields go here.
end
class Member
has_one :user, :as => :privilege
# member fields go here.
end
Now you can do things like this:
u = User.new(:attribute1 => user_val1, ...)
u.privilege = Admin.new(:admin_att1 => :admin_val1, ...)
u.save!
# Saves a new user (#3, for example) and a new
# admin entry (#2 in my pretend world).
u.privilege_type # Returns 'Admin'
u.privilege_id # Returns 2
u.privilege # returns the Admin#2 instance.
# ActiveRecord's SQL behind the scenes:
# SELECT * FROM admin WHERE id=2
u.privilege.is_a? Admin # returns true
u.privilege.is_a? Member # returns false
Admin.find(2).user # returns User#3
# ActiveRecord's SQL behind the scenes:
# SELECT * FROM user WHERE privilege_type='Admin'
# AND privilege_id=2
I would recommend you make the (accessor)_type field on the database an ENUM if you expect it to be a known set of values. An ENUM, IMHO, is a better choice than a VARCHAR255 which Rails would normally default to, is easier/faster/smaller to index but makes changes down the road more difficult/timeconsuming when you've got millions of users. Also, index the association properly:
add_column :privilege_type, "ENUM('Admin','Owner','Member')", :null => false
add_column :privilege_id, :integer, :null => false
add_index :user, [:privilege_type, :privilege_id], :unique => true
add_index :user, :privilege_type
The first index allows ActiveRecord to rapidly find the reverse association (e.g. find the user that has a privilege of Admin#2) and the second index allows you to find all Admins or all Members.
This RailsCast is a bit dated but a good tutorial on polymorphic relationships nonetheless.
One last note - in your question, you indicated Admin, Owner or Member was the user's type which is appropriate enough, but as you probably see, I'd have to explain that your user table would then have a user_type_type field.
I'm probably not going to give you a rails approved suggestion, but ..
Separating your profile is a good call. Consider using the Decorator pattern for the a role. You can have an AdminUserDecorator, OwnerUserDecorator, or MemberOwnerDecorator. You could also dynamically add the additional fields directly on the instance (it is Ruby after all), but I think that would get ugly and complicated. (If you really want to do bad things, use a visitor object to give you an instance of your decorator from a method on the user class.)
Also, why put the stripe or payment config on the Owner instead of being part of the store information? Unless perhaps an owner can have multiple stores and use the same payment info for each store?
UPDATE: I should also suggest using TDD to flush out what works.
You've already accepted an answer, but for what it's worth, I have a similar situation and chose to go with your "User Model as aggregate root" approach. My User model contains all the "profile" info, and the User, to use a fictitious example, has_one :buyer and has_one :seller. I use a simple tinyint field as bitflags for which roles the user holds, since users could be both buyers and sellers (or TBD other roles I need in the future). If a bit is set, you can assume the corresponding association is not nil (which hasn't been an issue for me since I always check the bitflags before using the association reference). I don't actually have too many unique fields in my real subordinate models, but it's very useful to keep things decluttered when each subordinate model has additional associations, like if seller has_one :merchant_account" and a buyer has_one :purchase_history, etc. I haven't gone live yet, but when I do, I'll follow this post up with any issues I encounter.

Resources