Rails: using STI to model client and partner records - ruby-on-rails

I know STI is a debated topic within the Rails community (and probably others), which is the reason I'm trying to find a different solution to my problem before going down the STI route.
I'm building a system that has a contact management portion, which contains both client and partner records. The difference is that partners will have an associated partner_type and a few additional fields that client will not have.
This looks like a good case for STI. The records are of the same "category", meaning they all represent "people" but in different ways. They will all have the same core fields and have many email_addresses/phone_numbers.
But the biggest requirement that led me to STI instead of separate tables, is that I need to list all of the contacts together alphabetically. My employer doesn't want separate pages for client records and partner records. If I broke this into multiple tables, I would have to somehow query both tables and arrange them alphabetically, also while taking pagination into account (will have thousands of records for each type).
Is there another solution besides STI? I know many developers have run into problems with STI before, but I'm leaning towards this is a text-book case where STI may actually work.
class Contact < ApplicationRecord
has_many :email_addresses # probably use polymorphic
has_many :phone_numbers # probably use polymorphic
validates :first_name, :last_name, presence: true
end
class Client < Contact
end
class Partner < Contact
belongs_to :partner_type
validates :partner_type, presence: true
# some attributes only applicable to client
validates :client_unique_field1, :client_unique_field2, presence: true
end

There are two design decisions you need to make this case:
Should partners and clients share the same table?
a. If "no", then you simple create separate tables and separate models.
b. If "yes", then you have a second design question to answer #2.
Should partners and clients share the same model class?
a. If "yes", then you can use an emum to identify the different roles of partner and client and use that enum to drive your business logic.
b. If "no" then you should implement STI.
I think there is a strong case to say "yes" to #1. It seems client and partner are both fundamentally the same thing. They are both people.
More importantly, they will contain most of the same information so sharing a table makes good sense.
So that leaves you with whether or not to use STI or an enum. The fundamental decision you need to make surrounds business logic associated with partners and clients.
If most of the business logic is shared, then it makes sense to use an enum. Let me give you an example. In one of my projects, I have a User model. All users can do basic things on the site. However, we also have school_admin users and class_admin users. Admins of course have greater access to portions of the site, but from a business logic perspective, there are only a couple of relations and a couple of methods that are unique to an admin and not shared by a user.
Since 95% of the business logic is shared between normal users and admins, I elected to keep them all in one class. I used an enum called role to distinguish users:
# in the User model
enum :role, [:graduate, :school_admin, :class_admin]
In the users table I have a column of type int called role. The enum opens up a bunch of helper methods, such as class_admin?, to make the business logic work.
Your case may be different. It seems clients and partners may have greater differences in business logic in your app. I don't know, but it sounds like there are some fundamental differences in their roles. You will have to decide how much business logic is shared between them and how much is different. If they are different enough, then STI makes sense.
Furthermore, you may want to go the STI route if you would like to take advantage of inheritance in methods. For example: you may have a contact_verified? method where partner.contact_verified? has different business logic (email and phone maybe) than client.contact_verified? (email only). A weak example maybe, but you get the idea. Of course, you could accomplish the same thing with a conditional inside contact_verified? when using the single model approach.
You are correct that the some in the Rails community tend to be down on STI. So do not make the decision to go the STI route lightly. However, I have used STI successfully in some apps with few STI-related problems.
It all depends on how much business logic is shared and if you want to take advantage of inheritance. The decision is ultimately up to you.

Tom Aranda gives a good framework for deciding on an approach (and it seems you should probably use one table). Your "biggest" requirement, however, could easily be solved in SQL with a UNION query even if you decided to use two tables.
SELECT * FROM (SELECT id, 'Client' as type, first_name, last_name FROM clients
UNION SELECT id, 'Partner' as type, first_name, last_name FROM partners) AS t1
ORDER BY last_name LIMIT 25;
You could go further and INNER JOIN the email addresses and phone numbers as well.

Related

Model which belongs_to can be several models?

This is the situation that I have now:
Adword has_many pages
page belongs_to Adword
Right now I want to place another Ad provider, and that will also generate pages. So, ideally I want somehow to say something like (this is where I am lost):
AdProvider has_many pages
page belongs_to AdProvider
Where AdProvider could be Adwords, X, Y - What is the correct way to approach this kind of situations in Rails? RIght now I just have a adword_id attribute in Page, but there will be pages which are not associated to Adword but to another Ad provider.
Use Polymorphic association.
From the doc:
With polymorphic associations, a model can belong to more than one
other model, on a single association. For example, you might have a
picture model that belongs to either an employee model or a product
model.
I use Single Table Inheritance (STI) when I want to accomplish this.
"STI should be considered when dealing with model classes that share much of the same functionality and data fields, but you as the developer may want more granular control over extending or adding to each class individually. Rather than duplicate the code over and over for multiple tables (and not being DRY) or forego the flexibility of adding idiosyncratic functionality or methods, STI permits you to use keep your data in a single table while writing specialized functionality."
You can read more about it here.
As suggested by #emaillenin you could use polimorphic associations to deal with this.
You can see more about that here
But you should have in mind that classes sharing this type of association should follow a pattern, or else you could end up with tons of if statements in order to avoid their differences.
However by your description of the relation between AdProvider and Adword, I'm guessing that you are using a STI (Single Table Inheritance) system, so those similarities are already implied.
This article will help you to choose between Polymorphism and STI approach: STI
Below you can find a quote from the article:
"
3. Do the objects have similar data but different behavior?
How many database columns are shared by every model? If there are going to be many model-specific columns, you should consider Polymorphic Associations. On the other hand, if a Car, Truck, and Motorcycle all have the same attributes, eg:
color
engine_size
price
but different method implementations, eg:
drivetrain_weight # sums different components
value_after_depreciation(years) # uses different depreciation rates
drivers_license_certifications # references different certifications
then Single Table Inheritance is probably a good design choice. If there are only minor differences in a few methods, you may want to “cheat” and go with a Single Class."

Is STI appropriate if model can be multiple subclasses at the same time?

When is the correct time to use STI in a Rails app? After a lot of reading (including this excellent Railscast) I'm still unsure what is the best approach for my needs.
This (contrived) example illustrates my dilemma.
Lets say a User class could categorized in several sub-classes, including Doctors and Patients. These are more than simple roles, with key data and logic differences.
A Doctor has certain database fields (e.g., qualification, speciality), certain view logic (e.g. if User.doctor? then display list of patients), and certain logic and roles (e.g. can manage patient records).
A Patient has different database fields (e.g., blood type), view logic (e.g. if User.patient? then display list of treatments), and logic (e.g. can edit appointments).
Both Doctors and Patients have :username, :email, :password fields in common, as well as a considerable amount of logic throughout the remainder of the app (e.g., User has_many :comments, :messages, etc ).
From my reading, the fact that Patients and Doctors have different logic requirements suggests STI may be appropriate. But with different database fields, polymorphic may be a better approach.
BUT....
A User may be both a Doctor and a Patient simultaneously.
Is there a "best approach" to this problem?
STI uses a single column to determine the object's type.
At first glance this would indicate you'd need to have User, Doctor, Patient, and DoctorPatient classes, which to me, seems a little loopy. It could potentially get even loopier if other types are added.
Moving Doctor, Patient, etc. functionality into another class (and table) seems like it'd make more sense, without knowing anything more, but other folks may have more useful input.

Two simple questions about STI implementation

I am using STI for my user models. I have an User class, and the subclasses Seller and Customer. A user cannot be both, so I think STI is alright for this case.
I have two questions:
How do I restrict the creation of User instances, so only sellers or customers can be created? I guess I could validate the presence of Type, but that doesnt feel very right to me.
Can I have extra, different fields for sellers and customers with STI? How?
You can only allow saving of users and customers by doing:
validates_inclusion_of :type, :in => [:customer, :user]
As far as your second question, the answer is: add columns. If you add columns for the customer the user model will ignore them, so no big deal. It depends on your use case though, in some cases it's best to avoid STI.

Authenticating multiple models with Authlogic

I have multiple user account types, each with different information. For example, business contacts link to businesses, school administrators and students link to schools. Students have physical addresses, but business contacts and school admins use the organizations address. There's other information unique to each type as well.
I'm leaning toward separate tables for students, school admins and business contacts, but using Authlogic, I have a Users table with authentication information (all need to log in).
The question is really how best to link this single authentication table with the individual profiles. It seems like a one-to-one relationship requires a single table (e.g. Users <-> Students or Users <-> Business_contacts). I want to have something of a "one-to-one-of-the-following" relationship (Users <-> Students or Business_contacts). Is there a good way to do this using a join table or other construct?
Alternatively, I could unify the common information in the Users table and come up with a "profile" column in XML to support the unique information. My thought was that keeping everything in clean DB columns would simplify selects/inserts.
Thoughts, ideas?
business contacts link to businesses, school administrators and students link to schools.
This means you have different roles of users rather than different users.
Students have physical addresses, but business contacts and school admins use the organizations address
There's other information unique to each type as well.
Which means different roles have different data and behavior, leading to different OO classes.
The question is really how best to link this single authentication table with the individual profiles.
I want to have something of a "one-to-one-of-the-following" relationship (Users <-> Students or Business_contacts). Is there a good way to do this using a join table or other construct?
The simplest model I can think of if the following:
User (Username, Email, Password, Names and other common data for every user)
Participant, abstract. (has relation to one user - defines the role of the participant, behavior and its common data). Should provide the interface for participants.
Student (inherits from Participant, adds its own behavior and data)
SchoolAdministrator (inherits from participant)
And so on
In the Object Oriented World (non RDBMS) this has a simple advantage: polymorphism. Having a user you don't need to know exactly who he is. You just do things like this:
user.participant.can_manage_stuff?
user.participant.order_book(harry_potter)
And the appropriate actions will take place which will be implemented in Student, SchoolAdministrator or other class inherited from Participant.
Another things is that it is very easy to add new roles for the system. Just inherit from Participant and implement its interface.
Now, when Object Oriented Design is done, let's have a look at the data storage. I assume you use RDBMS.
So now you have 2 types of links:
One-to-one association between User and Participant.
Generalization (inheritance) between Participant and Student, SchoolAdministrator.
Implementation of 1st is as simple as having has_one and/or belongs_to association for the following table (via participant_id column):
users:
id | username | email | password | etc | participant_id (non-null) FK_TO_participants table |
So you can easily implement that.
Now, the 2nd link can be implemented in RDBMS in number of different wasy.
But fortunately or unfortunately ActiveRecord supports only one option to do that. And it is hierarchy per table mapping strategy that uses discriminator column to distinguish the type (stored in type column by convention).
So you will have the 2nd table that looks like this:
perticipants:
id | type | student_card_number (null) | administrator_number (null) | etc
This table will have all the columns for all possible participants. This is the simplest and easiest implementation of hierarchy in DB. But not the most optimal from some points of view. As I said with ActiveRecords it is only one option available anyway.
So as a result of this design you will end up with 2 database tables (users, participants) and at least 3 classes (User, Participant, Student).
And, of course, you can vary from here a lot but that should deliver my point.
And yes, please no XML in database, don't waste your nerves and valuable personal time.
Cheers.
If you really need seperate models for each role you use, then I would recomend to use polymorphic association (read more here). So:
# User model
belongs_to :owner, :polymorphic => true
# Student model
has_one :user, :as => :owner
# Any other model
has_one :user, :as => :owner
And you need to add to users table:
t.integer :owner_id
t.string :owner_type

validates_uniqueness_of scope on other table

A User belongs to Groups, a Group belongs to a Company
I want to verify that the username of the user is unique per company. So my database can have multiple dublicate usernames as long as they belong to a different company.
A logical step would be to use validates_uniqueness _of scope and use the company id (stored in groups). Is there a way scope can get access to other tables or do I need to solve this ina different way?
thanks
In this design, I see a problem, if a User can belong to one group, and that a group can belong to one company. But the association of User and Company is very indirect, where as in reality it should be direct, independent of its association with the group.
So for example to change companies a User switches groups, not companies, which is not entirely right. It may make sense in your context I am not sure.
So storing company information in the User table would make sense ( as for uniqueness test as well, the relationship is direct).
and
validation_uniqueness_of :user, :scope=> company_id
would work.
Update
I am not asking you to denormalize tables, the group still may be tightly tied to the company. But that relationship has nothing to do with the user-company
My point is basically, User object should have direct visibility into the Company class, not through "Group".
That means adding a company column into your table, thats all, and establishing a direct relationship b/w user and Company.
and it wouldn't be so much of a management hassle.Plus, I sort of expect, to write something like
"user.company"
Instead of
"user.group.company".
In any case. Thats just my view, I don't know the whole context, so my advice is based on what I saw in your question
Making a custom validation rules is probably the best way to tackle this problem.
I had this issue as well and came to research an answer. I used Tarscher answer to come up with the following solution.
validates_each :username do |record, attr, value|
if User.joins(:group).where('username = ? and groups.company_id = ?', record.username, record.group.company_id).present?
record.errors.add attr, 'This username is already taken by some on in your company'
end
end

Resources