diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 8d283c1599e..000b691cb8a 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,12 @@ +* Trying to set a config key with the same name of a method now raises: + + ```ruby + config.load_defaults = 7.0 + # NoMethodError: Cannot assign to `load_defaults`, it is a configuration method + ``` + + *Xavier Noria* + * Deprecate `secrets:edit/show` and remove `secrets:setup` `bin/rails secrets:setup` has been deprecated since Rails 5.2 in favor of diff --git a/railties/lib/rails/railtie/configuration.rb b/railties/lib/rails/railtie/configuration.rb index 872bd684c72..788d2fee40a 100644 --- a/railties/lib/rails/railtie/configuration.rb +++ b/railties/lib/rails/railtie/configuration.rb @@ -92,9 +92,17 @@ module Rails end private + def actual_method?(key) + !@@options.key?(key) && respond_to?(key) + end + def method_missing(name, *args, &blk) if name.end_with?("=") - @@options[:"#{name[0..-2]}"] = args.first + key = name[0..-2].to_sym + if actual_method?(key) + raise NoMethodError.new("Cannot assign to `#{key}`, it is a configuration method") + end + @@options[key] = args.first elsif @@options.key?(name) @@options[name] else diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index b4a4b0c9445..56e9054b41a 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -4371,6 +4371,17 @@ module ApplicationTests assert_equal true, ActiveRecord.run_after_transaction_callbacks_in_order_defined end + test "raises if configuration tries to assign to an actual method" do + remove_from_config '.*config\.load_defaults.*\n' + add_to_config 'config.load_defaults = "7.0"' + + error = assert_raises(NoMethodError) do + app "development" + end + + assert_match(/Cannot assign to `load_defaults`, it is a configuration method/, error.message) + end + private def set_custom_config(contents, config_source = "custom".inspect) app_file "config/custom.yml", contents diff --git a/railties/test/configuration/dynamic_options_test.rb b/railties/test/configuration/dynamic_options_test.rb new file mode 100644 index 00000000000..90b8f6a152b --- /dev/null +++ b/railties/test/configuration/dynamic_options_test.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require "active_support" +require "active_support/test_case" +require "active_support/testing/autorun" +require "rails/railtie/configuration" + +module RailtiesTest + class DynamicOptionsTest < ActiveSupport::TestCase + class Configuration < Rails::Railtie::Configuration + def reset_options + @@options = {} + end + end + + setup do + @config = Configuration.new + @config.reset_options + end + + test "arbitrary keys can be set, reset, and read" do + @config.foo = 1 + assert_equal 1, @config.foo + + @config.foo = 2 + assert_equal 2, @config.foo + end + + test "raises NoMethodError if the key is unset and the method does not exist" do + assert_raises(NoMethodError) do + @config.unset_key + end + end + + test "raises NoMethodError with an informative message if assigning to an existing method" do + error = assert_raises(NoMethodError) do + @config.eager_load_namespaces = 1 + end + + assert_match(/Cannot assign to `eager_load_namespaces`, it is a configuration method/, error.message) + end + end +end