What should I do to make a Polymorphic Relationships in RoR? - ruby-on-rails

I have a user table, and have a role column, that identify their role. Now, I want to add a "Student" table, that have the student's student number, and I also want to add a "Teacher", to store the teacher's salary.
Both the Student and Teacher are the subclass of User. Now, I added a role column already in the User table, "T" = Teacher, and "S" = Student. Now, I want to add "Student" and "Teacher" in the database. What should I do? Generate scaffold or migration only? Also, it is necessary to modify my models? or I just need to modify the controller only??
Thank you.

This sounds more like a job for Rails' single table inheritance. It's really easy to use. Instead of using a separate table to store roles. Inherit your student and teacher models from User.
for example:
User < ActiveRecord::Base; ... end
Student < User; ... end
Teacher < User; ... end
Rails automatically creates a type column and stores the class there.
User.all will return all users Student.all will return users where the type is matches Student and Teacher.all will do the same for all teachers.
You can read more about single table inheritance at the ActiveRecord documentation, and here.

As nuclearsandwich said, this is a situation that calls for rails STI (single table inheritance), not polymorphic associations.
Just to highlight the difference:
1) Singe table inheritance is used when you have different models that are very similar to each other with minor differences. STI lumps all models in the same database table, with fields (columns) for all the models that use this table. There is also a "type" field which indicates which model this record belongs to.
Your case is a perfect example of STI. You have user, student, and teacher models, they share a lot of common fields and functionality, you just want an extra field for the student and the teacher.
2) Polymorphic association is used when you have a model that will be associated with different models. For example, imagine you have an "Address" model. Also you have a "Company" and a "User" model, both of which can have an address.
In your Address model, you can specify
belongs_to :user
belongs_to :company
And then make sure if the address belongs to a company not to call address.user and vice-versa. However, a better way to do it would be through a polymorphic association. In your address model you do
belongs_to :addressable, :polymorphic => true
and in your user and company model you add
has_one :address, :as => :addressable
This allows the address model to polymorph (literally: transform into many) its association, and be connected with multiple different models through a single association.

Related

setting up a belongs_to relation when the foreign key is stored in metadata

In a Rails 4 application, I have an STI model that stores metadata in a jsonb column.
Base Class:
class Post < ActiveRecord::Base
...
end
Subclass:
class JobPost < Post
# has a jsonb column for metadata
end
One of the data attributes in the metadata column of a JobPost is a foreign_key reference to another table (company_id). I'd like to add a belongs_to :company reference in the JobPost model. It seems like this should be possible by doing something like
class JobPost < Post
belongs_to :company do
Company.find_by_id self.metadata['company_id']
end
end
but that doesn't appear to work. Help?
Note: I am not necessarily intent on using belongs_to rather than writing needed methods like def company by hand, but I do need a way to eager load companies when listing job posts. If there's a way to do that eager loading without a belongs_to relation I'm all ears.
Update1
I have also tried the following, which doesn't appear to work either:
class JobPost < Post
belongs_to :company, foreign_key: "(posts.metadata->>'company_id')::integer".to_sym
end
Update2
To be more clear about my intentions and need:
1) A JobPost belongs_to a Company, but a Post (and other subclasses of Post) does not. I'd prefer not to jankily add the company_id column to the posts table when it won't be used by the other subclasses.
2) A JobPost could justify having it's own table (perhaps the relationship with a Company is enough to justify it). There are reasons why this wouldn't be ideal, but if that's the only answer I'm open to it. I'd, however, like a more definitive "what you're trying to do can't be done" response before going down this road, though.
The primary question is whether you can customize belongs_to so that it uses the metadata column rather than expecting the foreign key to be a column in the table.
The secondary question is whether you can eager load companies alongside job posts without having that belongs_to relation set up.
EDIT
UPD 2
You need to add "company_id" column to the base class of your STI table. If JobPost inherits from Post, and it should have "company_id" then add the "company_id" column to Post (base table).
Remember STI stands for "Single Table Inheritance" so there is only one table on database schema level. Imagine a column of a Post table, where few data records are the foreign key entries for Companies with company_id and what about the other records of this column with non JobPost subclass types, are they null/empty? Hence the relationship is defined with parent STI table and subclass inherits these relations. Additional type column in STI defines the subclass type.
Check here
You may need to dig further on Polymorphic classes instead of STI if both JobPost and Post have relationship with Company, else create two separate model, as they tend do have some unique relationships and column fields.
UPD
Based on updated ask
app/model/company.rb
class Company < ActiveRecord::Base
has_many :posts
delegate :jobposts, to: :posts
end
app/model/post.rb
class Post < ActiveRecord::Base
belongs_to :company
self.inheritance_column = :ptype
scope :job_posts, -> { where(ptype: 'JobPost') }
def self.ptype
%w(JobPost)
end
end
app/models/jobpost.rb
class JobPost < Post; end
Create a company
company = Company.create!(company_params)
Create some posts and add them to the company
company.posts << JobPost.new(jobpost_params)
To fetch jobpost by company relationship
company.job_posts
In case you are storing company_id in jsonb in any which column, just format your jobpost_params hash input accordingly and it should do the deed for you
OLD ASK
To find by primary key
Company.find(id)
In your case, id is self.metadata['company_id']
To find by other keys
Company.find_by(key: value)
Company.find_by_id is no more recommended
Please remove do and end after belongs_to in your model, instead in your controller you can write:
Jobpost.all.each do |x|
# your do
end
regarding foreign key, as rails is convention over configuration, it by default includes company_id reference to Jobpost which you can change in your Company.rb model

Making two associations to same model column in Rails

I'm pretty new to rails. I'm trying to setup associations to another model in Rails.
I have a User model with columns: id, user_name, email
Now I am trying to create another model Expense which has associations to the user model. The purpose of this model is to create an expense and associate the expense with two different users from the same model User. The association to the second user is to split the amount between the two users.
This is what I'm intending to do while creating the model Expense:
$ rails generate model Expense amount:decimal user:references split_with:references
Now how do I associate split_with to the User model, since both references associate to the same User model's id of two users?
You can refer to the same model two different ways:
class Expense < ActiveRecord::Base
belongs_to :user
belongs_to :approving_user,
class_name: 'User'
end
This would require columns user_id and approving_user_id to be present. You can always adjust the generated migration and model code to match this.
As always be sure that index: true are set on these columns.

How do you model user roles when the roles have different associations with external models

How do you model user roles when the roles have different associations with external models?
I have two roles, teacher and parent. teachers have an associated school, parents don't. It makes it hard to model teachers and parents in a single model.
I would prefer not to model them separately as their state (fields) are the same, and they both have a "has and belongs to many" (HABTM) association with children.
I have tried using single table inheritance (STI) which solved my immediate problem, but ultimately caused a great deal more problems. I was hoping for a "composition over inheritance" solution.
if you want to keep one class, conditional relations could help you
class User < AR
has_one :school, conditions: { role: 'teacher' }
end
Still kinda quirky, but better than STI
A conditional has_one relation could work for you:
class Role < ActiveRecord::Base
has_one :school, conditions: { name: 'Teacher' }
belongs_to :user
And then you should be able to do:
user = User.first
user.role # let's say he is a Teacher
user.role.school # => should return the school
Some documentation about has_one:
http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_one

Rails 3. How to setup User model to act as different roles?

I setup roles for the User model, for now, I just have a role string column for the users table. The value could be admin, salesperson, instructor or student.
Now I have a ScheduledSession model that an Instructor is supposed to teach. I have an instructor_id column, which is supposed to have an id from the users table (an user with a role of instructor).
So how can I setup the association in the models? in the User model is doesn't seem right to add has_many :scheduled_sessions because if the role is a salesperson or an admin, it just doesn't feel right. or setup something like belongs_to :user, :as instructor in the ScheduledSession model, but I get error Unknown key :as.
How would you set this up?
You want to set up your belongs_to slightly differently in the ScheduledSession model.
belongs_to :instructor, :class_name => 'User, :foreign_key => 'user_id'
This allows you to refer to the instructor of a ScheduledSession object and have it point to an entity in the User table.
There's no way around having a has_many relationship on your User model if you want to pursue a role based User with each user having a single role. Without it, you'll have no mechanism to retrieve the scheduled_sessions associated with a user.
An alternative might be to use Single Table inheritance here, which would allow you to create Admin users who don't have SchededSessions, but Instructors who do. You'll still store all of these in the same table, but ActiveRecord will make it easier for you to compartmentalize them.

How can use rails polymorphic for objects that actually have fields?

Let's say I have a User model and I want to have different user roles. In a single application, you can simple store the role a string, or as another model and that's fine.
But what if you want to store more associations for the individual models? For example, let's say Admins contain links to collections and belongs to many collections that regular users should not be associated to? In fact, other users will be linked to a whole other set of models that Admins do not have.
Do we put all of the associations in the User object and just ignore them based on the type of the user? Or do we start subclassing User (like in Hibernate) to only contain the associations and model logic for that user type?
What is the way to do this in rails? Thanks!
I would suggest using Rails Single Table Inheritance. Essentially, you'll have a users table in your database, and a root User model. Multiple models (one for each "role") can inherit from User, and have their own associations:
# Regular users have no associations
class User < ActiveRecord::Base
end
# Admins have collections and notes
class Admin < User
has_many :collections
has_many :notes
end
# Editors only have notes
class Editor < User
has_many :notes
end

Resources