diff options
33 files changed, 242 insertions, 187 deletions
diff --git a/actionmailbox/Rakefile b/actionmailbox/Rakefile index 5cda013f96..36aed17282 100644 --- a/actionmailbox/Rakefile +++ b/actionmailbox/Rakefile @@ -4,6 +4,8 @@ require "bundler/setup" require "bundler/gem_tasks" require "rake/testtask" +task :package + Rake::TestTask.new do |t| t.libs << "test" t.pattern = "test/**/*_test.rb" diff --git a/actionmailbox/app/controllers/rails/conductor/action_mailbox/inbound_emails_controller.rb b/actionmailbox/app/controllers/rails/conductor/action_mailbox/inbound_emails_controller.rb index 30d6afe0c0..3537a983ef 100644 --- a/actionmailbox/app/controllers/rails/conductor/action_mailbox/inbound_emails_controller.rb +++ b/actionmailbox/app/controllers/rails/conductor/action_mailbox/inbound_emails_controller.rb @@ -20,7 +20,11 @@ module Rails private def new_mail - Mail.new params.require(:mail).permit(:from, :to, :cc, :bcc, :in_reply_to, :subject, :body).to_h + Mail.new(params.require(:mail).permit(:from, :to, :cc, :bcc, :in_reply_to, :subject, :body).to_h).tap do |mail| + params[:mail][:attachments].to_a.each do |attachment| + mail.attachments[attachment.original_filename] = { filename: attachment.path, content_type: attachment.content_type } + end + end end def create_inbound_email(mail) diff --git a/actionmailbox/app/views/rails/conductor/action_mailbox/inbound_emails/new.html.erb b/actionmailbox/app/views/rails/conductor/action_mailbox/inbound_emails/new.html.erb index 7f1ec74496..a3c3d39064 100644 --- a/actionmailbox/app/views/rails/conductor/action_mailbox/inbound_emails/new.html.erb +++ b/actionmailbox/app/views/rails/conductor/action_mailbox/inbound_emails/new.html.erb @@ -38,5 +38,10 @@ <%= form.text_area :body, size: "40x20" %> </div> + <div> + <%= form.label :attachments, "Attachments" %><br> + <%= form.file_field :attachments, multiple: true %> + </div> + <%= form.submit "Deliver inbound email" %> <% end %> diff --git a/actionmailbox/test/controllers/rails/action_mailbox/inbound_emails_controller_test.rb b/actionmailbox/test/controllers/rails/action_mailbox/inbound_emails_controller_test.rb new file mode 100644 index 0000000000..60077d86e2 --- /dev/null +++ b/actionmailbox/test/controllers/rails/action_mailbox/inbound_emails_controller_test.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "test_helper" + +class Rails::Conductor::ActionMailbox::InboundEmailsControllerTest < ActionDispatch::IntegrationTest + test "create inbound email" do + with_rails_env("development") do + assert_difference -> { ActionMailbox::InboundEmail.count }, +1 do + post rails_conductor_inbound_emails_path, params: { + mail: { + from: "Jason Fried <jason@37signals.com>", + to: "Replies <replies@example.com>", + bcc: "", + in_reply_to: "<4e6e35f5a38b4_479f13bb90078178@small-app-01.mail>", + subject: "Discussion: Let's debate these attachments", + body: "Let's talk about these images:", + attachments: [fixture_file_upload("files/avatar1.jpeg"), fixture_file_upload("files/avatar2.jpeg")] + } + } + end + + assert_equal 2, ActionMailbox::InboundEmail.last.mail.attachments.size + end + end + + private + def with_rails_env(env) + old_rails_env = Rails.env + Rails.env = env + yield + ensure + Rails.env = old_rails_env + end +end diff --git a/actionmailbox/test/fixtures/files/avatar1.jpeg b/actionmailbox/test/fixtures/files/avatar1.jpeg Binary files differnew file mode 100644 index 0000000000..31111c3bc9 --- /dev/null +++ b/actionmailbox/test/fixtures/files/avatar1.jpeg diff --git a/actionmailbox/test/fixtures/files/avatar2.jpeg b/actionmailbox/test/fixtures/files/avatar2.jpeg Binary files differnew file mode 100644 index 0000000000..e844e7f3c6 --- /dev/null +++ b/actionmailbox/test/fixtures/files/avatar2.jpeg diff --git a/actiontext/Rakefile b/actiontext/Rakefile index 5cda013f96..36aed17282 100644 --- a/actiontext/Rakefile +++ b/actiontext/Rakefile @@ -4,6 +4,8 @@ require "bundler/setup" require "bundler/gem_tasks" require "rake/testtask" +task :package + Rake::TestTask.new do |t| t.libs << "test" t.pattern = "test/**/*_test.rb" diff --git a/actiontext/app/models/action_text/rich_text.rb b/actiontext/app/models/action_text/rich_text.rb index a577c991b9..705dd30983 100644 --- a/actiontext/app/models/action_text/rich_text.rb +++ b/actiontext/app/models/action_text/rich_text.rb @@ -25,3 +25,5 @@ module ActionText delegate :blank?, :empty?, :present?, to: :to_plain_text end end + +ActiveSupport.run_load_hooks :action_text_rich_text, ActionText::RichText diff --git a/activejob/lib/active_job/core.rb b/activejob/lib/active_job/core.rb index 4ab62f89b0..487cdd6d38 100644 --- a/activejob/lib/active_job/core.rb +++ b/activejob/lib/active_job/core.rb @@ -81,7 +81,7 @@ module ActiveJob @queue_name = self.class.queue_name @priority = self.class.priority @executions = 0 - @exception_executions = Hash.new(0) + @exception_executions = {} end # Returns a hash with the job data that can safely be passed to the diff --git a/activejob/lib/active_job/exceptions.rb b/activejob/lib/active_job/exceptions.rb index 48b35c8d05..9e00942a1c 100644 --- a/activejob/lib/active_job/exceptions.rb +++ b/activejob/lib/active_job/exceptions.rb @@ -50,8 +50,8 @@ module ActiveJob def retry_on(*exceptions, wait: 3.seconds, attempts: 5, queue: nil, priority: nil) rescue_from(*exceptions) do |error| # Guard against jobs that were persisted before we started having individual executions counters per retry_on - self.exception_executions ||= Hash.new(0) - self.exception_executions[exceptions.to_s] += 1 + self.exception_executions ||= {} + self.exception_executions[exceptions.to_s] = (exception_executions[exceptions.to_s] || 0) + 1 if exception_executions[exceptions.to_s] < attempts retry_job wait: determine_delay(wait), queue: queue, priority: priority, error: error diff --git a/activejob/test/cases/exceptions_test.rb b/activejob/test/cases/exceptions_test.rb index cac48cb6cb..c88162bf58 100644 --- a/activejob/test/cases/exceptions_test.rb +++ b/activejob/test/cases/exceptions_test.rb @@ -4,30 +4,26 @@ require "helper" require "jobs/retry_job" require "models/person" -class ExceptionsTest < ActiveJob::TestCase +class ExceptionsTest < ActiveSupport::TestCase setup do JobBuffer.clear - skip if ActiveJob::Base.queue_adapter.is_a?(ActiveJob::QueueAdapters::InlineAdapter) + skip if adapter_skips_scheduling?(ActiveJob::Base.queue_adapter) end test "successfully retry job throwing exception against defaults" do - perform_enqueued_jobs do - RetryJob.perform_later "DefaultsError", 5 + RetryJob.perform_later "DefaultsError", 5 - assert_equal [ - "Raised DefaultsError for the 1st time", - "Raised DefaultsError for the 2nd time", - "Raised DefaultsError for the 3rd time", - "Raised DefaultsError for the 4th time", - "Successfully completed job" ], JobBuffer.values - end + assert_equal [ + "Raised DefaultsError for the 1st time", + "Raised DefaultsError for the 2nd time", + "Raised DefaultsError for the 3rd time", + "Raised DefaultsError for the 4th time", + "Successfully completed job" ], JobBuffer.values end test "successfully retry job throwing exception against higher limit" do - perform_enqueued_jobs do - RetryJob.perform_later "ShortWaitTenAttemptsError", 9 - assert_equal 9, JobBuffer.values.count - end + RetryJob.perform_later "ShortWaitTenAttemptsError", 9 + assert_equal 9, JobBuffer.values.count end test "keeps the same attempts counter for several exceptions listed in the same retry_on declaration" do @@ -35,9 +31,7 @@ class ExceptionsTest < ActiveJob::TestCase SecondRetryableErrorOfTwo SecondRetryableErrorOfTwo) assert_raises SecondRetryableErrorOfTwo do - perform_enqueued_jobs do - RetryJob.perform_later(exceptions_to_raise, 5) - end + RetryJob.perform_later(exceptions_to_raise, 5) assert_equal [ "Raised FirstRetryableErrorOfTwo for the 1st time", @@ -54,9 +48,7 @@ class ExceptionsTest < ActiveJob::TestCase FirstRetryableErrorOfTwo FirstRetryableErrorOfTwo FirstRetryableErrorOfTwo) assert_nothing_raised do - perform_enqueued_jobs do - RetryJob.perform_later(exceptions_to_raise, 10) - end + RetryJob.perform_later(exceptions_to_raise, 10) assert_equal [ "Raised DefaultsError for the 1st time", @@ -72,108 +64,107 @@ class ExceptionsTest < ActiveJob::TestCase end test "failed retry job when exception kept occurring against defaults" do - perform_enqueued_jobs do - RetryJob.perform_later "DefaultsError", 6 - assert_equal "Raised DefaultsError for the 5th time", JobBuffer.last_value - rescue DefaultsError - pass - end + RetryJob.perform_later "DefaultsError", 6 + assert_equal "Raised DefaultsError for the 5th time", JobBuffer.last_value + rescue DefaultsError + pass end test "failed retry job when exception kept occurring against higher limit" do - perform_enqueued_jobs do - RetryJob.perform_later "ShortWaitTenAttemptsError", 11 - assert_equal "Raised ShortWaitTenAttemptsError for the 10th time", JobBuffer.last_value - rescue ShortWaitTenAttemptsError - pass - end + RetryJob.perform_later "ShortWaitTenAttemptsError", 11 + assert_equal "Raised ShortWaitTenAttemptsError for the 10th time", JobBuffer.last_value + rescue ShortWaitTenAttemptsError + pass end test "discard job" do - perform_enqueued_jobs do - RetryJob.perform_later "DiscardableError", 2 - assert_equal "Raised DiscardableError for the 1st time", JobBuffer.last_value - end + RetryJob.perform_later "DiscardableError", 2 + assert_equal "Raised DiscardableError for the 1st time", JobBuffer.last_value end test "custom handling of discarded job" do - perform_enqueued_jobs do - RetryJob.perform_later "CustomDiscardableError", 2 - assert_equal "Dealt with a job that was discarded in a custom way. Message: CustomDiscardableError", JobBuffer.last_value - end + RetryJob.perform_later "CustomDiscardableError", 2 + assert_equal "Dealt with a job that was discarded in a custom way. Message: CustomDiscardableError", JobBuffer.last_value end test "custom handling of job that exceeds retry attempts" do - perform_enqueued_jobs do - RetryJob.perform_later "CustomCatchError", 6 - assert_equal "Dealt with a job that failed to retry in a custom way after 6 attempts. Message: CustomCatchError", JobBuffer.last_value - end + RetryJob.perform_later "CustomCatchError", 6 + assert_equal "Dealt with a job that failed to retry in a custom way after 6 attempts. Message: CustomCatchError", JobBuffer.last_value end test "long wait job" do travel_to Time.now - perform_enqueued_jobs do - assert_performed_with at: (Time.now + 3600.seconds).to_i do - RetryJob.perform_later "LongWaitError", 5 - end - end + RetryJob.perform_later "LongWaitError", 2, :log_scheduled_at + + assert_equal [ + "Raised LongWaitError for the 1st time", + "Next execution scheduled at #{(Time.now + 3600.seconds).to_f}", + "Successfully completed job" + ], JobBuffer.values end test "exponentially retrying job" do travel_to Time.now - perform_enqueued_jobs do - assert_performed_with at: (Time.now + 3.seconds).to_i do - assert_performed_with at: (Time.now + 18.seconds).to_i do - assert_performed_with at: (Time.now + 83.seconds).to_i do - assert_performed_with at: (Time.now + 258.seconds).to_i do - RetryJob.perform_later "ExponentialWaitTenAttemptsError", 5 - end - end - end - end - end + RetryJob.perform_later "ExponentialWaitTenAttemptsError", 5, :log_scheduled_at + + assert_equal [ + "Raised ExponentialWaitTenAttemptsError for the 1st time", + "Next execution scheduled at #{(Time.now + 3.seconds).to_f}", + "Raised ExponentialWaitTenAttemptsError for the 2nd time", + "Next execution scheduled at #{(Time.now + 18.seconds).to_f}", + "Raised ExponentialWaitTenAttemptsError for the 3rd time", + "Next execution scheduled at #{(Time.now + 83.seconds).to_f}", + "Raised ExponentialWaitTenAttemptsError for the 4th time", + "Next execution scheduled at #{(Time.now + 258.seconds).to_f}", + "Successfully completed job" + ], JobBuffer.values end test "custom wait retrying job" do travel_to Time.now - perform_enqueued_jobs do - assert_performed_with at: (Time.now + 2.seconds).to_i do - assert_performed_with at: (Time.now + 4.seconds).to_i do - assert_performed_with at: (Time.now + 6.seconds).to_i do - assert_performed_with at: (Time.now + 8.seconds).to_i do - RetryJob.perform_later "CustomWaitTenAttemptsError", 5 - end - end - end - end - end + RetryJob.perform_later "CustomWaitTenAttemptsError", 5, :log_scheduled_at + + assert_equal [ + "Raised CustomWaitTenAttemptsError for the 1st time", + "Next execution scheduled at #{(Time.now + 2.seconds).to_f}", + "Raised CustomWaitTenAttemptsError for the 2nd time", + "Next execution scheduled at #{(Time.now + 4.seconds).to_f}", + "Raised CustomWaitTenAttemptsError for the 3rd time", + "Next execution scheduled at #{(Time.now + 6.seconds).to_f}", + "Raised CustomWaitTenAttemptsError for the 4th time", + "Next execution scheduled at #{(Time.now + 8.seconds).to_f}", + "Successfully completed job" + ], JobBuffer.values end test "successfully retry job throwing one of two retryable exceptions" do - perform_enqueued_jobs do - RetryJob.perform_later "SecondRetryableErrorOfTwo", 3 + RetryJob.perform_later "SecondRetryableErrorOfTwo", 3 - assert_equal [ - "Raised SecondRetryableErrorOfTwo for the 1st time", - "Raised SecondRetryableErrorOfTwo for the 2nd time", - "Successfully completed job" ], JobBuffer.values - end + assert_equal [ + "Raised SecondRetryableErrorOfTwo for the 1st time", + "Raised SecondRetryableErrorOfTwo for the 2nd time", + "Successfully completed job" ], JobBuffer.values end test "discard job throwing one of two discardable exceptions" do - perform_enqueued_jobs do - RetryJob.perform_later "SecondDiscardableErrorOfTwo", 2 - assert_equal [ "Raised SecondDiscardableErrorOfTwo for the 1st time" ], JobBuffer.values - end + RetryJob.perform_later "SecondDiscardableErrorOfTwo", 2 + assert_equal [ "Raised SecondDiscardableErrorOfTwo for the 1st time" ], JobBuffer.values end test "successfully retry job throwing DeserializationError" do - perform_enqueued_jobs do - RetryJob.perform_later Person.new(404), 5 - assert_equal ["Raised ActiveJob::DeserializationError for the 5 time"], JobBuffer.values - end + RetryJob.perform_later Person.new(404), 5 + assert_equal ["Raised ActiveJob::DeserializationError for the 5 time"], JobBuffer.values end + + private + def adapter_skips_scheduling?(queue_adapter) + [ + ActiveJob::QueueAdapters::InlineAdapter, + ActiveJob::QueueAdapters::AsyncAdapter, + ActiveJob::QueueAdapters::SneakersAdapter + ].include?(queue_adapter.class) + end end diff --git a/activejob/test/jobs/retry_job.rb b/activejob/test/jobs/retry_job.rb index 3b0dce1a3c..112d672006 100644 --- a/activejob/test/jobs/retry_job.rb +++ b/activejob/test/jobs/retry_job.rb @@ -30,7 +30,13 @@ class RetryJob < ActiveJob::Base discard_on FirstDiscardableErrorOfTwo, SecondDiscardableErrorOfTwo discard_on(CustomDiscardableError) { |job, error| JobBuffer.add("Dealt with a job that was discarded in a custom way. Message: #{error.message}") } - def perform(raising, attempts) + before_enqueue do |job| + if job.arguments.include?(:log_scheduled_at) && job.scheduled_at + JobBuffer.add("Next execution scheduled at #{job.scheduled_at}") + end + end + + def perform(raising, attempts, *) raising = raising.shift if raising.is_a?(Array) if raising && executions < attempts JobBuffer.add("Raised #{raising} for the #{executions.ordinalize} time") diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 10961ed9c8..cccd6e2210 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -579,13 +579,13 @@ module ActiveRecord m.alias_type %r(bit)i, "binary" m.register_type(%r(enum)i) do |sql_type| - limit = sql_type[/^enum\((.+)\)/i, 1] + limit = sql_type[/^enum\s*\((.+)\)/i, 1] .split(",").map { |enum| enum.strip.length - 2 }.max MysqlString.new(limit: limit) end m.register_type(%r(^set)i) do |sql_type| - limit = sql_type[/^set\((.+)\)/i, 1] + limit = sql_type[/^set\s*\((.+)\)/i, 1] .split(",").map { |set| set.strip.length - 1 }.sum - 1 MysqlString.new(limit: limit) end diff --git a/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb index 44bb2c7ab6..2ea27c8490 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb @@ -3,11 +3,7 @@ module ActiveRecord class PredicateBuilder class RangeHandler # :nodoc: - class RangeWithBinds < Struct.new(:begin, :end) - def exclude_end? - false - end - end + RangeWithBinds = Struct.new(:begin, :end, :exclude_end?) def initialize(predicate_builder) @predicate_builder = predicate_builder @@ -16,22 +12,7 @@ module ActiveRecord def call(attribute, value) begin_bind = predicate_builder.build_bind_attribute(attribute.name, value.begin) end_bind = predicate_builder.build_bind_attribute(attribute.name, value.end) - - if begin_bind.value.infinity? - if end_bind.value.infinity? - attribute.not_in([]) - elsif value.exclude_end? - attribute.lt(end_bind) - else - attribute.lteq(end_bind) - end - elsif end_bind.value.infinity? - attribute.gteq(begin_bind) - elsif value.exclude_end? - attribute.gteq(begin_bind).and(attribute.lt(end_bind)) - else - attribute.between(RangeWithBinds.new(begin_bind, end_bind)) - end + attribute.between(RangeWithBinds.new(begin_bind, end_bind, value.exclude_end?)) end private diff --git a/activerecord/lib/active_record/relation/query_attribute.rb b/activerecord/lib/active_record/relation/query_attribute.rb index f64bd30d38..b45326bdda 100644 --- a/activerecord/lib/active_record/relation/query_attribute.rb +++ b/activerecord/lib/active_record/relation/query_attribute.rb @@ -30,12 +30,12 @@ module ActiveRecord @_boundable = false end - def infinity? - _infinity?(value_before_type_cast) || boundable? && _infinity?(value_for_database) + def infinite? + infinity?(value_before_type_cast) || boundable? && infinity?(value_for_database) end private - def _infinity?(value) + def infinity?(value) value.respond_to?(:infinite?) && value.infinite? end end diff --git a/activerecord/lib/arel/nodes/bind_param.rb b/activerecord/lib/arel/nodes/bind_param.rb index ba8340558a..f145e44ae3 100644 --- a/activerecord/lib/arel/nodes/bind_param.rb +++ b/activerecord/lib/arel/nodes/bind_param.rb @@ -24,6 +24,10 @@ module Arel # :nodoc: all value.nil? end + def infinite? + value.respond_to?(:infinite?) && value.infinite? + end + def boundable? !value.respond_to?(:boundable?) || value.boundable? end diff --git a/activerecord/lib/arel/nodes/casted.rb b/activerecord/lib/arel/nodes/casted.rb index c1e6e97d6d..6e911b717d 100644 --- a/activerecord/lib/arel/nodes/casted.rb +++ b/activerecord/lib/arel/nodes/casted.rb @@ -27,6 +27,10 @@ module Arel # :nodoc: all class Quoted < Arel::Nodes::Unary # :nodoc: alias :val :value def nil?; val.nil?; end + + def infinite? + value.respond_to?(:infinite?) && value.infinite? + end end def self.build_quoted(other, attribute = nil) diff --git a/activerecord/lib/arel/predications.rb b/activerecord/lib/arel/predications.rb index 77502dd199..28679ae892 100644 --- a/activerecord/lib/arel/predications.rb +++ b/activerecord/lib/arel/predications.rb @@ -35,15 +35,15 @@ module Arel # :nodoc: all end def between(other) - if equals_quoted?(other.begin, -Float::INFINITY) - if equals_quoted?(other.end, Float::INFINITY) + if infinity?(other.begin) + if infinity?(other.end) not_in([]) elsif other.exclude_end? lt(other.end) else lteq(other.end) end - elsif equals_quoted?(other.end, Float::INFINITY) + elsif infinity?(other.end) gteq(other.begin) elsif other.exclude_end? gteq(other.begin).and(lt(other.end)) @@ -81,15 +81,15 @@ Passing a range to `#in` is deprecated. Call `#between`, instead. end def not_between(other) - if equals_quoted?(other.begin, -Float::INFINITY) - if equals_quoted?(other.end, Float::INFINITY) + if infinity?(other.begin) + if infinity?(other.end) self.in([]) elsif other.exclude_end? gteq(other.end) else gt(other.end) end - elsif equals_quoted?(other.end, Float::INFINITY) + elsif infinity?(other.end) lt(other.begin) else left = lt(other.begin) @@ -238,12 +238,8 @@ Passing a range to `#not_in` is deprecated. Call `#not_between`, instead. others.map { |v| quoted_node(v) } end - def equals_quoted?(maybe_quoted, value) - if maybe_quoted.is_a?(Nodes::Quoted) - maybe_quoted.val == value - else - maybe_quoted == value - end + def infinity?(value) + value.respond_to?(:infinite?) && value.infinite? end end end diff --git a/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb index 02e76ce146..38331aa641 100644 --- a/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb +++ b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb @@ -27,8 +27,12 @@ if current_adapter?(:Mysql2Adapter) def test_string_types assert_lookup_type :string, "enum('one', 'two', 'three')" assert_lookup_type :string, "ENUM('one', 'two', 'three')" + assert_lookup_type :string, "enum ('one', 'two', 'three')" + assert_lookup_type :string, "ENUM ('one', 'two', 'three')" assert_lookup_type :string, "set('one', 'two', 'three')" assert_lookup_type :string, "SET('one', 'two', 'three')" + assert_lookup_type :string, "set ('one', 'two', 'three')" + assert_lookup_type :string, "SET ('one', 'two', 'three')" end def test_set_type_with_value_matching_other_type diff --git a/activestorage/test/dummy/config/environments/development.rb b/activestorage/test/dummy/config/environments/development.rb index 47fc5bf25c..4b80d291ca 100644 --- a/activestorage/test/dummy/config/environments/development.rb +++ b/activestorage/test/dummy/config/environments/development.rb @@ -17,6 +17,7 @@ Rails.application.configure do # Enable/disable caching. By default caching is disabled. if Rails.root.join("tmp/caching-dev.txt").exist? config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true config.cache_store = :memory_store config.public_file_server.headers = { diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index e678f48244..360cef2b41 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,9 @@ +* Preserve key order passed to `ActiveSupport::CacheStore#fetch_multi`. + + `fetch_multi(*names)` now returns its results in the same order as the `*names` requested, rather than returning cache hits followed by cache misses. + + *Gannon McGibbon* + * If the same block is `included` multiple times for a Concern, an exception is no longer raised. *Mark J. Titorenko*, *Vlad Bokov* diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index e8518645d9..30a69c550b 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -438,18 +438,18 @@ module ActiveSupport options = merged_options(options) instrument :read_multi, names, options do |payload| - read_multi_entries(names, options).tap do |results| - payload[:hits] = results.keys - payload[:super_operation] = :fetch_multi + reads = read_multi_entries(names, options) + writes = {} + ordered = names.each_with_object({}) do |name, hash| + hash[name] = reads.fetch(name) { writes[name] = yield(name) } + end - writes = {} + payload[:hits] = reads.keys + payload[:super_operation] = :fetch_multi - (names - results.keys).each do |name| - results[name] = writes[name] = yield(name) - end + write_multi(writes, options) - write_multi writes, options - end + ordered end end diff --git a/activesupport/test/cache/behaviors/cache_store_behavior.rb b/activesupport/test/cache/behaviors/cache_store_behavior.rb index 9f54b1e7de..a696760bb2 100644 --- a/activesupport/test/cache/behaviors/cache_store_behavior.rb +++ b/activesupport/test/cache/behaviors/cache_store_behavior.rb @@ -130,7 +130,7 @@ module CacheStoreBehavior assert_equal("fufu", @cache.read("fu")) end - def test_multi_with_objects + def test_fetch_multi_with_objects cache_struct = Struct.new(:cache_key, :title) foo = cache_struct.new("foo", "FOO!") bar = cache_struct.new("bar") @@ -142,6 +142,14 @@ module CacheStoreBehavior assert_equal({ foo => "FOO!", bar => "BAM!" }, values) end + def test_fetch_multi_returns_ordered_names + @cache.write("bam", "BAM") + + values = @cache.fetch_multi("foo", "bar", "bam") { |key| key.upcase } + + assert_equal(%w(foo bar bam), values.keys) + end + def test_fetch_multi_without_block assert_raises(ArgumentError) do @cache.fetch_multi("foo") diff --git a/guides/source/engines.md b/guides/source/engines.md index 9558225b47..c4829299ca 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -1511,6 +1511,7 @@ To hook into the initialization process of one of the following classes use the | `ActionMailer::Base` | `action_mailer` | | `ActionMailer::TestCase` | `action_mailer_test_case` | | `ActionText::Content` | `action_text_content` | +| `ActionText::RichText` | `action_text_rich_text` | | `ActionView::Base` | `action_view` | | `ActionView::TestCase` | `action_view_test_case` | | `ActiveJob::Base` | `active_job` | diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 9897f6e011..673b6eac86 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,7 @@ +* Use original `bundler` environment variables during the process of generating a new rails project. + + *Marco Costa* + * Send Active Storage analysis and purge jobs to dedicated queues by default. Analysis jobs now use the `:active_storage_analysis` queue, and purge jobs @@ -71,12 +75,6 @@ *Gannon McGibbon* -* Add JSON support to rails properties route (`/rails/info/properties`). - - Now, `Rails::Info` properties may be accessed in JSON format at `/rails/info/properties.json`. - - *Yoshiyuki Hirano* - * Use Ids instead of memory addresses when displaying references in scaffold views. Fixes #29200. diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 3778690ef6..8df2b32dd2 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -388,19 +388,21 @@ module Rails # its own vendored Thor, which could be a different version. Running both # things in the same process is a recipe for a night with paracetamol. # - # We unset temporary bundler variables to load proper bundler and Gemfile. - # # Thanks to James Tucker for the Gem tricks involved in this call. _bundle_command = Gem.bin_path("bundler", "bundle") require "bundler" - Bundler.with_clean_env do - full_command = %Q["#{Gem.ruby}" "#{_bundle_command}" #{command}] - if options[:quiet] - system(env, full_command, out: File::NULL) - else - system(env, full_command) - end + Bundler.with_original_env do + exec_bundle_command(_bundle_command, command, env) + end + end + + def exec_bundle_command(bundle_command, command, env) + full_command = %Q["#{Gem.ruby}" "#{bundle_command}" #{command}] + if options[:quiet] + system(env, full_command, out: File::NULL) + else + system(env, full_command) end end diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt index 3807c8a9aa..2887bc2d67 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt @@ -16,6 +16,7 @@ Rails.application.configure do # Run rails dev:cache to toggle caching. if Rails.root.join('tmp', 'caching-dev.txt').exist? config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true config.cache_store = :memory_store config.public_file_server.headers = { diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb index c68405619d..72b555ec19 100644 --- a/railties/lib/rails/info.rb +++ b/railties/lib/rails/info.rb @@ -54,10 +54,6 @@ module Rails table << "</table>" end end - - def to_json - Hash[properties].to_json - end end # The Rails version. diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb index 14459623ac..f74d979721 100644 --- a/railties/lib/rails/info_controller.rb +++ b/railties/lib/rails/info_controller.rb @@ -14,16 +14,8 @@ class Rails::InfoController < Rails::ApplicationController # :nodoc: end def properties - respond_to do |format| - format.html do - @info = Rails::Info.to_html - @page_title = "Properties" - end - - format.json do - render json: Rails::Info.to_json - end - end + @info = Rails::Info.to_html + @page_title = "Properties" end def routes diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 47e401c34f..839e6feb39 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -773,6 +773,24 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_bundler_command_called("install") end + def test_generation_use_original_bundle_environment + generator([destination_root], skip_webpack_install: true) + + mock_original_env = -> do + { "BUNDLE_RUBYONRAILS__ORG" => "user:pass" } + end + + ensure_environment_is_set = -> *_args do + assert_equal "user:pass", ENV["BUNDLE_RUBYONRAILS__ORG"] + end + + Bundler.stub :original_env, mock_original_env do + generator.stub :exec_bundle_command, ensure_environment_is_set do + quietly { generator.invoke_all } + end + end + end + def test_dev_option generator([destination_root], dev: true, skip_webpack_install: true) diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb index 6ab68f8333..878a238f8d 100644 --- a/railties/test/rails_info_controller_test.rb +++ b/railties/test/rails_info_controller_test.rb @@ -50,11 +50,6 @@ class InfoControllerTest < ActionController::TestCase assert_select "table" end - test "info controller renders json with properties" do - get :properties, format: :json - assert_equal Rails::Info.to_json, response.body - end - test "info controller renders with routes" do get :routes assert_response :success diff --git a/railties/test/rails_info_test.rb b/railties/test/rails_info_test.rb index d167a86e56..50522c1be6 100644 --- a/railties/test/rails_info_test.rb +++ b/railties/test/rails_info_test.rb @@ -43,18 +43,6 @@ class InfoTest < ActiveSupport::TestCase end end - def test_json_includes_middleware - Rails::Info.module_eval do - property "Middleware", ["Rack::Lock", "Rack::Static"] - end - - hash = JSON.parse(Rails::Info.to_json) - assert_includes hash.keys, "Middleware" - properties.value_for("Middleware").each do |value| - assert_includes hash["Middleware"], value - end - end - private def properties Rails::Info.properties diff --git a/tasks/release.rb b/tasks/release.rb index a13003aa27..600e716a8d 100644 --- a/tasks/release.rb +++ b/tasks/release.rb @@ -1,6 +1,20 @@ # frozen_string_literal: true -FRAMEWORKS = %w( activesupport activemodel activerecord actionview actionpack activejob actionmailer actioncable activestorage railties ) +# Order dependent. E.g. Action Mailbox depends on Active Record so it should be after. +FRAMEWORKS = %w( + activesupport + activemodel + activerecord + actionview + actionpack + activejob + actionmailer + actioncable + activestorage + actionmailbox + actiontext + railties +) FRAMEWORK_NAMES = Hash.new { |h, k| k.split(/(?<=active|action)/).map(&:capitalize).join(" ") } root = File.expand_path("..", __dir__) |