How to create HABTM association with STI model sub-classes? - ruby-on-rails

Lets say I have a STI model called Company. It has three sub-classes Firm, Client and PriorityClient.
class Company < ActiveRecord::Base
scope :firms_n_clients, -> { where(type: %w(Firm Client)) }
end
class Firm < Company; end
class Client < Company; end
class PriorityClient < Company; end
I have another model called Country. Now I want to create a has_and_belongs_to_many association between Country and firms_n_clients(only Firm and Client type of Company). How would it be?
Thanks in advance.

has_and_belongs_to_many associations accept scopes. Some of them are discussed in the Ruby on Rails documentation. Assuming the necessary join table exists, the association can be established like so:
class Country < ActiveRecord::Base
has_and_belongs_to_many :companies, -> { where(type: %w(Firm Client)) }
end
class Firm < Company
has_and_belongs_to_many :countries
end
class Client < Company
has_and_belongs_to_many :countries
end
Please note the duplicate Code in Client and Firm. This is on purpose, because it reveals explicitly that Clients and Firms have and belong to countries, and PriorityClients do not.
I have not tested the below code, but an even better way to modify the HABTM Association would be to merge the the firms_n_clients scope:
class Country < ActiveRecord::Base
has_and_belongs_to_many :companies, -> { merge(Company.firms_n_clients) }
end
This has several advantages: The Country model doesn't need to know about the different Company types, and modifying the scope will affect the association as well.

Related

How to create has_many relationship from apartment public schema to

My model setup
class User < AR
has_many :books
...
end
class Book < AR
belongs_to :user
...
end
Books are in separate apartments and users are in the public schema.
I would like to add a shared model called assignment (to assign a book to a user) in the public schema that will have the following setup
class User < AR
has_many :books
has_many :assignments
...
end
class Book < AR
belongs_to :user
has_one :assignment
...
end
class Assignment < AR
belongs_to :user
belongs_to :book
...
end
The problem lies with the belongs_to :book portion in that the Assignment model is shared, giving no way to easily specify the schema that the book is in. There is also the possibility of id collisions among the books across different schemas which complicates the process further. As I see it I could:
1) Use scopes, specifying the apartment schema for the book
2) Use custom getters and setters for the book attribute in assignment, and fetch the required objects in the getter.
3) Put the assignment foreign key in the books model, which would be the simplest but not as clean (I want to be able to get the book_id/schema without a join)
4) Distribute the assignments model to the apartment schemas, which would be difficult as I must collect the assignments and sync them to an outside data source.
What is the best solution in this situation?

How to model invoice that can belong to either a person or a company?

In my Ruby on Rails application I have invoices which can belong to either a person or a company. To make things even more complicated, a person can also belong to a company.
I figured that the right way to model this in Rails would be something like this:
class Invoice < ApplicationRecord
belongs_to :invoiceable, polymorphic: true
end
class Person < ApplicationRecord
has_many :invoices, as: :invoiceable
end
class Company < ApplicationRecord
has_many :invoices, as: :invoiceable
end
Is this the right way to do it?
And if so, can a person still belong to a company this way?
Thanks for any help.

HMT Naming Similarity Issue

I have two two models: Company and CompanyType. I need an association setup where a company can have and belong to many types. Based off the rails convention of naming join models I'm in sort've a bind. I can't name my join model CompanyType because that obviously exists. What do you do in these sort've situations?
class Company < ApplicationRecord
end
class CompanyType < ApplicationRecord
end
If you are not going to attach more stuff to it, you can skip creating the join model by defining a has_and_belongs_to_many association:
class Company < ApplicationRecord
has_and_belongs_to_many :company_types
end
class CompanyType < ApplicationRecord
has_and_belongs_to_many :companies
end
# No join model needed
You still need a migration to create the table though. See: http://apidock.com/rails/v4.2.1/ActiveRecord/Associations/ClassMethods/has_and_belongs_to_many
If you need the join model, you can follow the method explained in the same document, I guess.

Ruby on Rails Model / Database Associations

I have a user model, farmer model, doctor model, and education model.
A farmer has a user and many educations.
A doctor has a user and many educations.
How do I setup the database for the education model?
Should it have a farmer_id AND a doctor_id?
But a education cannot belong to a farmer AND and doctor at the same time. It's one or the other.
So my education database entry would either have a farmer_id OR a doctor_id filled in, but not both.
Is there a way to guarantee that only one of the ids could be filled in at a time?
Or is there a better way to associate these models?
Your help would be appreciated!
Oh, and don't worry about the names of the models (farmer, doctor, etc.). It's just an example.
I see two possible solutions for this scenario.
The first one is to make use of polymorphic associations for education. That could look like this:
class Farmer < ActiveRecord::Base
belongs_to :user
has_many :educations, :as => :profession
end
class Doctor < ActiveRecord::Base
belongs_to :user
has_many :educations, :as => :profession
end
class Education < ActiveRecord::Base
belongs_to :profession, :polymorphic => true
end
So instead of education having a doctor_id or a farmer_id it has one profession_id and one profession_type.
The second solution would be to make use of Single Table Inheritance. And in your scenrio, that could be accomplished by letting a Doctor be a User instead of belonging to a User. And of course the same thing for a Farmer. That could look like this:
class User < ActiveRecord::Base
has_many :educations
end
class Farmer < User
end
class Doctor < User
end
class Education < ActiveRecord::Base
belongs_to :user
end
And in this scenario you would add a type column to the User model to store what type of class it is and then only having a user_id in the Education model
I think its appropriate to have the relations this way based on roles.
Class User
has_one :role
has_many :educations
end
Class Role
#What ever roles you have.
#Farmer or Doctor
belongs_to :user
end
class Education
belongs_to :user
end
This way you will store the user_id in the education object, which solves your problem.

multiple models in Rails with a shared interface

I'm not sure of the best structure for a particular situation in Rails. We have several types of workshops. The administration of the workshops is the same regardless of workshop type, so the data for the workshops is in a single model. We collect feedback from participants about the workshops, and the questionnaire is different for each type of workshop. I want to access the feedback about the workshop from the workshop model, but the class of the associated model will depend on the type of workshop. If I was doing this in something other than Rails, I would set up an abstract class for WorkshopFeedback, and then have subclasses for each type of workshop: WorkshopFeedbackOne, WorkshopFeedbackTwo, WorkshopFeedbackThree. I'm unsure how to best handle this with Rails.
I currently have:
class Workshop < ActiveRecord::Base
has_many :workshop_feedbacks
end
class Feedback < ActiveRecord::Base
belongs_to :workshop
has_many :feedback_ones
has_many :feedback_twos
has_many :feedback_threes
end
class FeedbackOne < ActiveRecord::Base
belongs_to :feedback
end
class FeedbackTwo < ActiveRecord::Base
belongs_to :feedback
end
class FeedbackThree < ActiveRecord::Base
belongs_to :feedback
end
This doesn't seem like to the cleanest way to access the feedback from the workshop model, as accessing the correct feedback will require logic investigating the Workshop type and then choosing, for instance, #workshop.feedback.feedback_one.
Is there a better way to handle this situation? Would it be better to use a polymorphic association for feedback? Or maybe using a Module or Mixin for the shared Feedback interface?
Note: I am avoiding using Single Table Inheritance here because the FeedbackOne, FeedbackTwo, FeedbackThree models do not share much common data, so I would end up with a large sparsely populated table with STI.
I think the best solution is create an abstract class Workshop, and 3 subclasses Workshop1, Workshop2 and Workshop3.
Hence, each will have its set of feedbacks, Feedback1 to Workshop1, Feedback2 to Workshop2, ...
You can change the declaration in the subclasses as follows:
class Workshop1 < Workshop
has_many :feedbacks, :class_name => "Feedback1"
end
class Feedback1 < ActiveRecord::Base
belongs_to :workshop, :class_name => "Workshop1"
end
And in your application can use workshop.feedbacks and and feedback.workshop no matter what class the instance of the Workshop or Feedback belongs.
EDIT: You have three types of workshop with information in common, but each workshop has a specific kind of feedback. So it's bestter to use STI for Workshop, and not for Feedback.
You can subclass your models, like so:
class Workshop < ActiveRecord::Base
has_many :feedback_ones
has_many :feedback_twos
has_many :feedback_threes
#has_many :feedbacks # This MIGHT work, but is untested. I'm not at a dev setup to try.
end
class Feedback < ActiveRecord::Base
belongs_to :workshop
has_many :feedback_ones
has_many :feedback_twos
has_many :feedback_threes
end
class FeedbackOne < Feedback
belongs_to :feedback
end
class FeedbackTwo < Feedback
belongs_to :feedback
end
class FeedbackThree < Feedback
belongs_to :feedback
end
This is likely a better solution, and is cleaner. Your Feedback table needs to have a type column, which it uses to use one table for multiple classes. This blog post gives a good basic introduction to the concept of Single Table Inheritance.

Resources