diff options
42 files changed, 222 insertions, 84 deletions
diff --git a/.travis.yml b/.travis.yml index d7544fecb6..e55ad1cde1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,7 +34,7 @@ before_install: - "[ -f /tmp/beanstalkd-1.10/Makefile ] || (curl -L https://github.com/kr/beanstalkd/archive/v1.10.tar.gz | tar xz -C /tmp)" - "pushd /tmp/beanstalkd-1.10 && make && (./beanstalkd &); popd" - "[[ -z $encrypted_0fb9444d0374_key && -z $encrypted_0fb9444d0374_iv ]] || openssl aes-256-cbc -K $encrypted_0fb9444d0374_key -iv $encrypted_0fb9444d0374_iv -in activestorage/test/service/configurations.yml.enc -out activestorage/test/service/configurations.yml -d" - - "[[ $GEM != 'av:ujs' ]] || nvm install node" + - "[[ $GEM != 'av:ujs' ]] || nvm install v10.3.0" # FIXME: Remove version lock. - "[[ $GEM != 'av:ujs' ]] || node --version" - "[[ $GEM != 'av:ujs' ]] || (cd actionview && npm install)" diff --git a/actioncable/lib/action_cable/subscription_adapter/postgresql.rb b/actioncable/lib/action_cable/subscription_adapter/postgresql.rb index e384ea4afb..50ec438c3a 100644 --- a/actioncable/lib/action_cable/subscription_adapter/postgresql.rb +++ b/actioncable/lib/action_cable/subscription_adapter/postgresql.rb @@ -14,7 +14,7 @@ module ActionCable end def broadcast(channel, payload) - with_connection do |pg_conn| + with_broadcast_connection do |pg_conn| pg_conn.exec("NOTIFY #{pg_conn.escape_identifier(channel_identifier(channel))}, '#{pg_conn.escape_string(payload)}'") end end @@ -31,14 +31,24 @@ module ActionCable listener.shutdown end - def with_connection(&block) # :nodoc: - ActiveRecord::Base.connection_pool.with_connection do |ar_conn| - pg_conn = ar_conn.raw_connection + def with_subscriptions_connection(&block) # :nodoc: + ar_conn = ActiveRecord::Base.connection_pool.checkout.tap do |conn| + # Action Cable is taking ownership over this database connection, and + # will perform the necessary cleanup tasks + ActiveRecord::Base.connection_pool.remove(conn) + end + pg_conn = ar_conn.raw_connection - unless pg_conn.is_a?(PG::Connection) - raise "The Active Record database must be PostgreSQL in order to use the PostgreSQL Action Cable storage adapter" - end + verify!(pg_conn) + yield pg_conn + ensure + ar_conn.disconnect! + end + def with_broadcast_connection(&block) # :nodoc: + ActiveRecord::Base.connection_pool.with_connection do |ar_conn| + pg_conn = ar_conn.raw_connection + verify!(pg_conn) yield pg_conn end end @@ -52,6 +62,12 @@ module ActionCable @listener || @server.mutex.synchronize { @listener ||= Listener.new(self, @server.event_loop) } end + def verify!(pg_conn) + unless pg_conn.is_a?(PG::Connection) + raise "The Active Record database must be PostgreSQL in order to use the PostgreSQL Action Cable storage adapter" + end + end + class Listener < SubscriberMap def initialize(adapter, event_loop) super() @@ -67,7 +83,7 @@ module ActionCable end def listen - @adapter.with_connection do |pg_conn| + @adapter.with_subscriptions_connection do |pg_conn| catch :shutdown do loop do until @queue.empty? diff --git a/actioncable/test/channel/base_test.rb b/actioncable/test/channel/base_test.rb index d368794f73..eb0e1673b0 100644 --- a/actioncable/test/channel/base_test.rb +++ b/actioncable/test/channel/base_test.rb @@ -5,7 +5,7 @@ require "minitest/mock" require "stubs/test_connection" require "stubs/room" -class ActionCable::Channel::BaseTest < ActiveSupport::TestCase +class ActionCable::Channel::BaseTest < ActionCable::TestCase class ActionCable::Channel::Base def kick @last_action = [ :kick ] diff --git a/actioncable/test/channel/broadcasting_test.rb b/actioncable/test/channel/broadcasting_test.rb index f184147c51..2cbfabc1d0 100644 --- a/actioncable/test/channel/broadcasting_test.rb +++ b/actioncable/test/channel/broadcasting_test.rb @@ -1,13 +1,10 @@ # frozen_string_literal: true require "test_helper" -require "active_support/testing/method_call_assertions" require "stubs/test_connection" require "stubs/room" -class ActionCable::Channel::BroadcastingTest < ActiveSupport::TestCase - include ActiveSupport::Testing::MethodCallAssertions - +class ActionCable::Channel::BroadcastingTest < ActionCable::TestCase class ChatChannel < ActionCable::Channel::Base end diff --git a/actioncable/test/channel/naming_test.rb b/actioncable/test/channel/naming_test.rb index 6f094fbb5e..45652d9cc9 100644 --- a/actioncable/test/channel/naming_test.rb +++ b/actioncable/test/channel/naming_test.rb @@ -2,7 +2,7 @@ require "test_helper" -class ActionCable::Channel::NamingTest < ActiveSupport::TestCase +class ActionCable::Channel::NamingTest < ActionCable::TestCase class ChatChannel < ActionCable::Channel::Base end diff --git a/actioncable/test/channel/periodic_timers_test.rb b/actioncable/test/channel/periodic_timers_test.rb index 8d9482577c..0c979f4c7c 100644 --- a/actioncable/test/channel/periodic_timers_test.rb +++ b/actioncable/test/channel/periodic_timers_test.rb @@ -4,11 +4,8 @@ require "test_helper" require "stubs/test_connection" require "stubs/room" require "active_support/time" -require "active_support/testing/method_call_assertions" - -class ActionCable::Channel::PeriodicTimersTest < ActiveSupport::TestCase - include ActiveSupport::Testing::MethodCallAssertions +class ActionCable::Channel::PeriodicTimersTest < ActionCable::TestCase class ChatChannel < ActionCable::Channel::Base # Method name arg periodically :send_updates, every: 1 diff --git a/actioncable/test/channel/rejection_test.rb b/actioncable/test/channel/rejection_test.rb index 897efeb65a..683eafcac0 100644 --- a/actioncable/test/channel/rejection_test.rb +++ b/actioncable/test/channel/rejection_test.rb @@ -5,7 +5,7 @@ require "minitest/mock" require "stubs/test_connection" require "stubs/room" -class ActionCable::Channel::RejectionTest < ActiveSupport::TestCase +class ActionCable::Channel::RejectionTest < ActionCable::TestCase class SecretChannel < ActionCable::Channel::Base def subscribed reject if params[:id] > 0 diff --git a/actioncable/test/channel/stream_test.rb b/actioncable/test/channel/stream_test.rb index ed42f1acd4..bfe1f92946 100644 --- a/actioncable/test/channel/stream_test.rb +++ b/actioncable/test/channel/stream_test.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "test_helper" -require "active_support/testing/method_call_assertions" require "minitest/mock" require "stubs/test_connection" require "stubs/room" @@ -164,8 +163,6 @@ module ActionCable::StreamTests end class StreamFromTest < ActionCable::TestCase - include ActiveSupport::Testing::MethodCallAssertions - setup do @server = TestServer.new(subscription_adapter: ActionCable::SubscriptionAdapter::Async) @server.config.allowed_request_origins = %w( http://rubyonrails.com ) diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb index 92fe59c803..e5f43488c4 100644 --- a/actioncable/test/client_test.rb +++ b/actioncable/test/client_test.rb @@ -7,7 +7,6 @@ require "websocket-client-simple" require "json" require "active_support/hash_with_indifferent_access" -require "active_support/testing/method_call_assertions" #### # 😷 Warning suppression 😷 @@ -28,8 +27,6 @@ WebSocket::Frame::Data.prepend Module.new { #### class ClientTest < ActionCable::TestCase - include ActiveSupport::Testing::MethodCallAssertions - WAIT_WHEN_EXPECTING_EVENT = 2 WAIT_WHEN_NOT_EXPECTING_EVENT = 0.5 diff --git a/actioncable/test/connection/authorization_test.rb b/actioncable/test/connection/authorization_test.rb index be41d510ff..f77e543435 100644 --- a/actioncable/test/connection/authorization_test.rb +++ b/actioncable/test/connection/authorization_test.rb @@ -1,12 +1,9 @@ # frozen_string_literal: true require "test_helper" -require "active_support/testing/method_call_assertions" require "stubs/test_server" class ActionCable::Connection::AuthorizationTest < ActionCable::TestCase - include ActiveSupport::Testing::MethodCallAssertions - class Connection < ActionCable::Connection::Base attr_reader :websocket diff --git a/actioncable/test/connection/base_test.rb b/actioncable/test/connection/base_test.rb index 9e480ab60d..6ffa0961bc 100644 --- a/actioncable/test/connection/base_test.rb +++ b/actioncable/test/connection/base_test.rb @@ -3,11 +3,8 @@ require "test_helper" require "stubs/test_server" require "active_support/core_ext/object/json" -require "active_support/testing/method_call_assertions" class ActionCable::Connection::BaseTest < ActionCable::TestCase - include ActiveSupport::Testing::MethodCallAssertions - class Connection < ActionCable::Connection::Base attr_reader :websocket, :subscriptions, :message_buffer, :connected diff --git a/actioncable/test/connection/client_socket_test.rb b/actioncable/test/connection/client_socket_test.rb index 07bdc7c52a..9176c7ac8b 100644 --- a/actioncable/test/connection/client_socket_test.rb +++ b/actioncable/test/connection/client_socket_test.rb @@ -2,11 +2,8 @@ require "test_helper" require "stubs/test_server" -require "active_support/testing/method_call_assertions" class ActionCable::Connection::ClientSocketTest < ActionCable::TestCase - include ActiveSupport::Testing::MethodCallAssertions - class Connection < ActionCable::Connection::Base attr_reader :connected, :websocket, :errors diff --git a/actioncable/test/connection/identifier_test.rb b/actioncable/test/connection/identifier_test.rb index a7e23b4cd8..707f4bab72 100644 --- a/actioncable/test/connection/identifier_test.rb +++ b/actioncable/test/connection/identifier_test.rb @@ -1,13 +1,10 @@ # frozen_string_literal: true require "test_helper" -require "active_support/testing/method_call_assertions" require "stubs/test_server" require "stubs/user" class ActionCable::Connection::IdentifierTest < ActionCable::TestCase - include ActiveSupport::Testing::MethodCallAssertions - class Connection < ActionCable::Connection::Base identified_by :current_user attr_reader :websocket diff --git a/actioncable/test/connection/stream_test.rb b/actioncable/test/connection/stream_test.rb index daf7c37c79..0f4576db40 100644 --- a/actioncable/test/connection/stream_test.rb +++ b/actioncable/test/connection/stream_test.rb @@ -1,13 +1,10 @@ # frozen_string_literal: true require "test_helper" -require "active_support/testing/method_call_assertions" require "minitest/mock" require "stubs/test_server" class ActionCable::Connection::StreamTest < ActionCable::TestCase - include ActiveSupport::Testing::MethodCallAssertions - class Connection < ActionCable::Connection::Base attr_reader :connected, :websocket, :errors diff --git a/actioncable/test/connection/subscriptions_test.rb b/actioncable/test/connection/subscriptions_test.rb index 7bc8c4241c..902085c5d6 100644 --- a/actioncable/test/connection/subscriptions_test.rb +++ b/actioncable/test/connection/subscriptions_test.rb @@ -1,11 +1,8 @@ # frozen_string_literal: true require "test_helper" -require "active_support/testing/method_call_assertions" class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase - include ActiveSupport::Testing::MethodCallAssertions - class Connection < ActionCable::Connection::Base attr_reader :websocket diff --git a/actioncable/test/server/base_test.rb b/actioncable/test/server/base_test.rb index 3b5931f0a4..d46debea45 100644 --- a/actioncable/test/server/base_test.rb +++ b/actioncable/test/server/base_test.rb @@ -3,11 +3,8 @@ require "test_helper" require "stubs/test_server" require "active_support/core_ext/hash/indifferent_access" -require "active_support/testing/method_call_assertions" - -class BaseTest < ActiveSupport::TestCase - include ActiveSupport::Testing::MethodCallAssertions +class BaseTest < ActionCable::TestCase def setup @server = ActionCable::Server::Base.new @server.config.cable = { adapter: "async" }.with_indifferent_access diff --git a/actioncable/test/server/broadcasting_test.rb b/actioncable/test/server/broadcasting_test.rb index 72cec26234..03c900182a 100644 --- a/actioncable/test/server/broadcasting_test.rb +++ b/actioncable/test/server/broadcasting_test.rb @@ -3,7 +3,7 @@ require "test_helper" require "stubs/test_server" -class BroadcastingTest < ActiveSupport::TestCase +class BroadcastingTest < ActionCable::TestCase test "fetching a broadcaster converts the broadcasting queue to a string" do broadcasting = :test_queue server = TestServer.new diff --git a/actioncable/test/stubs/test_adapter.rb b/actioncable/test/stubs/test_adapter.rb index 5f23a137ea..3b25c9168f 100644 --- a/actioncable/test/stubs/test_adapter.rb +++ b/actioncable/test/stubs/test_adapter.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class SuccessAdapter < ActionCable::SubscriptionAdapter::Base - class << self; attr_accessor :subscribe_called, :unsubscribe_called end - def broadcast(channel, payload) end diff --git a/actioncable/test/subscription_adapter/postgresql_test.rb b/actioncable/test/subscription_adapter/postgresql_test.rb index 1c375188ba..5fb26a8896 100644 --- a/actioncable/test/subscription_adapter/postgresql_test.rb +++ b/actioncable/test/subscription_adapter/postgresql_test.rb @@ -39,4 +39,27 @@ class PostgresqlAdapterTest < ActionCable::TestCase def cable_config { adapter: "postgresql" } end + + def test_clear_active_record_connections_adapter_still_works + server = ActionCable::Server::Base.new + server.config.cable = cable_config.with_indifferent_access + server.config.logger = Logger.new(StringIO.new).tap { |l| l.level = Logger::UNKNOWN } + + adapter_klass = Class.new(server.config.pubsub_adapter) do + def active? + !@listener.nil? + end + end + + adapter = adapter_klass.new(server) + + subscribe_as_queue("channel", adapter) do |queue| + adapter.broadcast("channel", "hello world") + assert_equal "hello world", queue.pop + end + + ActiveRecord::Base.clear_reloadable_connections! + + assert adapter.active? + end end diff --git a/actioncable/test/subscription_adapter/redis_test.rb b/actioncable/test/subscription_adapter/redis_test.rb index 63823d6ef0..3dc995331a 100644 --- a/actioncable/test/subscription_adapter/redis_test.rb +++ b/actioncable/test/subscription_adapter/redis_test.rb @@ -4,7 +4,6 @@ require "test_helper" require_relative "common" require_relative "channel_prefix" -require "active_support/testing/method_call_assertions" require "action_cable/subscription_adapter/redis" class RedisAdapterTest < ActionCable::TestCase @@ -30,9 +29,7 @@ class RedisAdapterTest::AlternateConfiguration < RedisAdapterTest end end -class RedisAdapterTest::Connector < ActiveSupport::TestCase - include ActiveSupport::Testing::MethodCallAssertions - +class RedisAdapterTest::Connector < ActionCable::TestCase test "slices url, host, port, db, and password from config" do config = { url: 1, host: 2, port: 3, db: 4, password: 5 } diff --git a/actioncable/test/test_helper.rb b/actioncable/test/test_helper.rb index 755f7b71b4..ac7881c950 100644 --- a/actioncable/test/test_helper.rb +++ b/actioncable/test/test_helper.rb @@ -2,6 +2,7 @@ require "action_cable" require "active_support/testing/autorun" +require "active_support/testing/method_call_assertions" require "puma" require "rack/mock" @@ -15,6 +16,8 @@ end Dir[File.expand_path("stubs/*.rb", __dir__)].each { |file| require file } class ActionCable::TestCase < ActiveSupport::TestCase + include ActiveSupport::Testing::MethodCallAssertions + def wait_for_async wait_for_executor Concurrent.global_io_executor end diff --git a/actioncable/test/worker_test.rb b/actioncable/test/worker_test.rb index bc1f3e415a..f7dc428441 100644 --- a/actioncable/test/worker_test.rb +++ b/actioncable/test/worker_test.rb @@ -2,7 +2,7 @@ require "test_helper" -class WorkerTest < ActiveSupport::TestCase +class WorkerTest < ActionCable::TestCase class Receiver attr_accessor :last_action diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 5d784ceb31..33b0bcbefe 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -109,7 +109,7 @@ module ActionController when :xml data = non_path_parameters.to_xml when :url_encoded_form - data = non_path_parameters.to_query + data = Rack::Utils.build_nested_query(non_path_parameters) else @custom_param_parsers[content_mime_type.symbol] = ->(_) { non_path_parameters } data = non_path_parameters.to_query diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index 734da3de9c..d4620d085a 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -220,7 +220,15 @@ XML params = Hash[:page, { name: "page name" }, "some key", 123] post :render_raw_post, params: params.dup - assert_equal params.to_query, @response.body + assert_equal Rack::Utils.build_nested_query(params), @response.body + end + + def test_params_round_trip + params = {"foo"=>{"contents"=>[{"name"=>"gorby", "id"=>"123"}, {"name"=>"puff", "d"=>"true"}]}} + post :test_params, params: params.dup + + controller_info = { "controller" => "test_case_test/test", "action" => "test_params" } + assert_equal params.merge(controller_info), JSON.parse(@response.body) end def test_body_stream @@ -228,7 +236,7 @@ XML post :render_body, params: params.dup - assert_equal params.to_query, @response.body + assert_equal Rack::Utils.build_nested_query(params), @response.body end def test_document_body_and_params_with_post diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 275e3f1313..d583d48b54 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -62,6 +62,11 @@ module ActiveModel CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict] MESSAGE_OPTIONS = [:message] + class << self + attr_accessor :i18n_full_message # :nodoc: + end + self.i18n_full_message = false + attr_reader :messages, :details # Pass in the instance of the object that is using the errors object. @@ -364,12 +369,51 @@ module ActiveModel # Returns a full message for a given attribute. # # person.errors.full_message(:name, 'is invalid') # => "Name is invalid" + # + # The `"%{attribute} %{message}"` error format can be overridden with either + # + # * <tt>activemodel.errors.models.person/contacts/addresses.attributes.street.format</tt> + # * <tt>activemodel.errors.models.person/contacts/addresses.format</tt> + # * <tt>activemodel.errors.models.person.attributes.name.format</tt> + # * <tt>activemodel.errors.models.person.format</tt> + # * <tt>errors.format</tt> def full_message(attribute, message) return message if attribute == :base + + if self.class.i18n_full_message && @base.class.respond_to?(:i18n_scope) + parts = attribute.to_s.split(".") + attribute_name = parts.pop + namespace = parts.join("/") unless parts.empty? + attributes_scope = "#{@base.class.i18n_scope}.errors.models" + + if namespace + defaults = @base.class.lookup_ancestors.map do |klass| + [ + :"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.attributes.#{attribute_name}.format", + :"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.format", + ] + end + else + defaults = @base.class.lookup_ancestors.map do |klass| + [ + :"#{attributes_scope}.#{klass.model_name.i18n_key}.attributes.#{attribute_name}.format", + :"#{attributes_scope}.#{klass.model_name.i18n_key}.format", + ] + end + end + + defaults.flatten! + else + defaults = [] + end + + defaults << :"errors.format" + defaults << "%{attribute} %{message}" + attr_name = attribute.to_s.tr(".", "_").humanize attr_name = @base.class.human_attribute_name(attribute, default: attr_name) - I18n.t(:"errors.format", - default: "%{attribute} %{message}", + I18n.t(defaults.shift, + default: defaults, attribute: attr_name, message: message) end diff --git a/activemodel/lib/active_model/railtie.rb b/activemodel/lib/active_model/railtie.rb index a9cdabba00..0ed70bd473 100644 --- a/activemodel/lib/active_model/railtie.rb +++ b/activemodel/lib/active_model/railtie.rb @@ -7,8 +7,14 @@ module ActiveModel class Railtie < Rails::Railtie # :nodoc: config.eager_load_namespaces << ActiveModel + config.active_model = ActiveSupport::OrderedOptions.new + initializer "active_model.secure_password" do ActiveModel::SecurePassword.min_cost = Rails.env.test? end + + initializer "active_model.i18n_full_message" do + ActiveModel::Errors.i18n_full_message = config.active_model.delete(:i18n_full_message) || false + end end end diff --git a/activemodel/test/cases/railtie_test.rb b/activemodel/test/cases/railtie_test.rb index ff5022e960..ab60285e2a 100644 --- a/activemodel/test/cases/railtie_test.rb +++ b/activemodel/test/cases/railtie_test.rb @@ -31,4 +31,24 @@ class RailtieTest < ActiveModel::TestCase assert_equal true, ActiveModel::SecurePassword.min_cost end + + test "i18n full message defaults to false" do + @app.initialize! + + assert_equal false, ActiveModel::Errors.i18n_full_message + end + + test "i18n full message can be disabled" do + @app.config.active_model.i18n_full_message = false + @app.initialize! + + assert_equal false, ActiveModel::Errors.i18n_full_message + end + + test "i18n full message can be enabled" do + @app.config.active_model.i18n_full_message = true + @app.initialize! + + assert_equal true, ActiveModel::Errors.i18n_full_message + end end diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb index 9cfe189d0e..1c62e750de 100644 --- a/activemodel/test/cases/validations/i18n_validation_test.rb +++ b/activemodel/test/cases/validations/i18n_validation_test.rb @@ -12,6 +12,9 @@ class I18nValidationTest < ActiveModel::TestCase I18n.load_path.clear I18n.backend = I18n::Backend::Simple.new I18n.backend.store_translations("en", errors: { messages: { custom: nil } }) + + @original_i18n_full_message = ActiveModel::Errors.i18n_full_message + ActiveModel::Errors.i18n_full_message = true end def teardown @@ -19,6 +22,7 @@ class I18nValidationTest < ActiveModel::TestCase I18n.load_path.replace @old_load_path I18n.backend = @old_backend I18n.backend.reload! + ActiveModel::Errors.i18n_full_message = @original_i18n_full_message end def test_full_message_encoding @@ -42,6 +46,61 @@ class I18nValidationTest < ActiveModel::TestCase assert_equal ["Field Name empty"], @person.errors.full_messages end + def test_errors_full_messages_doesnt_use_attribute_format_without_config + ActiveModel::Errors.i18n_full_message = false + + I18n.backend.store_translations("en", activemodel: { + errors: { models: { person: { attributes: { name: { format: "%{message}" } } } } } }) + + person = Person.new + assert_equal "Name cannot be blank", person.errors.full_message(:name, "cannot be blank") + assert_equal "Name test cannot be blank", person.errors.full_message(:name_test, "cannot be blank") + end + + def test_errors_full_messages_uses_attribute_format + ActiveModel::Errors.i18n_full_message = true + + I18n.backend.store_translations("en", activemodel: { + errors: { models: { person: { attributes: { name: { format: "%{message}" } } } } } }) + + person = Person.new + assert_equal "cannot be blank", person.errors.full_message(:name, "cannot be blank") + assert_equal "Name test cannot be blank", person.errors.full_message(:name_test, "cannot be blank") + end + + def test_errors_full_messages_uses_model_format + ActiveModel::Errors.i18n_full_message = true + + I18n.backend.store_translations("en", activemodel: { + errors: { models: { person: { format: "%{message}" } } } }) + + person = Person.new + assert_equal "cannot be blank", person.errors.full_message(:name, "cannot be blank") + assert_equal "cannot be blank", person.errors.full_message(:name_test, "cannot be blank") + end + + def test_errors_full_messages_uses_deeply_nested_model_attributes_format + ActiveModel::Errors.i18n_full_message = true + + I18n.backend.store_translations("en", activemodel: { + errors: { models: { 'person/contacts/addresses': { attributes: { street: { format: "%{message}" } } } } } }) + + person = Person.new + assert_equal "cannot be blank", person.errors.full_message(:'contacts/addresses.street', "cannot be blank") + assert_equal "Contacts/addresses country cannot be blank", person.errors.full_message(:'contacts/addresses.country', "cannot be blank") + end + + def test_errors_full_messages_uses_deeply_nested_model_model_format + ActiveModel::Errors.i18n_full_message = true + + I18n.backend.store_translations("en", activemodel: { + errors: { models: { 'person/contacts/addresses': { format: "%{message}" } } } }) + + person = Person.new + assert_equal "cannot be blank", person.errors.full_message(:'contacts/addresses.street', "cannot be blank") + assert_equal "cannot be blank", person.errors.full_message(:'contacts/addresses.country', "cannot be blank") + end + # ActiveModel::Validations # A set of common cases for ActiveModel::Validations message generation that diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index a7d3319164..a70fba6d47 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -3,7 +3,7 @@ Fixes #33024. *Federico Martinez* - + * Bump minimum SQLite version to 3.8 *Yasuo Honda* diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb index 4f3893588e..272eede824 100644 --- a/activerecord/lib/active_record/associations/alias_tracker.rb +++ b/activerecord/lib/active_record/associations/alias_tracker.rb @@ -33,7 +33,7 @@ module ActiveRecord elsif join.is_a?(Arel::Nodes::Join) join.left.name == name ? 1 : 0 elsif join.is_a?(Hash) - join.fetch(name, 0) + join[name] else raise ArgumentError, "joins list should be initialized by list of Arel::Nodes::Join" end diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 48cb819484..840d900bbc 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -400,7 +400,7 @@ module ActiveRecord records.each { |record| callback(:before_remove, record) } delete_records(existing_records, method) if existing_records.any? - records.each { |record| target.delete(record) } + @target -= records records.each { |record| callback(:after_remove, record) } end diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 46fa36d7dd..d5573b6d02 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -1289,6 +1289,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert_equal authors(:david), Author.joins(:comments_for_first_author).take end + def test_has_many_through_with_left_joined_same_table_with_through_table + assert_equal [comments(:eager_other_comment1)], authors(:mary).comments.left_joins(:post) + end + def test_has_many_through_with_unscope_should_affect_to_through_scope assert_equal [comments(:eager_other_comment1)], authors(:mary).unordered_comments end diff --git a/activesupport/lib/active_support/core_ext/range/compare_range.rb b/activesupport/lib/active_support/core_ext/range/compare_range.rb index 704041f6de..6f6d2a27bb 100644 --- a/activesupport/lib/active_support/core_ext/range/compare_range.rb +++ b/activesupport/lib/active_support/core_ext/range/compare_range.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module ActiveSupport - module CompareWithRange #:nodoc: + module CompareWithRange # Extends the default Range#=== to support range comparisons. # (1..5) === (1..5) # => true # (1..5) === (2..3) # => true diff --git a/activesupport/test/logger_test.rb b/activesupport/test/logger_test.rb index 5efbd10a7d..773b0daa1d 100644 --- a/activesupport/test/logger_test.rb +++ b/activesupport/test/logger_test.rb @@ -5,6 +5,7 @@ require "multibyte_test_helpers" require "stringio" require "fileutils" require "tempfile" +require "tmpdir" require "concurrent/atomics" class LoggerTest < ActiveSupport::TestCase diff --git a/activesupport/test/multibyte_conformance_test.rb b/activesupport/test/multibyte_conformance_test.rb index 748e8d16e1..a704505fc6 100644 --- a/activesupport/test/multibyte_conformance_test.rb +++ b/activesupport/test/multibyte_conformance_test.rb @@ -3,15 +3,10 @@ require "abstract_unit" require "multibyte_test_helpers" -require "fileutils" -require "open-uri" -require "tmpdir" - class MultibyteConformanceTest < ActiveSupport::TestCase include MultibyteTestHelpers UNIDATA_FILE = "/NormalizationTest.txt" - FileUtils.mkdir_p(CACHE_DIR) RUN_P = begin Downloader.download(UNIDATA_URL + UNIDATA_FILE, CACHE_DIR + UNIDATA_FILE) rescue diff --git a/activesupport/test/multibyte_grapheme_break_conformance_test.rb b/activesupport/test/multibyte_grapheme_break_conformance_test.rb index fac74cd80f..61b171a8d4 100644 --- a/activesupport/test/multibyte_grapheme_break_conformance_test.rb +++ b/activesupport/test/multibyte_grapheme_break_conformance_test.rb @@ -3,10 +3,6 @@ require "abstract_unit" require "multibyte_test_helpers" -require "fileutils" -require "open-uri" -require "tmpdir" - class MultibyteGraphemeBreakConformanceTest < ActiveSupport::TestCase include MultibyteTestHelpers diff --git a/activesupport/test/multibyte_normalization_conformance_test.rb b/activesupport/test/multibyte_normalization_conformance_test.rb index 1173a94e81..3674ea44f3 100644 --- a/activesupport/test/multibyte_normalization_conformance_test.rb +++ b/activesupport/test/multibyte_normalization_conformance_test.rb @@ -3,10 +3,6 @@ require "abstract_unit" require "multibyte_test_helpers" -require "fileutils" -require "open-uri" -require "tmpdir" - class MultibyteNormalizationConformanceTest < ActiveSupport::TestCase include MultibyteTestHelpers diff --git a/activesupport/test/multibyte_test_helpers.rb b/activesupport/test/multibyte_test_helpers.rb index f7cf993100..98f36aa939 100644 --- a/activesupport/test/multibyte_test_helpers.rb +++ b/activesupport/test/multibyte_test_helpers.rb @@ -1,5 +1,9 @@ # frozen_string_literal: true +require "fileutils" +require "open-uri" +require "tmpdir" + module MultibyteTestHelpers class Downloader def self.download(from, to) diff --git a/activesupport/test/testing/method_call_assertions_test.rb b/activesupport/test/testing/method_call_assertions_test.rb index 4af000bb7e..0500e47def 100644 --- a/activesupport/test/testing/method_call_assertions_test.rb +++ b/activesupport/test/testing/method_call_assertions_test.rb @@ -1,11 +1,8 @@ # frozen_string_literal: true require "abstract_unit" -require "active_support/testing/method_call_assertions" class MethodCallAssertionsTest < ActiveSupport::TestCase - include ActiveSupport::Testing::MethodCallAssertions - class Level def increment; 1; end def decrement; end diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 4d8883a7bd..4c2dea6721 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -305,6 +305,10 @@ All these configuration options are delegated to the `I18n` library. config.i18n.fallbacks.map = { az: :tr, da: [:de, :en] } ``` +### Configuring Active Model + +* `config.active_model.i18n_full_message` is a boolean value which controls whether the `full_message` error format can be overridden at the attribute or model level in the locale files + ### Configuring Active Record `config.active_record` includes a variety of configuration options: @@ -793,7 +797,7 @@ normal Rails server. config.active_storage.paths[:ffprobe] = '/usr/local/bin/ffprobe' ``` -* `config.active_storage.variable_content_types` accepts an array of strings indicating the content types that Active Storage can transform through ImageMagick. The default is `%w(image/png image/gif image/jpg image/jpeg image/vnd.adobe.photoshop)`. +* `config.active_storage.variable_content_types` accepts an array of strings indicating the content types that Active Storage can transform through ImageMagick. The default is `%w(image/png image/gif image/jpg image/jpeg image/vnd.adobe.photoshop image/vnd.microsoft.icon)`. * `config.active_storage.content_types_to_serve_as_binary` accepts an array of strings indicating the content types that Active Storage will always serve as an attachment, rather than inline. The default is `%w(text/html text/javascript image/svg+xml application/postscript application/x-shockwave-flash text/xml application/xml application/xhtml+xml)`. diff --git a/railties/test/application/per_request_digest_cache_test.rb b/railties/test/application/per_request_digest_cache_test.rb index 10d3313f6e..ab055c7648 100644 --- a/railties/test/application/per_request_digest_cache_test.rb +++ b/railties/test/application/per_request_digest_cache_test.rb @@ -5,11 +5,9 @@ require "rack/test" require "minitest/mock" require "action_view" -require "active_support/testing/method_call_assertions" class PerRequestDigestCacheTest < ActiveSupport::TestCase include ActiveSupport::Testing::Isolation - include ActiveSupport::Testing::MethodCallAssertions include Rack::Test::Methods setup do diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 3cde7c03b0..516c457e48 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -14,6 +14,7 @@ require "bundler/setup" unless defined?(Bundler) require "active_support" require "active_support/testing/autorun" require "active_support/testing/stream" +require "active_support/testing/method_call_assertions" require "active_support/test_case" RAILS_FRAMEWORK_ROOT = File.expand_path("../../..", __dir__) @@ -430,6 +431,7 @@ class ActiveSupport::TestCase include TestHelpers::Rack include TestHelpers::Generation include ActiveSupport::Testing::Stream + include ActiveSupport::Testing::MethodCallAssertions def frozen_error_class Object.const_defined?(:FrozenError) ? FrozenError : RuntimeError |