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
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 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
I have three models: Company, User and Employment. Each Company has many users though their Employments, and a user might belong to several companies through his employments.
Now, the tricky part: let's say my User1 belongs to 2 companies. He is SUPER_ADMIN in the first compnay, but just BASIC_USER in the second one. What would be the best way to define his roles ?
I used to have a simple has_many relationship between Company and User, which allowed me to simply set a is_admin attr on my User, but obviously this won't do with the new HMT relationship.
I thought of defining an array of IDs for each company, that would include the IDs of each admin user, but I'm pretty sure there is a cleaner way around.
In above scenario, you can use intermediate table i.e. Employment table to save all details of a user and it's associate company.
As Employment table will have ids of both user & company it will be easy for you to keep extra information related to user and company. Just add role column in this and use this to get information
I have a standard devise user model with the usual fields.
This is for a situation where people are either looking for a place to stay, or they have a place to stay. So I have two categories of user that a person can be. These two categories are very distinct (i.e. a person looking for a place to stay will have very different fields to a person who has a place to stay).
So a User has:
User: name, email, password, profile_id
A User can also have a Profile (i.e. they are looking for a house).
Profile: age, sexuality, religion, occupation
That's what I have at the moment. Now I need to change that slightly, so a User can have a profile OR... they can have a House (i.e. they have a house and are looking for more people):
House: price_per_week, address, etc
How best to model this in ActiveRecord? Polymorphic association of some kind?
I've found in general that polymorphic relationships don't work well over time if the objects they are modeling are even mildly different. For your case I'd recommend keeping the two objects separate.
In general, the best way is to consider the way you want to retrieve the data. For example, I'd imagine you want to access both:
#user.house
or
#user.profile
So I'd recommend beginning by setting up relationships (that can be optional) between the users table and both the profiles and houses table. I'd also add a type field that can be used to determine which of the two types the users are.
This allows users to be of either type and allows them to have both a profile and a house.
So both houses and profiles belong_to users, and users have_many (or have_one) houses and profiles
My Rails application uses STI where I have different types of companies and persons. For example I have suppliers, manufacturers and customers as types of Company. I have also employees, contacts and customers as types of People.
Now I want to refer to a Customer which can either be a Company Customer or a Person Customer. Which method can I use/should I use to combine these two different entities into one? So I can refer to a Customer, from an Order?
You could either use:
Order
belongs_to :company
belongs_to :person
end
And have two foreign keys - and then add some validations to make sure one of them is filled in, and then maybe add a 'customer' method which returns either the related company or person, depending which is used.
OR, create a separate Customer model (and table), which has those two same two foreign keys, and then Order can simply belong_to :customer. This second method may be preferably if you want to store additional data at the customer level (such as credit limit, or billing details), and may be cleaner long-term.
Alternative, you could reconsider your business logic, and insist that all orders belongs to a Person, even if that person is an employee of a Company and is purchasing on behalf of the company.
F