Single Table Inheritance Alternative - ruby-on-rails

I'm trying to think in a business model very similar to the one described here, using STI.
class Person < ActiveRecord::Base
# identified by email
end
class Owner < Person
end
class Customer < Person
end
class Employee < Person
end
class Store < ActiveRecord::Base
belongs_to :owner
has_many :customers
has_many :employees
end
The classes above describe what I intend to do. The problem here is that a Employee can never act as a Customer, and hire the services provided by the store he works, or even another store, unless a new record is created to represent the same person acting as the a different role in a different context. That is not very DRY, but I don't know if there is a better solution.
Is there? Anyone has any suggestion on how I could resolve this issue?
Thank you very much.

Being an owner (note that there may be several for a given store and one person may own several stores) is not part of a person's identity, it is a relationship between a person and store so subclassing isn't really appropriate here. Similarly for being a customer or employee.
This leaves us with five components:
People.
Stores.
The "person owns a store" relationship.
The "person is a customer of a store" relationship.
The "person is an employee of a store" relationship.
All three relationships are, realistically, many-to-many. Also note that there's STI anywhere in sight; this is a good thing, STI is almost always (IMO) a mistake so you should start questioning your data model and your judgement as soon as it shows up. STI does have its place of course but you should think hard to justify it whenever it comes up.
This leaves us with two fairly simple models (Person and Store) and three many-to-many relationships between people and stores. The standard ways of modelling many-to-many relationships with ActiveRecord are has_many ... :through and has_and_belongs_to_many. If you need to work with one of the person-store relationships as a separate entity (such as an employee with an employee number, hourly rate, tax records, ...) then you'd probably want has_many :through; if you only need the association then has_and_belongs_to_many would probably work.
Some references:
The has_many :through Association
The has_and_belongs_to_many Association
Choosing Between has_many :through and has_and_belongs_to_many

Actually, it is DRY from a code perspective. I actually work on a very similar project using STI where we have users, managers, and administrators, and there must be three records for each in the database. This is DRY from a Rails perspective because each of those records has their own unique attributes, methods in their own classes, etc. but share common code from a similar model to what you call Person. I actually think this is a good way to do it if you're using STI.
An alternative would be to have common code in a module which you could include in each of Customer, Employee, and Owner.
Another alternative (most likely what I would do if starting from scratch) would be to have a single Person table and use roles, using cancan and maybe even rolify. This way you have one class you're dealing with called Person where an instance of Person can have one or many roles, such as customer, employee, or owner.

Related

How does one create a `has_one` relationship between two models using a join table/model?

TL;DR: How does one create a has_one association using through join table and vice-versa a belongs_to through?
Context: I have two models, ProcessLog and Encounter. ProcessLog, as the name (somewhat) suggests, saves log of a single run (corresponding to a row in DB) of an external process (which is run multiple times). On the other hand, Encounter is a model that keeps track of some information X. Encounters can be produced either internally or as a result of a successful execution of the external process mentioned earlier. What it entails is that not all Encounters have an associated ProcessLog and not all ProcessLogs have an associated Encounter. However, If there is a ProcessLog for an Encounter, this is a 1:1 relationship. An Encounter cannot have more than one ProcessLog and a ProcessLog cannot belong to more than one Encounter. From DB design perspective, this is an optional relationship (I hope I haven't forgotten my lessons). In a database, this would be modelled using a join table with encounter_id as the primary key and process_log_id as the foreign key.
Rails: In Rails, 1:1 relationships are generally modelled without using a join table and the belongs_to table generally having a foreign key to the other table. So in my case, this would be encounter_id in process_logs table.
Problem: With traditional Rails approach of has_one and belongs_to, this will result in many rows in process_logs table with NULL values for encounter_id column. Now there are pros and cons to this approach by Rails, however, that is not my intention to discuss here. Yes, it will keep the table structure simple, however, in my case it breaks the semantics and also introduces lots of NULL values, which I don't consider a good approach. And is also the reason why a join table exists for optional relationships.
What have I done so far?: There aren't a whole lot of helpful documents I could find on this topic, except for the following two linked documents, though they have their own issues and don't solve my problem.
SO Question The approach here is using has_many for the join model, whereas I have only one
Discussion on RoR Similarly, it is using has_many and yet somehow talks about has_one
I created a join model called EncounterProcessLog (which has belongs_to for both ProcessLog and Encounter) and then a has_one ..., through: on the other two models, but Rails is looking for a many-to-many association and of course looking for encounter_id on process_logs table.
Question: How can I achieve what I intend to achieve here? Something on the lines of (non-working) code below:
class Encounter:
has_one :process_log, through: :encounter_process_logs
class ProcessLog:
belongs_to :encounter, through: :encounter_process_logs # This may be incorrect way of specifying the relationship?
class EncounterProcessLog:
belongs_to :encounter
belongs_to :process_log # May be this should be a has_one?
I hope someone is able to guide me in the right direction. Thanks for reading so far.
One way I can think of for doing this is:
class Encounter:
has_one :encounter_process_log
has_one :process_log, through: :encounter_process_log
class ProcessLog:
has_one :encounter_process_log
has_one :encounter, through: :encounter_process_log
class EncounterProcessLog:
belongs_to :encounter
belongs_to :process_log
This would return the process_log for encounter and vice versa which is what you want probably.

How to associate 3 different models in to a 2 model relationship

The gist of the matter is I want to know the best way to associate the below setup.
I have 2 customer models. Individual Customer & Corporate Customer.
I have another Vehicle model. Which I would like to maintain separately.
I would like to have a HMT model called VehicleOwner. Which now adds the r/ship of any of the two(2) customers as an owner & the vehicle.
The reason for this approach is an architecture design to allow the same vehicle to be migrated to other customers and not have every time a customer wants to add a vehicle; they keep adding a vehicle even if we have it.
My question is this?
How can I link in the Vehicle Owner. That the owner can either be an individual customer or a corporate customer.
Or is there another better way to map the two(2) customers with a vehicle.
Thanks
Perhaps you are looking for Polymorphic association in Rails, in your case it would be, VehicleOwner model should look like
belongs_to :customer, polymorphic: true
In IndividualCustomer and CorporateCustomer models
has_many :vehicle_owners, as: :customer
This is just an example of how to implement this, you can read more about in official Rails guides
https://guides.rubyonrails.org/association_basics.html#polymorphic-associations
Hope that helps!

How to implement a belongs_to with by_group in rails

I am wondering how to set up the models in my rails application. I know I need to use some sort of nested association but not sure what the proper specification should be. Description of the problem can be found below, any suggestions would be greatly appreciated.
I work for a private equity firm that owns many different companies. We want to build an web application to track purchasing across the portfolio. More specifically, we want to understand how much we are spending with specific vendors and with specific categories of vendors, both in aggregate and at each company.
Modeling this would seem straightforward enough. You create a companies model, a categories model, a vendors model (where each vendor belongs to a category), and a transactions model (where each transaction belongs to a specific vendor and a specific company). However, there is a wrinkle in the requirements that I am not sure how to handle. We want to allow different companies to classify the same vendor differently, but also have a standard classification for each vendor. So one company could classify staples as office equipment, another might classify them as SGA-office supplies but we would maintain a standard classification that always mapped staples to office supplies.
What is the best way to set this up? I was thinking I would create 2 category models, one called stanard_category with :has_many => vendors. Another called company_category with :has_many => vendors by company. Is this the right approach? If so, how do I specify that the :has_many relationship on company_category is specific to each company? Is this a situation where I would want to use nested routes?
Here's an example structure that would suit your needs:
class Company
has_many :company_categories
end
class CompanyCategory
belongs_to :company
has_and_belongs_to_many :vendors
end
class StandardCategory
has_many :vendors
end
class Vendor
has_and_belongs_to_many :company_categories
belongs_to :standard_category
end
Whether or not you'd use nested routes depends on the specifics of your application.

Confusion over the tables for has_one and has_many

In the Rails ActiveRecord Associations guide, I'm confused over why the tables for has_one and has_many are identical:
Example tables for has_many:
customers(id,name)
orders(id,customer_id,order_date)
Example tables for has_one:
these tables will, at the database level, also allow a supplier to have many accounts, but we just want one account per supplier
suppliers(id,name)
accounts(id,supplier_id,account_number) #Foreign Key to supplier here??
Shouldn't the tables for has_one be like this instead:
suppliers(id,name,account_id) #Foreign Key to account here
accounts(id,account_number)
Now because the account_id is in the suppliers table, a supplier can never have more than one account.
Is the example in the Rails Guide incorrect?
Or, does Rails use the has_many kind of approach but restricts the many part from happening?
If you think about this way -- they are all the same:
1 customer can have many orders, so each order record points back to customer.
1 supplier can have one account, and it is a special case of "has many", so it equally works with account pointing back to supplier.
and it is the same case with many-to-many, with junction table pointing back to the individual records... (if a student can take many classes, and one class can have many students, then the enrollment table points back to the student and class records).
as to why account points back to supplier vs account points to supplier, that one I am not entirely sure whether we can have it either way, or one form is better than the other.
I believe it has to do with the constraints. With has_one rails will try to enforce that there is only one account per supplier. However, with a has_many, there will be no constraint enforced, so a supplier with has_many would be allowed to exist with multiple accounts.
It does take some getting used to when thinking about the relationships and their creation in rails. If you want to enforce foreign keys on the database side (since rails doesn't do this outside of the application layer), take a look at Mathew Higgins' foreigner
If I understand your question correctly, you believe there is a 1:1 relationship bi-directionally in a has_one/belongs_to relationship. That's not exactly true. You could have:
Class Account
belongs_to :supplier
belongs_to :wholesaler
belongs_to :shipper
# ...
end
account = supplier.account # Get supplier's account
wholesaler = Wholesaler.new
wholesaler.accounts << account # Tell wholesaler this is one of their suppliers
wholesaler.save
I'm not saying your app actually behaves this way, but you can see how a table -- no, let's say a model -- that "belongs to" another model is not precluded from belonging to any number of models. Right? So the relationship is really infinity:1.
I should add that has_one is really a degenerate case of has_many and just adds syntactic sugar of singularizing the association and a few other nits. Otherwise, it's pretty much the same thing and it's pretty much why they look alike.

Objects and Relations in Ruby on Rails

I'm attempting to create a simple web application for some personal development, but I've run into an obstacle, which I'm hoping others will find trivial. I have working classes created, but I'm not sure how define their relationships, or whether their models are even appropriate.
For the sake of argument, I have the following classes already in existence: User, Team, and Athlete
The functionality I'm striving for is for a user to create a team by adding one athlete to the team object. Other users can either add players to the team, or they can add alternatives to an existing athlete on the team roster. Essentially, I want to create some sort of class to wrap an array of athlete objects, we'll call it AthleteArray.
If my understanding is correct, these are the relationships that would be appropriate:
So a Team would have-many AthleteArrays
An AthleteArray would have-many Athletes and would belong-to a Team
Athletes would belong-to a User, and would belong-to a AthleteArray.
Users would have-many athletes, but that would be the extent of their involvement.
Since the AthleteArray class wouldn't have any attributes, is it wise to create it as an ActiveRecord object(would it merely have an ID)? Is there another way to implement this idea(can you define the team class, to have an array of arrays of athlete objects)? I have very little knowledge of RoR, but I thought it would be a good place to start with web development. Any help would be appreciated. Thanks in advance!
Edit: The names of Athletes, Teams, and AthleteArrays are unimportant. Assume that an Athlete is basically a comment, validated against a list of athletes. Duplication is acceptable. If I'm understanding the answers posted, I should basically create an intermediate class which takes the IDs of their parents?
Is response to Omar:
Eventually, I'd like to add a voting system. So after basic team is created, users can individual suggest replacements for a given player, and users can vote up the best choices. For instance, If I have a team of chess_players created:
Bobby Fischer
Garry Kasparov
Vladimir Kramnik
someone might click on Kasparov, and decide that a better athlete would be Deep Blue, so this option would appear nested under Kasparov until it received more votes. This same process would occur for every character, but unlike a comment system, you wouldn't be able to respond to "Deep Blue" and substitute another player, you would simply respond to the position number 2, and suggest another player.
Right, so if I understand the question properly, there will be many possible combinations of athletes for a team, which may or may not use the same athletes?
Something sounds off here, possibly in the naming of your models. Don't quite like the smell of AthleteArrays.
has_many_through might be what you need to access the athletes from your team e.g.
has_many :athletes, :through => :team_permuations # your AthleteArray model
Josh Susser has an old roundup of many to many associations which i guess is what you need, since according to your specification, theoretically it can be possible for an athlete to belong to any number of teams. I suppose the best thing is that you can have automagically populated audit columns (created/updated_at/on) on your many to many associations, which is a nice thing to have.
http://blog.hasmanythrough.com/2006/4/20/many-to-many-dance-off
Please comment if you think I have understood the question wrong.
If I understand your need, this is something that can be solved through has_many :through.
basically you can represent this in the database like
athletes:
id
other_fields
teams:
id
other_fields
roster_items:
id
team_id
athlete_id
position
is_active #is currently on the team or just a suggestion
adding_user_id #the user who added this roster item
class Athlete < ActiveRecord::Base
has_many :roster_items
end
class Team < ActiveRecord::Base
has_many :roster_items
has_many :athletes, :through => :roster_items
end
class RosterItem < ActiveRecord::Base
belongs_to :team
belongs_to :athlete
belongs_to :adding_user, :class_name => 'User'
end

Resources