Merge pull request #37429 from gmcgibbon/opt_into_has_many_inverse

Opt into has many inverse
This commit is contained in:
Gannon McGibbon 2019-10-15 13:15:39 -04:00 committed by GitHub
commit 41bbd7cd0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 91 additions and 170 deletions

View File

@ -109,7 +109,8 @@ module ActiveRecord
end
def invertible_for?(record)
inverse_reflection_for(record)
inverse = inverse_reflection_for(record)
inverse && (inverse.has_one? || ActiveRecord::Base.has_many_inversing)
end
def stale_state

View File

@ -286,6 +286,8 @@ module ActiveRecord
end
def target=(record)
return super unless ActiveRecord::Base.has_many_inversing
case record
when Array
super

View File

@ -128,6 +128,8 @@ module ActiveRecord
mattr_accessor :reading_role, instance_accessor: false, default: :reading
mattr_accessor :has_many_inversing, instance_accessor: false, default: false
class_attribute :default_connection_handler, instance_writer: false
self.filter_attributes = []

View File

@ -28,6 +28,7 @@ module ActiveRecord
config.active_record.use_schema_cache_dump = true
config.active_record.maintain_test_schema = true
config.active_record.has_many_inversing = false
config.active_record.sqlite3 = ActiveSupport::OrderedOptions.new
config.active_record.sqlite3.represent_boolean_as_integer = nil

View File

@ -607,7 +607,7 @@ class InverseBelongsToTests < ActiveRecord::TestCase
assert_equal f.description, m.face.description, "Description of face should be the same after changes to newly-created-parent-owned instance"
end
def test_should_try_to_set_inverse_instances_when_the_inverse_is_a_has_many
def test_should_not_try_to_set_inverse_instances_when_the_inverse_is_a_has_many
i = interests(:trainspotting)
m = i.man
assert_not_nil m.interests
@ -615,14 +615,31 @@ class InverseBelongsToTests < ActiveRecord::TestCase
assert_not_nil iz
assert_equal i.topic, iz.topic, "Interest topics should be the same before changes to child"
i.topic = "Eating cheese with a spoon"
assert_equal i.topic, iz.topic, "Interest topics should be the same after changes to child"
assert_not_equal i.topic, iz.topic, "Interest topics should not be the same after changes to child"
iz.topic = "Cow tipping"
assert_equal i.topic, iz.topic, "Interest topics should be the same after changes to parent-owned instance"
assert_not_equal i.topic, iz.topic, "Interest topics should not be the same after changes to parent-owned instance"
end
def test_does_not_trigger_association_callbacks_on_set_when_the_inverse_is_a_has_many
man = interests(:trainspotting).man_with_callbacks
assert_not_predicate man, :add_callback_called?
def test_with_has_many_inversing_should_try_to_set_inverse_instances_when_the_inverse_is_a_has_many
with_has_many_inversing do
i = interests(:trainspotting)
m = i.man
assert_not_nil m.interests
iz = m.interests.detect { |_iz| _iz.id == i.id }
assert_not_nil iz
assert_equal i.topic, iz.topic, "Interest topics should be the same before changes to child"
i.topic = "Eating cheese with a spoon"
assert_equal i.topic, iz.topic, "Interest topics should be the same after changes to child"
iz.topic = "Cow tipping"
assert_equal i.topic, iz.topic, "Interest topics should be the same after changes to parent-owned instance"
end
end
def test_with_has_many_inversing_does_not_trigger_association_callbacks_on_set_when_the_inverse_is_a_has_many
with_has_many_inversing do
man = interests(:trainspotting).man_with_callbacks
assert_not_predicate man, :add_callback_called?
end
end
def test_child_instance_should_be_shared_with_replaced_via_accessor_parent
@ -709,7 +726,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
assert_equal old_inversed_man.object_id, new_inversed_man.object_id
end
def test_should_try_to_set_inverse_instances_when_the_inverse_is_a_has_many
def test_should_not_try_to_set_inverse_instances_when_the_inverse_is_a_has_many
i = interests(:llama_wrangling)
m = i.polymorphic_man
assert_not_nil m.polymorphic_interests
@ -717,14 +734,31 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
assert_not_nil iz
assert_equal i.topic, iz.topic, "Interest topics should be the same before changes to child"
i.topic = "Eating cheese with a spoon"
assert_equal i.topic, iz.topic, "Interest topics should be the same after changes to child"
assert_not_equal i.topic, iz.topic, "Interest topics should not be the same after changes to child"
iz.topic = "Cow tipping"
assert_equal i.topic, iz.topic, "Interest topics should be the same after changes to parent-owned instance"
assert_not_equal i.topic, iz.topic, "Interest topics should not be the same after changes to parent-owned instance"
end
def test_does_not_trigger_association_callbacks_on_set_when_the_inverse_is_a_has_many
man = interests(:llama_wrangling).polymorphic_man_with_callbacks
assert_not_predicate man, :add_callback_called?
def test_with_has_many_inversing_should_try_to_set_inverse_instances_when_the_inverse_is_a_has_many
with_has_many_inversing do
i = interests(:llama_wrangling)
m = i.polymorphic_man
assert_not_nil m.polymorphic_interests
iz = m.polymorphic_interests.detect { |_iz| _iz.id == i.id }
assert_not_nil iz
assert_equal i.topic, iz.topic, "Interest topics should be the same before changes to child"
i.topic = "Eating cheese with a spoon"
assert_equal i.topic, iz.topic, "Interest topics should be the same after changes to child"
iz.topic = "Cow tipping"
assert_equal i.topic, iz.topic, "Interest topics should be the same after changes to parent-owned instance"
end
end
def test_with_has_many_inversing_does_not_trigger_association_callbacks_on_set_when_the_inverse_is_a_has_many
with_has_many_inversing do
man = interests(:llama_wrangling).polymorphic_man_with_callbacks
assert_not_predicate man, :add_callback_called?
end
end
def test_trying_to_access_inverses_that_dont_exist_shouldnt_raise_an_error

View File

@ -79,6 +79,14 @@ module ActiveRecord
model.reset_column_information
model.column_names.include?(column_name.to_s)
end
def with_has_many_inversing
old = ActiveRecord::Base.has_many_inversing
ActiveRecord::Base.has_many_inversing = true
yield
ensure
ActiveRecord::Base.has_many_inversing = old
end
end
class PostgreSQLTestCase < TestCase

View File

@ -154,6 +154,10 @@ module Rails
end
when "6.1"
load_defaults "6.0"
if respond_to?(:active_record)
active_record.has_many_inversing = true
end
else
raise "Unknown version #{target_version.to_s.inspect}"
end

View File

@ -477,7 +477,7 @@ module Rails
def delete_new_framework_defaults
unless options[:update]
remove_file "config/initializers/new_framework_defaults_6_0.rb"
remove_file "config/initializers/new_framework_defaults_6_1.rb"
end
end

View File

@ -1,45 +0,0 @@
# Be sure to restart your server when you modify this file.
#
# This file contains migration options to ease your Rails 6.0 upgrade.
#
# Once upgraded flip defaults one by one to migrate to the new default.
#
# Read the Guide for Upgrading Ruby on Rails for more info on each option.
# Don't force requests from old versions of IE to be UTF-8 encoded.
# Rails.application.config.action_view.default_enforce_utf8 = false
# Embed purpose and expiry metadata inside signed and encrypted
# cookies for increased security.
#
# This option is not backwards compatible with earlier Rails versions.
# It's best enabled when your entire app is migrated and stable on 6.0.
# Rails.application.config.action_dispatch.use_cookies_with_metadata = true
# Change the return value of `ActionDispatch::Response#content_type` to Content-Type header without modification.
# Rails.application.config.action_dispatch.return_only_media_type_on_content_type = false
# Return false instead of self when enqueuing is aborted from a callback.
# Rails.application.config.active_job.return_false_on_aborted_enqueue = true
# Send Active Storage analysis and purge jobs to dedicated queues.
# Rails.application.config.active_storage.queues.analysis = :active_storage_analysis
# Rails.application.config.active_storage.queues.purge = :active_storage_purge
# When assigning to a collection of attachments declared via `has_many_attached`, replace existing
# attachments instead of appending. Use #attach to add new attachments without replacing existing ones.
# Rails.application.config.active_storage.replace_on_assign_to_many = true
# Use ActionMailer::MailDeliveryJob for sending parameterized and normal mail.
#
# The default delivery jobs (ActionMailer::Parameterized::DeliveryJob, ActionMailer::DeliveryJob),
# will be removed in Rails 6.1. This setting is not backwards compatible with earlier Rails versions.
# If you send mail in the background, job workers need to have a copy of
# MailDeliveryJob to ensure all delivery jobs are processed properly.
# Make sure your entire app is migrated and stable on 6.0 before using this setting.
# Rails.application.config.action_mailer.delivery_job = "ActionMailer::MailDeliveryJob"
# Enable the same cache key to be reused when the object being cached of type
# `ActiveRecord::Relation` changes by moving the volatile information (max updated at and count)
# of the relation's cache key into the cache version to support recycling cache key.
# Rails.application.config.active_record.collection_cache_versioning = true

View File

@ -0,0 +1,10 @@
# Be sure to restart your server when you modify this file.
#
# This file contains migration options to ease your Rails 6.1 upgrade.
#
# Once upgraded flip defaults one by one to migrate to the new default.
#
# Read the Guide for Upgrading Ruby on Rails for more info on each option.
# Support for inversing belongs_to -> has_many Active Record associations.
# Rails.application.config.active_record.has_many_inversing = true

View File

@ -2144,42 +2144,6 @@ module ApplicationTests
assert_equal({}, Rails.application.config.my_custom_config)
end
test "represent_boolean_as_integer is deprecated" do
remove_from_config '.*config\.load_defaults.*\n'
app_file "config/initializers/new_framework_defaults_6_0.rb", <<-RUBY
Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true
RUBY
app_file "app/models/post.rb", <<-RUBY
class Post < ActiveRecord::Base
end
RUBY
app "development"
assert_deprecated do
force_lazy_load_hooks { Post }
end
end
test "represent_boolean_as_integer raises when the value is false" do
remove_from_config '.*config\.load_defaults.*\n'
app_file "config/initializers/new_framework_defaults_6_0.rb", <<-RUBY
Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = false
RUBY
app_file "app/models/post.rb", <<-RUBY
class Post < ActiveRecord::Base
end
RUBY
app "development"
assert_raises(RuntimeError) do
force_lazy_load_hooks { Post }
end
end
test "config_for containing ERB tags should evaluate" do
app_file "config/custom.yml", <<-RUBY
development:
@ -2287,6 +2251,18 @@ module ApplicationTests
assert_equal "https://example.org/", last_response.location
end
test "ActiveSupport::MessageEncryptor.use_authenticated_message_encryption can be configured via config.active_support.use_authenticated_message_encryption" do
remove_from_config '.*config\.load_defaults.*\n'
app_file "config/initializers/new_framework_defaults_6_1.rb", <<-RUBY
Rails.application.config.active_record.has_many_inversing = true
RUBY
app "development"
assert_equal true, ActiveRecord::Base.has_many_inversing
end
test "ActiveSupport::MessageEncryptor.use_authenticated_message_encryption is true by default for new apps" do
app "development"
@ -2301,18 +2277,6 @@ module ApplicationTests
assert_equal false, ActiveSupport::MessageEncryptor.use_authenticated_message_encryption
end
test "ActiveSupport::MessageEncryptor.use_authenticated_message_encryption can be configured via config.active_support.use_authenticated_message_encryption" do
remove_from_config '.*config\.load_defaults.*\n'
app_file "config/initializers/new_framework_defaults_6_0.rb", <<-RUBY
Rails.application.config.active_support.use_authenticated_message_encryption = true
RUBY
app "development"
assert_equal true, ActiveSupport::MessageEncryptor.use_authenticated_message_encryption
end
test "ActiveSupport::Digest.hash_digest_class is Digest::SHA1 by default for new apps" do
app "development"
@ -2327,18 +2291,6 @@ module ApplicationTests
assert_equal Digest::MD5, ActiveSupport::Digest.hash_digest_class
end
test "ActiveSupport::Digest.hash_digest_class can be configured via config.active_support.use_sha1_digests" do
remove_from_config '.*config\.load_defaults.*\n'
app_file "config/initializers/new_framework_defaults_6_0.rb", <<-RUBY
Rails.application.config.active_support.use_sha1_digests = true
RUBY
app "development"
assert_equal Digest::SHA1, ActiveSupport::Digest.hash_digest_class
end
test "custom serializers should be able to set via config.active_job.custom_serializers in an initializer" do
class ::DummySerializer < ActiveJob::Serializers::ObjectSerializer; end
@ -2365,18 +2317,6 @@ module ApplicationTests
assert_equal true, ActionView::Helpers::FormTagHelper.default_enforce_utf8
end
test "ActionView::Helpers::FormTagHelper.default_enforce_utf8 can be configured via config.action_view.default_enforce_utf8" do
remove_from_config '.*config\.load_defaults.*\n'
app_file "config/initializers/new_framework_defaults_6_0.rb", <<-RUBY
Rails.application.config.action_view.default_enforce_utf8 = true
RUBY
app "development"
assert_equal true, ActionView::Helpers::FormTagHelper.default_enforce_utf8
end
test "ActionView::Template.finalize_compiled_template_methods is true by default" do
app "test"
assert_deprecated do
@ -2413,18 +2353,6 @@ module ApplicationTests
assert_equal false, ActiveJob::Base.return_false_on_aborted_enqueue
end
test "ActiveJob::Base.return_false_on_aborted_enqueue can be configured in the new framework defaults" do
remove_from_config '.*config\.load_defaults.*\n'
app_file "config/initializers/new_framework_defaults_6_0.rb", <<-RUBY
Rails.application.config.active_job.return_false_on_aborted_enqueue = true
RUBY
app "development"
assert_equal true, ActiveJob::Base.return_false_on_aborted_enqueue
end
test "ActiveStorage.queues[:analysis] is :active_storage_analysis by default" do
app "development"
@ -2468,18 +2396,6 @@ module ApplicationTests
assert_equal true, ActionDispatch::Response.return_only_media_type_on_content_type
end
test "ActionDispatch::Response.return_only_media_type_on_content_type can be configured in the new framework defaults" do
remove_from_config '.*config\.load_defaults.*\n'
app_file "config/initializers/new_framework_defaults_6_0.rb", <<-RUBY
Rails.application.config.action_dispatch.return_only_media_type_on_content_type = false
RUBY
app "development"
assert_equal false, ActionDispatch::Response.return_only_media_type_on_content_type
end
test "ActionMailbox.logger is Rails.logger by default" do
app "development"
@ -2566,18 +2482,6 @@ module ApplicationTests
assert_equal ActionMailer::DeliveryJob, ActionMailer::Base.delivery_job
end
test "ActionMailer::Base.delivery_job can be configured in the new framework defaults" do
remove_from_config '.*config\.load_defaults.*\n'
app_file "config/initializers/new_framework_defaults_6_0.rb", <<-RUBY
Rails.application.config.action_mailer.delivery_job = "ActionMailer::MailDeliveryJob"
RUBY
app "development"
assert_equal ActionMailer::MailDeliveryJob, ActionMailer::Base.delivery_job
end
test "ActiveRecord::Base.filter_attributes should equal to filter_parameters" do
app_file "config/initializers/filter_parameters_logging.rb", <<-RUBY
Rails.application.config.filter_parameters += [ :password, :credit_card_number ]

View File

@ -212,7 +212,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_new_application_doesnt_need_defaults
run_generator
assert_no_file "config/initializers/new_framework_defaults_6_0.rb"
assert_no_file "config/initializers/new_framework_defaults_6_1.rb"
end
def test_new_application_load_defaults
@ -269,14 +269,14 @@ class AppGeneratorTest < Rails::Generators::TestCase
app_root = File.join(destination_root, "myapp")
run_generator [app_root]
assert_no_file "#{app_root}/config/initializers/new_framework_defaults_6_0.rb"
assert_no_file "#{app_root}/config/initializers/new_framework_defaults_6_1.rb"
stub_rails_application(app_root) do
generator = Rails::Generators::AppGenerator.new ["rails"], { update: true }, { destination_root: app_root, shell: @shell }
generator.send(:app_const)
quietly { generator.send(:update_config_files) }
assert_file "#{app_root}/config/initializers/new_framework_defaults_6_0.rb"
assert_file "#{app_root}/config/initializers/new_framework_defaults_6_1.rb"
end
end