diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 337fc8a35023c63c8b47be27d0f3d2e848b10821..b12af5bdb67deb59cbefcbcc2ad2d8484cbfd2d4 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,12 @@ +* Support storing demodulized class name for polymorphic type. + + Before Rails 6.1, storing demodulized class name is supported only for STI type + by `store_full_sti_class` class attribute. + + Now `store_full_class_name` class attribute can handle both STI and polymorphic types. + + *Ryuta Kamizono* + * Deprecate `rails db:structure:{load, dump}` tasks and extend `rails db:schema:{load, dump}` tasks to work with either `:ruby` or `:sql` format, depending on `config.active_record.schema_format` configuration value. diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index c38dd10bf339e0f1debe5f1907ae4cfb959434b7..d0ef768433f9cf9e626d5f9893e79b1b110b6fae 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -38,6 +38,8 @@ module Inheritance extend ActiveSupport::Concern included do + class_attribute :store_full_class_name, instance_writer: false, default: true + # Determines whether to store the full constant name including namespace when using STI. # This is true, by default. class_attribute :store_full_sti_class, instance_writer: false, default: true @@ -164,14 +166,14 @@ def abstract_class? # Returns the value to be stored in the inheritance column for STI. def sti_name - store_full_sti_class ? name : name.demodulize + store_full_sti_class && store_full_class_name ? 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 + if store_full_sti_class && store_full_class_name ActiveSupport::Dependencies.constantize(type_name) else compute_type(type_name) @@ -186,14 +188,18 @@ def sti_class_for(type_name) # Returns the value to be stored in the polymorphic type column for Polymorphic Associations. def polymorphic_name - base_class.name + store_full_class_name ? base_class.name : base_class.name.demodulize 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 + if store_full_class_name + ActiveSupport::Dependencies.constantize(name) + else + compute_type(name) + end end def inherited(subclass) diff --git a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb index 673d5f1dcf03d21ba5b64d06208f0ccad29393d4..c5cc94bb05d01391384b832dcebc4ed2f4ffa1ba 100644 --- a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb +++ b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb @@ -8,14 +8,10 @@ module Namespaced class Post < ActiveRecord::Base self.table_name = "posts" has_one :tagging, as: :taggable, class_name: "Tagging" - - def self.polymorphic_name - sti_name - end end end -module PolymorphicFullStiClassNamesSharedTest +module FullStiClassNamesSharedTest def setup @old_store_full_sti_class = ActiveRecord::Base.store_full_sti_class ActiveRecord::Base.store_full_sti_class = store_full_sti_class @@ -31,7 +27,7 @@ def teardown def test_class_names ActiveRecord::Base.store_full_sti_class = !store_full_sti_class post = Namespaced::Post.find_by_title("Great stuff") - assert_nil post.tagging + assert_equal @tagging, post.tagging ActiveRecord::Base.store_full_sti_class = store_full_sti_class post = Namespaced::Post.find_by_title("Great stuff") @@ -41,7 +37,7 @@ def test_class_names def test_class_names_with_includes ActiveRecord::Base.store_full_sti_class = !store_full_sti_class post = Namespaced::Post.includes(:tagging).find_by_title("Great stuff") - assert_nil post.tagging + assert_equal @tagging, post.tagging ActiveRecord::Base.store_full_sti_class = store_full_sti_class post = Namespaced::Post.includes(:tagging).find_by_title("Great stuff") @@ -51,7 +47,7 @@ def test_class_names_with_includes def test_class_names_with_eager_load ActiveRecord::Base.store_full_sti_class = !store_full_sti_class post = Namespaced::Post.eager_load(:tagging).find_by_title("Great stuff") - assert_nil post.tagging + assert_equal @tagging, post.tagging ActiveRecord::Base.store_full_sti_class = store_full_sti_class post = Namespaced::Post.eager_load(:tagging).find_by_title("Great stuff") @@ -62,15 +58,15 @@ def test_class_names_with_find_by post = Namespaced::Post.find_by_title("Great stuff") ActiveRecord::Base.store_full_sti_class = !store_full_sti_class - assert_nil Tagging.find_by(taggable: post) + assert_equal @tagging, Tagging.find_by(taggable: post) ActiveRecord::Base.store_full_sti_class = store_full_sti_class assert_equal @tagging, Tagging.find_by(taggable: post) end end -class PolymorphicFullStiClassNamesTest < ActiveRecord::TestCase - include PolymorphicFullStiClassNamesSharedTest +class FullStiClassNamesTest < ActiveRecord::TestCase + include FullStiClassNamesSharedTest private def store_full_sti_class @@ -78,11 +74,83 @@ def store_full_sti_class end end -class PolymorphicNonFullStiClassNamesTest < ActiveRecord::TestCase - include PolymorphicFullStiClassNamesSharedTest +class NonFullStiClassNamesTest < ActiveRecord::TestCase + include FullStiClassNamesSharedTest private def store_full_sti_class false end end + +module PolymorphicFullClassNamesSharedTest + def setup + @old_store_full_class_name = ActiveRecord::Base.store_full_class_name + ActiveRecord::Base.store_full_class_name = store_full_class_name + + post = Namespaced::Post.create(title: "Great stuff", body: "This is not", author_id: 1) + @tagging = post.create_tagging! + end + + def teardown + ActiveRecord::Base.store_full_class_name = @old_store_full_class_name + end + + def test_class_names + ActiveRecord::Base.store_full_class_name = !store_full_class_name + post = Namespaced::Post.find_by_title("Great stuff") + assert_nil post.tagging + + ActiveRecord::Base.store_full_class_name = store_full_class_name + post = Namespaced::Post.find_by_title("Great stuff") + assert_equal @tagging, post.tagging + end + + def test_class_names_with_includes + ActiveRecord::Base.store_full_class_name = !store_full_class_name + post = Namespaced::Post.includes(:tagging).find_by_title("Great stuff") + assert_nil post.tagging + + ActiveRecord::Base.store_full_class_name = store_full_class_name + post = Namespaced::Post.includes(:tagging).find_by_title("Great stuff") + assert_equal @tagging, post.tagging + end + + def test_class_names_with_eager_load + ActiveRecord::Base.store_full_class_name = !store_full_class_name + post = Namespaced::Post.eager_load(:tagging).find_by_title("Great stuff") + assert_nil post.tagging + + ActiveRecord::Base.store_full_class_name = store_full_class_name + post = Namespaced::Post.eager_load(:tagging).find_by_title("Great stuff") + assert_equal @tagging, post.tagging + end + + def test_class_names_with_find_by + post = Namespaced::Post.find_by_title("Great stuff") + + ActiveRecord::Base.store_full_class_name = !store_full_class_name + assert_nil Tagging.find_by(taggable: post) + + ActiveRecord::Base.store_full_class_name = store_full_class_name + assert_equal @tagging, Tagging.find_by(taggable: post) + end +end + +class PolymorphicFullClassNamesTest < ActiveRecord::TestCase + include PolymorphicFullClassNamesSharedTest + + private + def store_full_class_name + true + end +end + +class PolymorphicNonFullClassNamesTest < ActiveRecord::TestCase + include PolymorphicFullClassNamesSharedTest + + private + def store_full_class_name + false + end +end