Postgres: Many-to-many vs. multiple columns vs. array column - ruby-on-rails

I need help designing complex user permissions within a Postgres database. In my Rails app, each user will be able to access a unique set of features. In other words, there are no pre-defined "roles" that determine which features a user can access.
In almost every controller/view, the app will check whether or not the current user has access to different features. Ideally, the app will provide ~100 different features and will support 500k+ users.
At the moment, I am considering three different options (but welcome alternatives!) and would like to know which option offers the best performance. Thank you in advance for any help/suggestions.
Option 1: Many-to-many relationship
By constructing a many-to-many relationship between the User table and a Feature table, the app could check whether a user has access to a given feature by querying the join table.
E.g., if there is a record in the join table that connects user1 and feature1, then user1 has access to feature1.
Option 2: Multiple columns
The app could represent each feature as a boolean column on the User table. This would avoid querying multiple tables to check permissions.
E.g., if user1.has_feature1 is true, then user1 has access to feature1.
Option 3: Array column
The app could store features as strings in a (GIN-indexed?) array column on the User table. Then, to check whether a user has access to a feature, it would search the array column for the given feature.
E.g., if user1.features.include? 'feature1' is true, then user1 has access to feature1.

Many-to-many relationships are the only viable option here. There is a reason why they call it a relational database.
Why?
Joins are actually not that expensive.
Multiple columns - The number of columns in your tables will be ludicris and it will be true developer hell. As each feature adds a migration the amount of churn in your codebase will be silly.
Array column - Using an array column may seem like an attractive alternative until you realize that its actually just a marginal improvement over stuffing things into a comma seperated string. you have no referential integrety and none of the code organization benefits that come from have having models that represent the entities in your application.
Oh and every time a feature is yanked you have to update every one of those 500k+ users. VS just using CASCADE.
class Feature
has_many :user_features
has_many :users, through: :user_features
end
class UserFeature
belongs_to :user
belongs_to :feature
end
class User
has_many :user_features
has_many :features, through: :user_features
def has_feature?(name)
features.exist?(name: name)
end
end

Related

Database record containing multiple entries in one column

I am working on a web app written in rails. It is currently running on heroku with a postgres database.
I am supposed to add a feature where users may enter up to three codes for each one of the user's students. The codes themselves are irrelevant, they are simply strings that will be entered into the database.
This brings me to my dilemma. I am unsure of how to best store the codes in terms of their relationship to the student table. My original thought was to use the rails method serialize to store up to three codes in an array, but I have read that more often than not, storing data in an array in a database is not what you want to do.
Should I create a new table "codes" and set up a has_many relationship with the "students" table? Or is there a more preferable away to set up this relationship?
Given your situation, this sounds like the most reasonable approach to have a Code model and then setup has_many association with Student model.
student has_many codes and
code belongs_to student.

Has Many Through Relationship in MongoDB with ONE query

I want to create the following two pages, using ONE query each:
A list of all courses for a particular student
A list of all students for a particular course
I would like the recreate the following with MongoDB (or another NoSQL solution)
class Student < ActiveRecord::Base
has_many :assignments
has_many :courses, :through=>:assignments
end
class Course < ActiveRecord::Base
has_many :assignments
has_many :students, :through=>:assignments
end
class Assignment &lt ActiveRecord::Base
belongs_to :course
belongs_to :student
With a relational database, I can accomplish this by eager loading the association.
However in Mongo, when I create by schema, I can either chose to embed or link the student data in the course, or embed or link the course data in the student.
If I chose to embed the student data in the course document, I can easily pull all students for a particular course (one document). However, if I want to find all courses a particular student is taking, I will have to query MongoDB N times (where N is the number of courses a student is taking)!
In a web application backed by a relational database, making N calls to the database to load a page isn't an option. It needs to be done in a constant number of queries. Is this not the case with MongoDB? Is there a way to structure my documents such that I can load the two pages above with ONE query?
If I chose to embed the student data in the course document, I
can easily pull all students for a particular course (one document).
However, if I want to find all courses a particular student is taking,
I will have to query MongoDB N times (where N is the number of courses
a student is taking)!
If you keep your information highly normalized, that is correct. There are other possibilities, though, such as maintaining an array of current course IDs in the student record rather than relying on relationship queries.
In a web application backed by a relational database, making N calls
to the database to load a page isn't an option. It needs to be done
in a constant number of queries. Is this not the case with MongoDB?
Is there a way to structure my documents such that I can load the two
pages above with ONE query?
MongoDB is not a relational database, and intentionally does not support joins. The MongoDB data modelling approach is more akin to a data warehouse, where some data is denormalized or embedded in related data to remove the need for joins.
There is a client driver notion of relationships using the convention of Database References (DBRefs) but there is no server support for hydrating references and these will result in additional queries.
Multiple queries aren't necessarily bad .. but the general design goal would be to model your data to most effectively balance speed of inserts, updates, and reads based on your application's common use cases.
For more information on data modelling, see:
MongoDB Schema Design .. includes some best practices and related links
Designing MongoDB Schemas with Embedded, Non-Embedded and Bucket Structures

Rails and multiple profiles

I have the app, where user can have one of several different profiles. Some of profile data are always the same (like first and last name, gender etc). Other fields may vary (for example, doctor can have license number and text about himself, while patient can have phone number etc).
I found approach, that fits pretty well, but still have some doubts. The point of my approach looks like this:
User model contains a lot of system-specific data, controlled by Devise and has_one :person
Person model contains common profile data and belongs_to :profile, :polymorphic => true
Doctor/Patient/Admin/etc contains more specific profile data and has_one :person, :as => :profile
With this approach i can simply check in Person model:
def doctor?
self.profile_type == 'Doctor'
end
But there is the few things doesn't give me a rest.
First one is performance. This approach requires a lot of additional joins. For example, for reading doctor's license number, first/last name and email at the same time it will generate 2 additional joins.
Second one is different ids for profile-specific model (i.e. Doctor) and for Person/User models. There will be situations, when user with ID=1 will have Patient relation with different ID, but it would be logical to have same ID for all this associated models.
Maybe you guys will see any more pitfalls in this approach? Is there any better solution for my situation?
You've got four basic patterns you can use here that might work.
Omnirecord
In this model you have all the possible fields in a single record and then use STI to differentiate between profile types. This is the simplest to implement but looks the most messy as few people will have all fields populated. Keep in mind that NULL string fields don't take up a lot of database space, typically one bit per column, so having a lot of them isn't a big deal.
Optional Joins
In this model you create a number of possible linkages to different profile types, like doctor_profile_id linking to a DoctorProfile, patient_profile_id linking to a PatientProfile, and so forth. Since each relationship is spelled out in a specific field, you can even enforce foreign key constraints if you prefer, and indexing is easy. This can come in handy when a single record requires multiple different profiles to be associated with it, as in the case of a patient that's also a doctor.
Polymorphic Join
In this model you link to a specific profile type and profile id using the :polymorphic option, much like you've suggested. Indexing is more complicated and foreign keys are impossible. You're also limited to having one and only one profile. These tend to work as a starting point but may prove to be trouble down the road when you get a doctor + patient requirement.
Key/Value Store
In this model you abandon all efforts to organize things into singular records and instead build an associated ProfileField and ProfileValue table. The ProfileField identifies what fields are available on what kinds of profiles, such as label, allowed values, data type and so forth, while the ProfileValue is used to store specific values for specific profiles.
class User < ActiveRecord::Base
has_many :profile_fields
end
class ProfileField < ActiveRecord::Base
has_many :profile_values
end
class ProfileValue < ActiveRecord::Base
belongs_to :user
belongs_to :profile_field
end
Since this one is wide open you can allow the site administrator to redefine what field are required, add new fields, and so on, without having to make a schema change.

A database design for variable column names

I have a situation that involves Companies, Projects, and Employees who write Reports on Projects.
A Company owns many projects, many reports, and many employees.
One report is written by one employee for one of the company's projects.
Companies each want different things in a report. Let's say one company wants to know about project performance and speed, while another wants to know about cost-effectiveness. There are 5-15 criteria, set differently by each company, which ALL apply to all of that company's project reports.
I was thinking about different ways to do this, but my current stalemate is this:
To company table, add text field criteria, which contains an array of the criteria desired in order.
In the report table, have a company_id and columns criterion1, criterion2, etc.
I am completely aware that this is typically considered horrible database design - inelegant and inflexible. So, I need your help! How can I build this better?
Conclusion
I decided to go with the serialized option in my case, for these reasons:
My requirements for the criteria are simple - no searching or sorting will be required of the reports once they are submitted by each employee.
I wanted to minimize database load - where these are going to be implemented, there is already a large page with overhead.
I want to avoid complicating my database structure for what I believe is a relatively simple need.
CouchDB and Mongo are not currently in my repertoire so I'll save them for a more needy day.
This would be a great opportunity to use NoSQL! Seems like the textbook use-case to me. So head over to CouchDB or Mongo and start hacking.
With conventional DBs you are slightly caught in the problem of how much to normalize your data:
A sort of "good" way (meaning very normalized) would look something like this:
class Company < AR::Base
has_many :reports
has_many :criteria
end
class Report < AR::Base
belongs_to :company
has_many :criteria_values
has_many :criteria, :through => :criteria_values
end
class Criteria < AR::Base # should be Criterion but whatever
belongs_to :company
has_many :criteria_values
# one attribute 'name' (or 'type' and you can mess with STI)
end
class CriteriaValues < AR::Base
belongs_to :report
belongs_to :criteria
# one attribute 'value'
end
This makes something very simple and fast in NoSQL a triple or quadruple join in SQL and you have many models that pretty much do nothing.
Another way is to denormalize:
class Company < AR::Base
has_many :reports
serialize :criteria
end
class Report < AR::Base
belongs_to :company
serialize :criteria_values
def criteria
self.company.criteria
end
# custom code here to validate that criteria_values correspond to criteria etc.
end
Related to that is the rather clever way of serializing at least the criteria (and maybe values if they were all boolean) is using bit fields. This basically gives you more or less easy migrations (hard to delete and modify, but easy to add) and search-ability without any overhead.
A good plugin that implements this is Flag Shih Tzu which I've used on a few projects and could recommend.
Variable columns (eg. crit1, crit2, etc.).
I'd strongly advise against it. You don't get much benefit (it's still not very searchable since you don't know in which column your info is) and it leads to maintainability nightmares. Imagine your db gets to a few million records and suddenly someone needs 16 criteria. What could have been a complete no-issue is suddenly a migration that adds a completely useless field to millions of records.
Another problem is that a lot of the ActiveRecord magic doesn't work with this - you'll have to figure out what crit1 means by yourself - now if you wan't to add validations on these fields then that adds a lot of pointless work.
So to summarize: Have a look at Mongo or CouchDB and if that seems impractical, go ahead and save your stuff serialized. If you need to do complex validation and don't care too much about DB load then normalize away and take option 1.
Well, when you say "To company table, add text field criteria, which contains an array of the criteria desired in order" that smells like the company table wants to be normalized: you might break out each criterion in one of 15 columns called "criterion1", ..., "criterion15" where any or all columns can default to null.
To me, you are on the right track with your report table. Each row in that table might represent one report; and might have corresponding columns "criterion1",...,"criterion15", as you say, where each cell says how well the company did on that column's criterion. There will be multiple reports per company, so you'll need a date (or report-number or similar) column in the report table. Then the date plus the company id can be a composite key; and the company id can be a non-unique index. As can the report date/number/some-identifier. And don't forget a column for the reporting-employee id.
Any and every criterion column in the report table can be null, meaning (maybe) that the employee did not report on this criterion; or that this criterion (column) did not apply in this report (row).
It seems like that would work fine. I don't see that you ever need to do a join. It looks perfectly straightforward, at least to these naive and ignorant eyes.
Create a criteria table that lists the criteria for each company (company 1 .. * criteria).
Then, create a report_criteria table (report 1 .. * report_criteria) that lists the criteria for that specific report based on the criteria table (criteria 1 .. * report_criteria).

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.

Resources