Role-dependent associations - ruby-on-rails

I am working on my first real Rails app for timetracking in a company and I have run into database/model design concern. My app has a User model and a Role model which are linked together through a has_and_belongs_to_many association.
One of the roles is the manager. A manager can have many assistants and assistants can have many managers (although typically only one). This relationship is represented through a manager_assistant_relationship expressed in the User model as:
has_many :assistants, :through => :manager_assistant_relationships
has_many :managers, :through => :manager_assistant_relationships
Assistants can have many shifts (a different model representing a work shift) and managers can have many clients (another model that will be billed for the assistants' work). And more associations are coming.
As far as I can tell the only solution is to put all these relationships in the User model, but something feels wrong about this and the User model could get bloated as roles increases and associations mount and it also seems unintuitive, because a User only has many assistants if he is a manager. I would like some advice about potential pitfalls to this approach and what alternatives I should consider.
Would it be a good idea to subclass the User model into roles?

Good news, you're doing it correctly with roles.
To answer your question, no it is not a good idea to subclass the User model for what you describe. If you did, it tends to lead to tricky self-joins for associations, or to STI (single table inheritance), or to inheritance or classes instead of composition of modules. Much better to use roles.
It may help you to think of the user as a person, not a role. So the User class contains just personal information, such as name, birthdate, height, and maybe an email address, sign in password, etc. Make the personal information separate from the time tracking role.
To use your roles, you can do model relationships like this psuedocode:
class Manager
has_many :clients
end
manager = Manager.find(id)
client = manager.clients.first
Or like this pseudocode:
class Assistant
has_many :workshifts
end
assistant = Assistant.find(id)
workshift = assistant.workshifts
Other good reasons to prefer using roles is for Role-Based Access Control (RBAC) and for Data Context Interation (DCI) if you want to read up on these.

Related

Managing polymorphic data in Rails

I have an application where a User can create many Links, and each Link can store different type of data, depending on what type of Link it is. For example, a TelephoneLinkData stores a telephone number, an EmailLinkData stores an email address, a subject and a body. Each Link also has some fields in common, such as a reference to the user and a name.
I've tried to map this into ActiveRecord as cleanly as I can. Currently, I have a polymorphic relationship from Link to its data-type:
class Link < ApplicationRecord
belongs_to :user
belongs_to :link_data, polymorphic: true
...
class EmailLinkData < ApplicationRecord
has_one :link, as: :link_data
accepts_nested_attributes_for :links
...
Technically, I think this would be described as a reverse polymorphic relationship as instead of a class having possibly different parent classes, what I'm trying to model is a class having multiple possible different child classes. This works fine, and I'm able to create Links through the various *LinkData controllers, but what I'd really want to do is have the Link act as the primary source of interaction for the user, so that the user manages their links through the /links path. For example, I would like the API to allow a User to create a link by posting to /links with the data for the LinkData nested in the link_data field
I've looked around for other ways to model this relationship, and the most common other suggestion seems to be Single-Table Inheritance, but the majority of my columns will differ between LinkData classes, so that feels like the wrong abstraction.
Is there a more idiomatic way to model this data structure?
As is always the case, the best choice depends on the business or application needs, so it's difficult to provide a recommendation without knowing more about what you're trying to do.
It sounds like you prefer the MTI approach, essentially using actual foreign keys and an XOR constraint to the Link table instead of a type column. That's a totally reasonable (although not as common) alternative to a polymorphic association.
However, I think there was a bit of a misunderstanding in your question.
Technically, I think this would be described as a reverse polymorphic relationship as instead of a class having possibly different parent classes...
A polymorphic association in Ruby/Rails doesn't have anything to do with class inheritance (e.g. parents and children). You might be thinking of Single table inheritance. A polymorphic association allows one class (e.g. a Link) to be associated a record in any other table (e.g. the various classes of LinkData) via two fields, a association_id and association_type. These associated classes need not be related to each other. For example, a common use case might be the acts_as_commentable gem, that allows you to add a comment to any other object, and the comment would have a polymorphic association with the other classes.
In the second part of your question you mention that you'd like the User to interact with Link's via a single controller.
I would like the API to allow a User to create a link by posting to /links with the data for the LinkData nested in the link_data field
There's nothing stopping you from implementing this using the initially proposed data model. ActiveRecord may not handle this completely for you out of the box, but you can imagine implementing a link_data= method on the Link class that would create the appropriate associated object.
I'd say the pros/cons of using a polymorphic association would be...
Pros:
easy to setup and use
easy to make required (validate presence of / not null)
easy to associate with a new class
Cons:
no referential / database integrity
have to migrate data if you change a class name
And using the MTI approach is basically the opposite. A bit harder to setup and use, harder to add a new association/table, harder to ensure exactly one association exists... but the long term data quality benefits are significant.
I was able to get things to work the way I wanted to using multiple table inheritance, based largely on this chapter: https://danchak99.wordpress.com/enterprise-rails/chapter-10-multiple-table-inheritance/

Rails Polymorphic Association with multiple models

This is the case: I have 4 models which are "owner", "user", "location" and "landlord". All of these models share email addresses and phones. So I'm thinking to use Polymorphic Association and I made a research but I just see cases for 3 models. In my case as you can see I will have more than 3 models.
So, do you think is a good idea to implement this kind of logic where I want to use a model like the "repository" for all emails and phones numbers?
There is a limit or something in order to use that kind of association?. I'm thinking in some models like:
email
emailable
user
owner
landlord
location
Each model will have their necessary fields.
Thanks in advance.
There's no limit. A polymorphic association is an open interface that any other model can plug into. In your example, maybe you have a Contact model, which belongs_to :contactable, polymorphic: true. The contacts table will need two indexed columns: contactable_id:integer, and contactable_type:string. Any other model can be contactable, as long as it has_one :contact, as: :contactable.
As far as if it's a good idea, I'd say if you think you will need to work with contacts as a separate entity from the contactable models, then this is a good solution. However, if you won't need to deal directly with contacts then it might be overcomplicating when you could just add email and phone fields to these models.

Models with almost same attributes

I'm pretty new to rails and I'm trying to implement these things in my application but I'm not sure how to proceed and what ways is the best.
I would like to have different type of activities like restaurant, gym, store, ecc.
All these type of activities have the same 'base' attributes like name, city, address, email, but for example restaurant can also have 'food type' attribute or gym can have 'courses'.
I was thinking about not creating different models and so different tables that have redundant attribute, but creating a base model 'activity' and then models for 'store', 'gym', etc..
What is the best solution and how can i achieve it? I looked for STI and multiple inheritance table, but I'm not sure if they are suitable and which solution is the best.
Your question is a bit vague, but it does sound like you'd want to use relations, not inheritance.
A class (or model in the rails MVC paradigm) can have or belong to another model or models. That's called a relation. The models don't inherit from each other (although that is possible for other use cases.) They just have relationships between each other, the same way that database tables can have relationships between them. In fact, relations are how you declare the relationships between the underlying database tables for models.
Address is not the only model that has a relation to gym. A gym is also a business, so it belongs_to the business class. It has one and only one address so it has_one address.
You create models that each have their own attributes (attributes are created with a migration). How you set them up depends on your needs. Since your gym has to have an address, you create an Address model that contains the attributes for an address:
class Address
# your attributes are in the database as created by your migration(s). You don't need to list them in the class.
# validations, methods, etc.
end`
You then use your address model for anything that has an address. You don't need to repeat yourself by including address attributes in every class.
Then you set the relation between Gym and Address and Business in the model class.
Since your gym is a Business, (One of the many businesses in your global conglomerate :) you declare that it belongs_to the Business model.
A class can belong to more than one other class. A class can have one of another class or it have have many of another class. (There's also has_and_belongs_to_many, which is a bit more complicated.)
So here's your Gym model:
class Gym
belongs_to :business
has_one :address
has_many :employees
has_many :members
#rest of Gym class
end
Now you can reference the the gym's address like this:
gym_one.address.line_1
gym_one.address.line_2
gym_one.address.ship_attention
gym_one.address.city
gym_one.address.state
gym_one.address.zip
gym_one.address.country
You can also reference which one of your businesses the gym belongs to:
gym_one.business
Assuming you set the relation between business and address you can get the address attributes for the gym's parent business:
gym_one.business.address.city
Now that you have a Gym class, you don't have to repeat your attributes in every class. You only add the attributes that are unique to a gym in your migration:
In db/migrations/{date_code}_create_gym:
class Gyms < ActiveRecord::Migration
def change
create_table :gyms do |t|
t.string :name
# Other attributes that only a gym has. Not attributes of things that have a relation to Gym
end
end
end
I believe you can use relations here.
You can have model called Attribute, which has id and name. Then you create model called AttributeValue - which belongs_to attribute and restaurant, gym, store etc. Activities will have many attribute_values, and many attributes - through attribute_values.
Then you will have dynamic attributes - without duplicating data.
Combining this with STI and defined per model (restaurant, gym) constraints on attributes - checked using validations
There is no general "best solution". Which concept suits best for your app is not that easy to determine. At least not without more concrete details about what you are planning.
Based on your examples of basic attributes, I would suggest to move them to their own model. city and address would make a location model, email (and probably other communication detail) might suit for a contact model or the like. These models can easily be referenced by different tables through polymorphic associations. That would leave only the name attribute to be present in all tables.
But this is just based on my understanding of your problem... There seem to be different interpretations about what you actually want to do.

Organization model extends user model

I have User model and Organization model. The only difference is that organization has_many users, all other properties are same. I don't want to put it in one table/model. How can I remove tons of code duplicating in this models? Should I use Concerns? I think, it will be not normal if User model will looks like :
class User < ActiveRecord::Base
include user_concern
end
So, how can I extend user model in other model? And how to generate this model with rails g with all User's fields inside?
beware STI
I would keep with concerns rather than using STI. STI often causes more problem that it solves (type mismatches, form urls, etc), plus inheritance won't make sense, here : an user is not a kind of company, and a company is not a kind of user.
That's a naming problem
I think your problem is a naming one. Obviously, your concern should not be "UserConcern". The question is : what kind of methods do you group in that concern ?
Are your methods about social relation between users ? Then, you need a Socializable concern. Are they about subscribing to mailing list ? Then you need a Subscribable concern.
It's ok to have several ones, even if they have a single method in it, because developers won't wonder "what the hell does this do ?" if all concerns are correctly named.
What to duplicate anyway
You should also probably let class level method calls out concerns.
If it's ok for scopes to be embedded in concerns (after all, they resolve in method definitions), it feels less natural to me to put relations in there.
It's ok to duplicate #has_many :foos, we do it all the time in separate models, and it's already difficult enough to get an idea of table schema from a model without hiding more information.
You could use single table inheritance (STI) for this. To get it to work, your model needs a type-field of type string, in which ActiveRecord stores the actual type of your record. Then, you just extend your base model for this.
migration
add_column :users, :type, :string
models
class User < ActiveRecord::Base and class Organisation < User.
ActiveRecord will now fill your type-field with the model-name, and store both in your users table (since this is the one the organisation model is inheriting from).
Have a look at the according section on http://api.rubyonrails.org/classes/ActiveRecord/Base.html .
However, in your case, I'd create a base model, e.g. Address, and then extend User and Organisation from it, to maintain semantically correct models.

accessing previous methods in a chain (Ruby (on Rails))

Lets say I have classes for employees, managers, and companies. These inherit from ActiveRecord and are all associated as you might expect.
I have two instances of the Employee class, bob and jane. They both work for the same company but have different managers.
I want to be able to call bob.company.x or jane.company.x and get different results because, although they work for the same company, they have different managers. There are other limitations that prevent me from just defining x as a method for employees and calling bob.company_x, which I realize would be simpler in this example.
Is there any way that I can know, within my method definition for x, which employee started this method chain?
What you propose cannot be done in a clean and simple way, and aside of that, is counter-intuitive.
class Employee
belongs_to :company
belongs_to :manager
end
is the only clean solution. This has the relations cleanly defined. These relations need to be available anyway: how would you know which is the manager for bob?
A solution, as you propose, where you can write bob.company.my_manager does not make sense, because bob.company is his company, and at that level (Company) there is no more knowledge of bob.
For completeness sake: actually a manager is an employee itself, so we would write that relation a bit differently:
class Employee
belongs_to :manager, :class => Employee
end
(and in your employees table you need to add a field manager_id).
Hope this helps.

Resources