From 3e2822684f90bfc03f35e0a172a60175a62102d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Thu, 17 Oct 2019 16:43:36 -0400 Subject: [PATCH] Add methods to the public API of ActiveRecord::Inheritance Those methods make possible to extend STI and Polymorphic associations. They are useful for cases where you renamed a class and the name of the class doesn't match the data in the database. You can now implement those methods in your model to load records with name of classes that don't exist anymore. A simple implementation would look like: class Animal < ActiveRecord::Base @@old_names = { "Lion" => "BigCat" } def self.sti_name name = super @@old_names[name] || name end def self.sti_class_for(type_name) @@old_names.inverse[type_name]&.constantize || super end end --- .../belongs_to_polymorphic_association.rb | 2 +- activerecord/lib/active_record/inheritance.rb | 42 +++++++++++++------ 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb index 9ae452e7a1..6f17b755db 100644 --- a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb @@ -6,7 +6,7 @@ module ActiveRecord class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc: def klass type = owner[reflection.foreign_type] - type.presence && type.constantize + type.presence && owner.class.polymorphic_class_for(type) end def target_changed? diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index 5ca48fa18c..bb67f7f1b0 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -162,14 +162,40 @@ module ActiveRecord defined?(@abstract_class) && @abstract_class == true end + # Returns the value to be stored in the inheritance column for STI. def sti_name store_full_sti_class ? name : name.demodulize end + # Returns the class for the provided +type_name+. + # + # It is used to find the class correspondent to the value stored in the inheritance column. + def sti_class_for(type_name) + if store_full_sti_class + ActiveSupport::Dependencies.constantize(type_name) + else + compute_type(type_name) + end + rescue NameError + raise SubclassNotFound, + "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " \ + "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " \ + "Please rename this column if you didn't intend it to be used for storing the inheritance class " \ + "or overwrite #{name}.inheritance_column to use another column for that information." + end + + # Returns the value to be stored in the polymorphic type column for Polymorphic Associations. def polymorphic_name base_class.name end + # Returns the class for the provided +name+. + # + # It is used to find the class correspondent to the value stored in the polymorphic type column. + def polymorphic_class_for(name) + name.constantize + end + def inherited(subclass) subclass.instance_variable_set(:@_type_candidates_cache, Concurrent::Map.new) super @@ -224,22 +250,12 @@ module ActiveRecord def find_sti_class(type_name) type_name = base_class.type_for_attribute(inheritance_column).cast(type_name) - subclass = begin - if store_full_sti_class - ActiveSupport::Dependencies.constantize(type_name) - else - compute_type(type_name) - end - rescue NameError - raise SubclassNotFound, - "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " \ - "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " \ - "Please rename this column if you didn't intend it to be used for storing the inheritance class " \ - "or overwrite #{name}.inheritance_column to use another column for that information." - end + subclass = sti_class_for(type_name) + unless subclass == self || descendants.include?(subclass) raise SubclassNotFound, "Invalid single-table inheritance type: #{subclass.name} is not a subclass of #{name}" end + subclass end