Using Rails 7
I am using a Single Table Inheritance (STI) to store some very simple associations. The source object uses has_many associations with the STI models. Following some advice in question https://stackoverflow.com/a/45681641/1014251 I am using a polymorphic relationship in the join model. This works really well with one annoyance:
When creating a join models the source type is being taken from the root STI class rather than the actual source.
class Guidance < ApplicationRecord
has_many :guidance_details
has_many :themes, through: :guidance_details, source: :detailable, source_type: "Theme"
end
class Detail < ApplicationRecord
has_many :guidance_details, as: :detailable
has_many :guidances, through: :guidance_details
end
class GuidanceDetail < ApplicationRecord
belongs_to :detailable, polymorphic: true
belongs_to :guidance
end
class Theme < Detail
end
If I create a new GuidanceDetail and do not specify the detailable_source
the system inserts "Detail" rather than "Theme".:
guidance_detail = GuidanceDetail.create(guidance: Guidance.first, detailable: Theme.first)
guidance_detail.detailable_type => "Detail"
The detailable type should be "Theme".
To fix this currently I am having to specify the detailable_type each time I create a new GuidanceDetail.
I have tried specifying the has_many association of the child object directly, but get the same result:
class Theme < Detail
has_many :guidance_details, as: :detailable
end
theme = Theme.first
guidance = Guidance.first
guidance.themes << theme
Outputs:
GuidanceDetail Create (1.3ms) INSERT INTO "guidance_details" ("detailable_id", "guidance_id", "position", "created_at", "updated_at", "detailable_type") VALUES ($1, $2, $3, $4, $5, $6) RETURNING "id" [["detailable_id", 11], ["guidance_id", 1], ["position", nil], ["created_at", "2024-11-14 10:24:19.785623"], ["updated_at", "2024-11-14 10:24:19.785623"], ["detailable_type", "Detail"]]
As you see: "detailable_type" is "Detail".
From my understanding, in order to store the class name "Theme"
, it appears Detail
needs to be a abstract_class
.
ActiveRecord::Associations::BelongsToPolymorphicAssociation sets the foreign_type
to the polymorphic_name
polymorphic_name
is defined as:
def polymorphic_name
store_full_class_name ? base_class.name : base_class.name.demodulize
end
and base_class
def set_base_class # :nodoc:
@base_class = if self == Base
self
else
unless self < Base
raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
end
if superclass == Base || superclass.abstract_class?
self
else
superclass.base_class
end
end
end
So you can see that base_class.name
will only use the actual class name if the class is the base class or the superclass is an abstract class. At least this is how I tracked it around.