I saw some threads on this already on Stack, but wanted a little more clarification.
I have seen many apps where there is a product model and category category model. This is a has and belongs to many association, or a has_many through association.
I have also seen many apps where there is a user model and an email_address model. Email_address belongs to user, but user can have many email addresses.
My question is, would there ever be a situation where you can lump all the email addresses or categories into the user and product models, respectively? So in your user model, you will have email_one, email_two, etc?
What are the pros and cons of breaking it into different models? Thanks.
If the attribute is simple, it's almost certainly best to keep it in a single model - you can even serialize the attribute so that it takes, for example, and array of email_addresses. BUT (big but) you may well want to add a lot more information to an email address - which one is the primary one, when was it last profiled, email last sent to .. etc etc. This of course is much easier to handle if you have a separate email address model. So perhaps the question is really 'when should i use serialized attributes?'. My own answer would be 'only if I'm sure that I am storing something in that field that I never want to add further attributes to'. Usually that means it is something pretty peripheral to the main application, and about which no-one cares very much ...
Related
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.
I’m working with Ruby on Rails, but this question applies to application/database model design in general. I want to model many types of people such as User (someone who can log in), Employee, and Customer. Each of these has common attributes like name and email that should be part of a Person superclass. Each type also has other more specific attributes. A Person can be one or more of these roles (someone can be both an employee and a customer).
In code, the appropriate structure seems like a superclass/subclass relationship, but from a database normalization perspective, there should be a Customer table that references the Person table for its common attributes.
I need to decide how to combine these two approaches.
For example, I want to be able to create a Customer by Customer.create(name: “Johnny Appleseed”, favorite_product: widget) and query Employee.where(email: “someone#company.com”), and avoid duplicating Person fields on every role model. That way I can change the way I model a Person without needing to update all the other tables with the same changes.
It would be better if you use Single Table Inheritance.
You can start from this tutorial https://samurails.com/tutorial/single-table-inheritance-with-rails-4-part-1/
This is what I want:
It is like an appointment system/personal schedule. For example, a doctor have 4 periods of working time(sessions) everyday, and in each session he will have some patient to meet(the number in the table field). So His schedule look like the picture above.
At first thought, it looks like: Every day has many sessions. And a session contains some other information like the number of patients, etc. (and do not need to set its specific time period. )
The problem is how to set this up? I just have no clue about what the model should like. How should Session relates to Time/Day? And it seems Day and Session are already created.
I feel this could just be done with Session model, the model could contain some time attribute to sort them like the mock-up above.
I have checked some calender gem/plugin( e.g fullcalender), but they does not seem to help this problem.
This could easily be done with a Sessions model with enough logic to enforce your rules. You will probably want to associate a Doctors model with a Patients model through Sessions, but I don't know enough about your needs to be sure.
You should review the Rails Guide on ActiveRecord Associations for details on how to create associations and for ideas that may help you. One of the examples (2.4 The has_many :through Association) involves doctors and patients and is similar to what you describe.
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.
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