Reputation: 1013
Rails 5
Looking on how to do a join table with has many through relationships where both sides are polymorphic and the relationships are just parents or children which could be of any class
I'm building a Laboratory Information Management System and inside this system there are approximately 30 "data modules" (each their own model) which fall into three different categories and can generally be parents or children to any of the other 30 data module classes
So lets say I have DataModuleA - DataModuleZ (ie 26 modules one for each letter)
I want any of those modules to be either a parent or a child to other modules and I want to be able to call .child_modules
or .parent_modules
on any of those objects and get a list of all the parents or children
Right now I have a join table module_relationships
create_table :module_relationships, id: :uuid do |t|
t.references :child_module, polymorphic: true, type: :uuid, index: {:name => 'index_module_relationships_on_child_type_and_id'}
t.references :parent_module, polymorphic: true, type: :uuid, index: {:name => 'index_module_relationships_on_parent_type_and_id'}
t.timestamps
end
And then an abstract base class which all of the modules can inherit from
class ApplicationModule < ApplicationRecord
## maybe should go back to being a concern where individual has many throughs are determined at the class level
self.abstract_class = true
has_many :child_module_relationships, class_name: "ModuleRelationship", foreign_key: "parent_module"
has_many :parent_module_relationships, class_name: "ModuleRelationship", foreign_key: "child_module"
# has_many :child_modules, through: :child_module_relationships ## doesnt work now
# has_many :parent_modules, through: :parent_module_relationships ## doesnt work now
def create_child_module_relationship(child_module)
ModuleRelationship.create(
parent_module_id: self.id,
parent_module_type: self.class.to_s,
child_module_id: child_module.id,
child_module_type: child_module.class.to_s
)
end
def create_parent_module_relationship(parent_module)
ModuleRelationship.create(
child_module_id: self.id,
child_module_type: self.class.to_s,
parent_module_id: parent_module.id,
parent_module_type: parent_module.class.to_s
)
end
end
Right now trying to call .child_modules
throws an error:
ActiveRecord::HasManyThroughAssociationPolymorphicSourceError (Cannot have a has_many :through association 'ApplicationModule#child_modules' on the polymorphic object 'ChildModule#child_module' without 'source_type'. Try adding 'source_type: "ChildModule"' to 'has_many :through' definition.)
Which more or less makes sense to me but I thought that was the whole point of defining the type on the join table so it would know where to look for the objects.
Should I just write a scope and do a nasty n+1 query for each ModuleRelationship Record?
Any other thoughts on how I could get the .child_modules
and .parent_modules
to return an a multi_model list of modules ?
Upvotes: 0
Views: 189
Reputation: 747
Not sure if I understand your question correctly, but here is something that maybe helps you to generate some ideas for your solution.
An example where I'm storing countries and link countries to companies:
class Admin::SystemValues::Country < ApplicationRecord
has_many :linked_countries, class_name: "Common::LinkedCountry"
has_many :companies, class_name: "Common::Company", through: :linked_countries,
source: :countryable, source_type: "Common::Company"
end
class Common::LinkedCountry < ApplicationRecord
# == Schema Information
#
# Table name: common_linked_countries
#
# id :bigint(8) not null, primary key
# country_id :integer not null
# countryable_id :bigint(8) not null
# countryable_type :string not null
# updated_at :datetime
#
# Indexes
#
# index_common_linked_countries_on_country_and_countryable (country_id,countryable_id,countryable_type) UNIQUE
#
belongs_to :countryable, polymorphic: true
belongs_to :country, class_name: 'Admin::SystemValues::Country', optional: true
end
class Common::Company < ApplicationRecord
has_many :linked_countries, class_name: 'Common::LinkedCountry', as: :countryable
has_many :countries, class_name: 'Admin::SystemValues::Country', through: :linked_countries
end
So basically I can link Country
to any model I want through country_id
. I can get my countries with Common::Company.first.countries
.
Maybe you can somehow store those DataModuleA - DataModuleZ (ie 26 modules one for each letter)
within one model and then do something like above. For example maybe you can store an attribute like letter
(a..z) and then you have all data in one model and table to query later.
Upvotes: 1