diff options
57 files changed, 559 insertions, 142 deletions
diff --git a/.rubocop.yml b/.rubocop.yml index 3e15d34dbd..a673e6ba83 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -173,6 +173,12 @@ Lint/RequireParentheses: Lint/StringConversionInInterpolation: Enabled: true +Lint/UriEscapeUnescape: + Enabled: true + +Style/ParenthesesAroundCondition: + Enabled: true + Style/RedundantReturn: Enabled: true AllowMultipleReturnValues: true diff --git a/Gemfile.lock b/Gemfile.lock index 87d017a8a9..5af1ce00e2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -276,9 +276,9 @@ GEM httpclient (2.8.3) i18n (1.0.1) concurrent-ruby (~> 1.0) - image_processing (1.2.0) + image_processing (1.6.0) mini_magick (~> 4.0) - ruby-vips (>= 2.0.10, < 3) + ruby-vips (>= 2.0.11, < 3) io-like (0.3.0) jaro_winkler (1.5.1) jaro_winkler (1.5.1-java) @@ -411,7 +411,7 @@ GEM ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) ruby-progressbar (1.9.0) - ruby-vips (2.0.12) + ruby-vips (2.0.13) ffi (~> 1.9) ruby_dep (1.5.0) rubyzip (1.2.1) @@ -579,4 +579,4 @@ DEPENDENCIES websocket-client-simple! BUNDLED WITH - 1.16.2 + 1.16.3 diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index a5497aa055..a30f178190 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,19 @@ +* Purpose metadata for signed/encrypted cookies. + + Rails can now thwart attacks that attempt to copy signed/encrypted value + of a cookie and use it as the value of another cookie. + + It does so by stashing the cookie-name in the purpose field which is + then signed/encrypted along with the cookie value. Then, on a server-side + read, we verify the cookie-names and discard any attacked cookies. + + Enable `action_dispatch.use_cookies_with_metadata` to use this feature, which + writes cookies with the new purpose and expiry metadata embedded. + + Pull Request: #32937 + + *Assain Jaleel* + * Raises `ActionController::RespondToMismatchError` with confliciting `respond_to` invocations. `respond_to` can match multiple types and lead to undefined behavior when diff --git a/actionpack/lib/action_controller/renderer.rb b/actionpack/lib/action_controller/renderer.rb index 2d1523f0fc..2b4559c760 100644 --- a/actionpack/lib/action_controller/renderer.rb +++ b/actionpack/lib/action_controller/renderer.rb @@ -81,7 +81,7 @@ module ActionController # * <tt>:html</tt> - Renders the provided HTML safe string, otherwise # performs HTML escape on the string first. Sets the content type as <tt>text/html</tt>. # * <tt>:json</tt> - Renders the provided hash or object in JSON. You don't - # need to call <tt>.to_json<tt> on the object you want to render. + # need to call <tt>.to_json</tt> on the object you want to render. # * <tt>:body</tt> - Renders provided text and sets content type of <tt>text/plain</tt>. # # If no <tt>options</tt> hash is passed or if <tt>:update</tt> is specified, the default is diff --git a/actionpack/lib/action_dispatch/request/utils.rb b/actionpack/lib/action_dispatch/request/utils.rb index 0ae464082d..fb0efb9a58 100644 --- a/actionpack/lib/action_dispatch/request/utils.rb +++ b/actionpack/lib/action_dispatch/request/utils.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "active_support/core_ext/hash/indifferent_access" + module ActionDispatch class Request class Utils # :nodoc: diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index 34ead0a4c0..6637c2cae9 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -1405,8 +1405,7 @@ class CookiesTest < ActionController::TestCase assert_equal "5-2-Stable Chocolate Cookies", cookies.encrypted[:favorite] - freeze_time do - travel 1001.years + travel 1001.years do assert_nil cookies.encrypted[:favorite] end @@ -1422,8 +1421,7 @@ class CookiesTest < ActionController::TestCase assert_equal "5-2-Stable Choco Chip Cookie", cookies.signed[:favorite] - freeze_time do - travel 1001.years + travel 1001.years do assert_nil cookies.signed[:favorite] end @@ -1439,8 +1437,7 @@ class CookiesTest < ActionController::TestCase assert_equal "5-2-Stable Chocolate Cookies", cookies.encrypted[:favorite] - freeze_time do - travel 1001.years + travel 1001.years do assert_nil cookies.encrypted[:favorite] end @@ -1456,8 +1453,7 @@ class CookiesTest < ActionController::TestCase assert_equal "5-2-Stable Choco Chip Cookie", cookies.signed[:favorite] - freeze_time do - travel 1001.years + travel 1001.years do assert_nil cookies.signed[:favorite] end diff --git a/actionpack/test/dispatch/exception_wrapper_test.rb b/actionpack/test/dispatch/exception_wrapper_test.rb index 600280d6b3..668469a01d 100644 --- a/actionpack/test/dispatch/exception_wrapper_test.rb +++ b/actionpack/test/dispatch/exception_wrapper_test.rb @@ -20,6 +20,7 @@ module ActionDispatch setup do @cleaner = ActiveSupport::BacktraceCleaner.new + @cleaner.remove_filters! @cleaner.add_silencer { |line| line !~ /^lib/ } end diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb index 14bd8ffa84..cbcce4a4dc 100644 --- a/actionview/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb @@ -55,7 +55,7 @@ module ActionView # that path. # * <tt>:skip_pipeline</tt> - This option is used to bypass the asset pipeline # when it is set to true. - # * <tt>:nonce<tt> - When set to true, adds an automatic nonce value if + # * <tt>:nonce</tt> - When set to true, adds an automatic nonce value if # you have Content Security Policy enabled. # # ==== Examples diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb index 77a1c1fed9..a338d076e4 100644 --- a/actionview/lib/action_view/helpers/text_helper.rb +++ b/actionview/lib/action_view/helpers/text_helper.rb @@ -228,7 +228,7 @@ module ActionView # pluralize(2, 'Person', locale: :de) # # => 2 Personen def pluralize(count, singular, plural_arg = nil, plural: plural_arg, locale: I18n.locale) - word = if (count == 1 || count =~ /^1(\.0+)?$/) + word = if count == 1 || count =~ /^1(\.0+)?$/ singular else plural || singular.pluralize(locale) diff --git a/actionview/test/template/text_helper_test.rb b/actionview/test/template/text_helper_test.rb index 45edfe18be..4d47706bda 100644 --- a/actionview/test/template/text_helper_test.rb +++ b/actionview/test/template/text_helper_test.rb @@ -9,7 +9,7 @@ class TextHelperTest < ActionView::TestCase super # This simulates the fact that instance variables are reset every time # a view is rendered. The cycle helper depends on this behavior. - @_cycles = nil if (defined? @_cycles) + @_cycles = nil if defined?(@_cycles) end def test_concat diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md index 8526741383..8c4a74f739 100644 --- a/activejob/CHANGELOG.md +++ b/activejob/CHANGELOG.md @@ -1,3 +1,9 @@ +* Allow `perform_enqueued_jobs` to be called without a block. + + Performs all of the jobs that have been enqueued up to this point in the test. + + *Kevin Deisz* + * Move `enqueue`/`enqueue_at` notifications to an around callback. Improves timing accuracy over the old after callback by including diff --git a/activejob/lib/active_job/execution.rb b/activejob/lib/active_job/execution.rb index d75be376ec..f5a343311f 100644 --- a/activejob/lib/active_job/execution.rb +++ b/activejob/lib/active_job/execution.rb @@ -31,11 +31,11 @@ module ActiveJob # # MyJob.new(*args).perform_now def perform_now + # Guard against jobs that were persisted before we started counting executions by zeroing out nil counters + self.executions = (executions || 0) + 1 + deserialize_arguments_if_needed run_callbacks :perform do - # Guard against jobs that were persisted before we started counting executions by zeroing out nil counters - self.executions = (executions || 0) + 1 - perform(*arguments) end rescue => exception diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb index 04cde28a96..b45cc57fb0 100644 --- a/activejob/lib/active_job/test_helper.rb +++ b/activejob/lib/active_job/test_helper.rb @@ -117,12 +117,12 @@ module ActiveJob # end def assert_enqueued_jobs(number, only: nil, except: nil, queue: nil) if block_given? - original_count = enqueued_jobs_size(only: only, except: except, queue: queue) + original_count = enqueued_jobs_with(only: only, except: except, queue: queue) yield - new_count = enqueued_jobs_size(only: only, except: except, queue: queue) + new_count = enqueued_jobs_with(only: only, except: except, queue: queue) assert_equal number, new_count - original_count, "#{number} jobs expected, but #{new_count - original_count} were enqueued" else - actual_count = enqueued_jobs_size(only: only, except: except, queue: queue) + actual_count = enqueued_jobs_with(only: only, except: except, queue: queue) assert_equal number, actual_count, "#{number} jobs expected, but #{actual_count} were enqueued" end end @@ -362,7 +362,9 @@ module ActiveJob instantiate_job(matching_job) end - # Performs all enqueued jobs in the duration of the block. + # Performs all enqueued jobs. If a block is given, performs all of the jobs + # that were enqueued throughout the duration of the block. If a block is + # not given, performs all of the enqueued jobs up to this point in the test. # # def test_perform_enqueued_jobs # perform_enqueued_jobs do @@ -371,6 +373,14 @@ module ActiveJob # assert_performed_jobs 1 # end # + # def test_perform_enqueued_jobs_without_block + # MyJob.perform_later(1, 2, 3) + # + # perform_enqueued_jobs + # + # assert_performed_jobs 1 + # end + # # This method also supports filtering. If the +:only+ option is specified, # then only the listed job(s) will be performed. # @@ -405,7 +415,8 @@ module ActiveJob queue_adapter.perform_enqueued_at_jobs = true queue_adapter.filter = only queue_adapter.reject = except - yield + + block_given? ? yield : flush_enqueued_jobs(only: only, except: except) ensure queue_adapter.perform_enqueued_jobs = old_perform_enqueued_jobs queue_adapter.perform_enqueued_at_jobs = old_perform_enqueued_at_jobs @@ -432,10 +443,12 @@ module ActiveJob performed_jobs.clear end - def enqueued_jobs_size(only: nil, except: nil, queue: nil) + def enqueued_jobs_with(only: nil, except: nil, queue: nil) validate_option(only: only, except: except) + enqueued_jobs.count do |job| job_class = job.fetch(:job) + if only next false unless Array(only).include?(job_class) elsif except @@ -444,10 +457,20 @@ module ActiveJob if queue next false unless queue.to_s == job.fetch(:queue, job_class.queue_name) end + + yield job if block_given? true end end + def flush_enqueued_jobs(only: nil, except: nil) + enqueued_jobs_with(only: only, except: except) do |payload| + args = ActiveJob::Arguments.deserialize(payload[:args]) + instantiate_job(payload.merge(args: args)).perform_now + queue_adapter.performed_jobs << payload + end + end + def serialize_args_for_assertion(args) args.dup.tap do |serialized_args| serialized_args[:args] = ActiveJob::Arguments.serialize(serialized_args[:args]) if serialized_args[:args] diff --git a/activejob/test/cases/exceptions_test.rb b/activejob/test/cases/exceptions_test.rb index 47d4e3c0c2..37bb65538a 100644 --- a/activejob/test/cases/exceptions_test.rb +++ b/activejob/test/cases/exceptions_test.rb @@ -2,6 +2,7 @@ require "helper" require "jobs/retry_job" +require "models/person" class ExceptionsTest < ActiveJob::TestCase setup do @@ -131,4 +132,11 @@ class ExceptionsTest < ActiveJob::TestCase assert_equal [ "Raised SecondDiscardableErrorOfTwo for the 1st time" ], JobBuffer.values end 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 + end end diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb index d0a21a5da3..53b332f6e3 100644 --- a/activejob/test/cases/test_helper_test.rb +++ b/activejob/test/cases/test_helper_test.rb @@ -610,7 +610,7 @@ class EnqueuedJobsTest < ActiveJob::TestCase end class PerformedJobsTest < ActiveJob::TestCase - def test_performed_enqueue_jobs_with_only_option_doesnt_leak_outside_the_block + def test_perform_enqueued_jobs_with_only_option_doesnt_leak_outside_the_block assert_nil queue_adapter.filter perform_enqueued_jobs only: HelloJob do assert_equal HelloJob, queue_adapter.filter @@ -618,7 +618,7 @@ class PerformedJobsTest < ActiveJob::TestCase assert_nil queue_adapter.filter end - def test_performed_enqueue_jobs_with_except_option_doesnt_leak_outside_the_block + def test_perform_enqueued_jobs_with_except_option_doesnt_leak_outside_the_block assert_nil queue_adapter.reject perform_enqueued_jobs except: HelloJob do assert_equal HelloJob, queue_adapter.reject @@ -626,6 +626,22 @@ class PerformedJobsTest < ActiveJob::TestCase assert_nil queue_adapter.reject end + def test_perform_enqueued_jobs_without_block + HelloJob.perform_later("kevin") + + assert_performed_jobs 1, only: HelloJob do + perform_enqueued_jobs + end + end + + def test_perform_enqueued_jobs_without_block_respects_filter + HelloJob.perform_later("kevin") + + assert_no_performed_jobs do + perform_enqueued_jobs only: LoggingJob + end + end + def test_assert_performed_jobs assert_nothing_raised do assert_performed_jobs 1 do diff --git a/activejob/test/jobs/retry_job.rb b/activejob/test/jobs/retry_job.rb index 1383fffd7d..68dc17e16c 100644 --- a/activejob/test/jobs/retry_job.rb +++ b/activejob/test/jobs/retry_job.rb @@ -24,6 +24,7 @@ class RetryJob < ActiveJob::Base retry_on ExponentialWaitTenAttemptsError, wait: :exponentially_longer, attempts: 10 retry_on CustomWaitTenAttemptsError, wait: ->(executions) { executions * 2 }, attempts: 10 retry_on(CustomCatchError) { |job, error| JobBuffer.add("Dealt with a job that failed to retry in a custom way after #{job.arguments.second} attempts. Message: #{error.message}") } + retry_on(ActiveJob::DeserializationError) { |job, error| JobBuffer.add("Raised #{error.class} for the #{job.executions} time") } discard_on DiscardableError discard_on FirstDiscardableErrorOfTwo, SecondDiscardableErrorOfTwo diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb index 0478915be7..3753040316 100644 --- a/activemodel/lib/active_model/validations/numericality.rb +++ b/activemodel/lib/active_model/validations/numericality.rb @@ -23,6 +23,8 @@ module ActiveModel if record.respond_to?(came_from_user) && record.public_send(came_from_user) raw_value = record.read_attribute_before_type_cast(attr_name) + elsif record.respond_to?(:read_attribute) + raw_value = record.read_attribute(attr_name) end raw_value ||= value diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index cfd723cd5f..f6a6aa05a9 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,7 @@ +* SQLite3 adapter `alter_table` method restores foreign keys. + + *Yasuo Honda* + * Allow `:to_table` option to `invert_remove_foreign_key`. Example: diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index b5fb0092d7..0166ed98ca 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -34,7 +34,7 @@ module ActiveRecord::Associations::Builder # :nodoc: foreign_key = reflection.foreign_key cache_column = reflection.counter_cache_column - if (@_after_replace_counter_called ||= false) + if @_after_replace_counter_called ||= false @_after_replace_counter_called = false elsif association(reflection.name).target_changed? if reflection.polymorphic? diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index bee74dc33d..c61e94f159 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -409,12 +409,23 @@ module ActiveRecord def alter_table(table_name, options = {}) altered_table_name = "a#{table_name}" - caller = lambda { |definition| yield definition if block_given? } + foreign_keys = foreign_keys(table_name) + + caller = lambda do |definition| + rename = options[:rename] || {} + foreign_keys.each do |fk| + if column = rename[fk.options[:column]] + fk.options[:column] = column + end + definition.foreign_key(fk.to_table, fk.options) + end + + yield definition if block_given? + end transaction do disable_referential_integrity do - move_table(table_name, altered_table_name, - options.merge(temporary: true)) + move_table(table_name, altered_table_name, options.merge(temporary: true)) move_table(altered_table_name, table_name, &caller) end end @@ -446,6 +457,7 @@ module ActiveRecord primary_key: column_name == from_primary_key ) end + yield @definition if block_given? end copy_table_indexes(from, to, options[:rename] || {}) diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb index 1ae6840921..6b84431343 100644 --- a/activerecord/lib/active_record/log_subscriber.rb +++ b/activerecord/lib/active_record/log_subscriber.rb @@ -4,6 +4,8 @@ module ActiveRecord class LogSubscriber < ActiveSupport::LogSubscriber IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"] + class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new + def self.runtime=(value) ActiveRecord::RuntimeRegistry.sql_runtime = value end @@ -100,21 +102,15 @@ module ActiveRecord end def log_query_source - location = extract_query_source_location(caller_locations) - - if location - source = "#{location.path}:#{location.lineno}" - source = source.sub("#{::Rails.root}/", "") if defined?(::Rails.root) + source = extract_query_source_location(caller) + if source logger.debug(" ↳ #{source}") end end - RAILS_GEM_ROOT = File.expand_path("../../..", __dir__) + "/" - PATHS_TO_IGNORE = /\A(#{RAILS_GEM_ROOT}|#{RbConfig::CONFIG["rubylibdir"]})/ - def extract_query_source_location(locations) - locations.find { |line| line.absolute_path && !line.absolute_path.match?(PATHS_TO_IGNORE) } + backtrace_cleaner.clean(locations).first end end end diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 009f412234..7ece083fd4 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -77,6 +77,10 @@ module ActiveRecord ActiveSupport.on_load(:active_record) { self.logger ||= ::Rails.logger } end + initializer "active_record.backtrace_cleaner" do + ActiveSupport.on_load(:active_record) { LogSubscriber.backtrace_cleaner = ::Rails.backtrace_cleaner } + end + initializer "active_record.migration_error" do if config.active_record.delete(:migration_error) == :page_load config.app_middleware.insert_after ::ActionDispatch::Callbacks, diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index f414fbf64b..482302055d 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -781,7 +781,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end def test_has_many_through_polymorphic_has_manys_works - assert_equal [10, 20].to_set, pirates(:redbeard).treasure_estimates.map(&:price).to_set + assert_equal ["$10.00", "$20.00"].to_set, pirates(:redbeard).treasure_estimates.map(&:price).to_set end def test_symbols_as_keys diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index d7e898a1c0..9eea34d2b9 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -661,6 +661,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase self.table_name = "books" belongs_to :author, class_name: "SpecialAuthor" has_one :subscription, class_name: "SpecialSupscription", foreign_key: "subscriber_id" + + enum status: [:proposed, :written, :published] end class SpecialAuthor < ActiveRecord::Base @@ -678,6 +680,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase book = SpecialBook.create!(status: "published") author.book = book + assert_equal "published", book.status assert_not_equal 0, SpecialAuthor.joins(:book).where(books: { status: "published" }).count end diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb index c471dd1106..bb233fbf74 100644 --- a/activerecord/test/cases/migration/foreign_key_test.rb +++ b/activerecord/test/cases/migration/foreign_key_test.rb @@ -53,16 +53,49 @@ if ActiveRecord::Base.connection.supports_foreign_keys_in_create? end def test_change_column_of_parent_table - foreign_keys = ActiveRecord::Base.connection.foreign_keys("astronauts") rocket = Rocket.create!(name: "myrocket") rocket.astronauts << Astronaut.create! @connection.change_column_null :rockets, :name, false + foreign_keys = @connection.foreign_keys("astronauts") + assert_equal 1, foreign_keys.size + + fk = foreign_keys.first + assert_equal "myrocket", Rocket.first.name + assert_equal "astronauts", fk.from_table + assert_equal "rockets", fk.to_table + end + + def test_rename_column_of_child_table + rocket = Rocket.create!(name: "myrocket") + rocket.astronauts << Astronaut.create! + + @connection.rename_column :astronauts, :name, :astronaut_name + + foreign_keys = @connection.foreign_keys("astronauts") + assert_equal 1, foreign_keys.size + + fk = foreign_keys.first + assert_equal "myrocket", Rocket.first.name + assert_equal "astronauts", fk.from_table + assert_equal "rockets", fk.to_table + end + + def test_rename_reference_column_of_child_table + rocket = Rocket.create!(name: "myrocket") + rocket.astronauts << Astronaut.create! + + @connection.rename_column :astronauts, :rocket_id, :new_rocket_id + + foreign_keys = @connection.foreign_keys("astronauts") + assert_equal 1, foreign_keys.size + fk = foreign_keys.first assert_equal "myrocket", Rocket.first.name assert_equal "astronauts", fk.from_table assert_equal "rockets", fk.to_table + assert_equal "new_rocket_id", fk.options[:column] end end end diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb index 4cd4515c3b..1192b30b14 100644 --- a/activerecord/test/cases/serialized_attribute_test.rb +++ b/activerecord/test/cases/serialized_attribute_test.rb @@ -322,7 +322,7 @@ class SerializedAttributeTest < ActiveRecord::TestCase topic = Topic.create!(content: {}) topic2 = Topic.create!(content: nil) - assert_equal [topic, topic2], Topic.where(content: nil) + assert_equal [topic, topic2], Topic.where(content: nil).sort_by(&:id) end def test_nil_is_always_persisted_as_null diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb index a33877f43a..66763c727f 100644 --- a/activerecord/test/cases/validations_test.rb +++ b/activerecord/test/cases/validations_test.rb @@ -3,11 +3,11 @@ require "cases/helper" require "models/topic" require "models/reply" -require "models/person" require "models/developer" require "models/computer" require "models/parrot" require "models/company" +require "models/price_estimate" class ValidationsTest < ActiveRecord::TestCase fixtures :topics, :developers @@ -183,6 +183,22 @@ class ValidationsTest < ActiveRecord::TestCase assert_not_predicate klass.new(wibble: BigDecimal("97.179")), :valid? end + def test_numericality_validator_wont_be_affected_by_custom_getter + price_estimate = PriceEstimate.new(price: 50) + + assert_equal "$50.00", price_estimate.price + assert_equal 50, price_estimate.price_before_type_cast + assert_equal 50, price_estimate.read_attribute(:price) + + assert_predicate price_estimate, :price_came_from_user? + assert_predicate price_estimate, :valid? + + price_estimate.save! + + assert_not_predicate price_estimate, :price_came_from_user? + assert_predicate price_estimate, :valid? + end + def test_acceptance_validator_doesnt_require_db_connection klass = Class.new(ActiveRecord::Base) do self.table_name = "posts" diff --git a/activerecord/test/models/price_estimate.rb b/activerecord/test/models/price_estimate.rb index f1f88d8d8d..669d0991f7 100644 --- a/activerecord/test/models/price_estimate.rb +++ b/activerecord/test/models/price_estimate.rb @@ -1,6 +1,14 @@ # frozen_string_literal: true class PriceEstimate < ActiveRecord::Base + include ActiveSupport::NumberHelper + belongs_to :estimate_of, polymorphic: true belongs_to :thing, polymorphic: true + + validates_numericality_of :price + + def price + number_to_currency super + end end diff --git a/activestorage/CHANGELOG.md b/activestorage/CHANGELOG.md index f6f195770c..8bfda4799e 100644 --- a/activestorage/CHANGELOG.md +++ b/activestorage/CHANGELOG.md @@ -1,3 +1,8 @@ +* Added the `ActiveStorage::SetCurrent` concern for custom Active Storage + controllers that can't inherit from `ActiveStorage::BaseController`. + + *George Claghorn* + * Active Storage error classes like `ActiveStorage::IntegrityError` and `ActiveStorage::UnrepresentableError` now inherit from `ActiveStorage::Error` instead of `StandardError`. This permits rescuing `ActiveStorage::Error` to diff --git a/activestorage/app/assets/javascripts/activestorage.js b/activestorage/app/assets/javascripts/activestorage.js index d3fd795a3a..375eb6b533 100644 --- a/activestorage/app/assets/javascripts/activestorage.js +++ b/activestorage/app/assets/javascripts/activestorage.js @@ -855,14 +855,22 @@ return DirectUploadsController; }(); var processingAttribute = "data-direct-uploads-processing"; + var submitButtonsByForm = new WeakMap(); var started = false; function start() { if (!started) { started = true; + document.addEventListener("click", didClick, true); document.addEventListener("submit", didSubmitForm); document.addEventListener("ajax:before", didSubmitRemoteElement); } } + function didClick(event) { + var target = event.target; + if (target.tagName == "INPUT" && target.type == "submit" && target.form) { + submitButtonsByForm.set(target.form, target); + } + } function didSubmitForm(event) { handleFormSubmissionEvent(event); } @@ -894,7 +902,7 @@ } } function submitForm(form) { - var button = findElement(form, "input[type=submit]"); + var button = submitButtonsByForm.get(form) || findElement(form, "input[type=submit]"); if (button) { var _button = button, disabled = _button.disabled; button.disabled = false; @@ -909,6 +917,7 @@ button.click(); form.removeChild(button); } + submitButtonsByForm.delete(form); } function disable(input) { input.disabled = true; diff --git a/activestorage/app/controllers/active_storage/base_controller.rb b/activestorage/app/controllers/active_storage/base_controller.rb index 59312ac8df..b27d2bd8aa 100644 --- a/activestorage/app/controllers/active_storage/base_controller.rb +++ b/activestorage/app/controllers/active_storage/base_controller.rb @@ -1,10 +1,8 @@ # frozen_string_literal: true -# The base controller for all ActiveStorage controllers. +# The base class for all Active Storage controllers. class ActiveStorage::BaseController < ActionController::Base - protect_from_forgery with: :exception + include ActiveStorage::SetCurrent - before_action do - ActiveStorage::Current.host = request.base_url - end + protect_from_forgery with: :exception end diff --git a/activestorage/app/controllers/concerns/active_storage/set_current.rb b/activestorage/app/controllers/concerns/active_storage/set_current.rb new file mode 100644 index 0000000000..597afe7064 --- /dev/null +++ b/activestorage/app/controllers/concerns/active_storage/set_current.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +# Sets the <tt>ActiveStorage::Current.host</tt> attribute, which the disk service uses to generate URLs. +# Include this concern in custom controllers that call ActiveStorage::Blob#service_url, +# ActiveStorage::Variant#service_url, or ActiveStorage::Preview#service_url so the disk service can +# generate URLs using the same host, protocol, and base path as the current request. +module ActiveStorage::SetCurrent + extend ActiveSupport::Concern + + included do + before_action do + ActiveStorage::Current.host = request.base_url + end + end +end diff --git a/activestorage/app/javascript/activestorage/ujs.js b/activestorage/app/javascript/activestorage/ujs.js index 08c535470d..f5353389ef 100644 --- a/activestorage/app/javascript/activestorage/ujs.js +++ b/activestorage/app/javascript/activestorage/ujs.js @@ -2,16 +2,25 @@ import { DirectUploadsController } from "./direct_uploads_controller" import { findElement } from "./helpers" const processingAttribute = "data-direct-uploads-processing" +const submitButtonsByForm = new WeakMap let started = false export function start() { if (!started) { started = true + document.addEventListener("click", didClick, true) document.addEventListener("submit", didSubmitForm) document.addEventListener("ajax:before", didSubmitRemoteElement) } } +function didClick(event) { + const { target } = event + if (target.tagName == "INPUT" && target.type == "submit" && target.form) { + submitButtonsByForm.set(target.form, target) + } +} + function didSubmitForm(event) { handleFormSubmissionEvent(event) } @@ -49,7 +58,8 @@ function handleFormSubmissionEvent(event) { } function submitForm(form) { - let button = findElement(form, "input[type=submit]") + let button = submitButtonsByForm.get(form) || findElement(form, "input[type=submit]") + if (button) { const { disabled } = button button.disabled = false @@ -64,6 +74,7 @@ function submitForm(form) { button.click() form.removeChild(button) } + submitButtonsByForm.delete(form) } function disable(input) { diff --git a/activestorage/app/jobs/active_storage/analyze_job.rb b/activestorage/app/jobs/active_storage/analyze_job.rb index 2a952f9f74..804ee4557a 100644 --- a/activestorage/app/jobs/active_storage/analyze_job.rb +++ b/activestorage/app/jobs/active_storage/analyze_job.rb @@ -2,6 +2,8 @@ # Provides asynchronous analysis of ActiveStorage::Blob records via ActiveStorage::Blob#analyze_later. class ActiveStorage::AnalyzeJob < ActiveStorage::BaseJob + retry_on ActiveStorage::IntegrityError, attempts: 10, wait: :exponentially_longer + def perform(blob) blob.analyze end diff --git a/activestorage/test/jobs/purge_job_test.rb b/activestorage/test/jobs/purge_job_test.rb index ed4100b78d..251022a96f 100644 --- a/activestorage/test/jobs/purge_job_test.rb +++ b/activestorage/test/jobs/purge_job_test.rb @@ -24,14 +24,4 @@ class ActiveStorage::PurgeJobTest < ActiveJob::TestCase end end end - - test "ignores attached blob" do - User.create! name: "DHH", avatar: @blob - - perform_enqueued_jobs do - assert_nothing_raised do - ActiveStorage::PurgeJob.perform_later @blob - end - end - end end diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb index 16dd733ddb..1796956bd7 100644 --- a/activesupport/lib/active_support/backtrace_cleaner.rb +++ b/activesupport/lib/active_support/backtrace_cleaner.rb @@ -31,6 +31,9 @@ module ActiveSupport class BacktraceCleaner def initialize @filters, @silencers = [], [] + add_gem_filter + add_gem_silencer + add_stdlib_silencer end # Returns the backtrace after all filters and silencers have been run @@ -82,6 +85,26 @@ module ActiveSupport end private + + FORMATTED_GEMS_PATTERN = /\A[^\/]+ \([\w.]+\) / + + def add_gem_filter + gems_paths = (Gem.path | [Gem.default_dir]).map { |p| Regexp.escape(p) } + return if gems_paths.empty? + + gems_regexp = %r{(#{gems_paths.join('|')})/(bundler/)?gems/([^/]+)-([\w.]+)/(.*)} + gems_result = '\3 (\4) \5'.freeze + add_filter { |line| line.sub(gems_regexp, gems_result) } + end + + def add_gem_silencer + add_silencer { |line| FORMATTED_GEMS_PATTERN.match?(line) } + end + + def add_stdlib_silencer + add_silencer { |line| line.start_with?(RbConfig::CONFIG["rubylibdir"]) } + end + def filter_backtrace(backtrace) @filters.each do |f| backtrace = backtrace.map { |line| f.call(line) } diff --git a/activesupport/test/clean_backtrace_test.rb b/activesupport/test/clean_backtrace_test.rb index 1b44c7c9bf..a0a7056952 100644 --- a/activesupport/test/clean_backtrace_test.rb +++ b/activesupport/test/clean_backtrace_test.rb @@ -74,3 +74,43 @@ class BacktraceCleanerFilterAndSilencerTest < ActiveSupport::TestCase assert_equal [ "/class.rb" ], @bc.clean([ "/mongrel/class.rb" ]) end end + +class BacktraceCleanerDefaultFilterAndSilencerTest < ActiveSupport::TestCase + def setup + @bc = ActiveSupport::BacktraceCleaner.new + end + + test "should format installed gems correctly" do + backtrace = [ "#{Gem.default_dir}/gems/nosuchgem-1.2.3/lib/foo.rb" ] + result = @bc.clean(backtrace, :all) + assert_equal "nosuchgem (1.2.3) lib/foo.rb", result[0] + end + + test "should format installed gems not in Gem.default_dir correctly" do + target_dir = Gem.path.detect { |p| p != Gem.default_dir } + # skip this test if default_dir is the only directory on Gem.path + if target_dir + backtrace = [ "#{target_dir}/gems/nosuchgem-1.2.3/lib/foo.rb" ] + result = @bc.clean(backtrace, :all) + assert_equal "nosuchgem (1.2.3) lib/foo.rb", result[0] + end + end + + test "should format gems installed by bundler" do + backtrace = [ "#{Gem.default_dir}/bundler/gems/nosuchgem-1.2.3/lib/foo.rb" ] + result = @bc.clean(backtrace, :all) + assert_equal "nosuchgem (1.2.3) lib/foo.rb", result[0] + end + + test "should silence gems from the backtrace" do + backtrace = [ "#{Gem.path[0]}/gems/nosuchgem-1.2.3/lib/foo.rb" ] + result = @bc.clean(backtrace) + assert_empty result + end + + test "should silence stdlib" do + backtrace = ["#{RbConfig::CONFIG["rubylibdir"]}/lib/foo.rb"] + result = @bc.clean(backtrace) + assert_empty result + end +end diff --git a/activesupport/test/core_ext/object/to_query_test.rb b/activesupport/test/core_ext/object/to_query_test.rb index b0b7ef0913..561dadbbcf 100644 --- a/activesupport/test/core_ext/object/to_query_test.rb +++ b/activesupport/test/core_ext/object/to_query_test.rb @@ -88,7 +88,7 @@ class ToQueryTest < ActiveSupport::TestCase } expected = "foo[contents][][name]=gorby&foo[contents][][id]=123&foo[contents][][name]=puff&foo[contents][][d]=true" - assert_equal expected, URI.decode(params.to_query) + assert_equal expected, URI.decode_www_form_component(params.to_query) end private diff --git a/activesupport/test/key_generator_test.rb b/activesupport/test/key_generator_test.rb index cdde2c573a..9dfc0b2154 100644 --- a/activesupport/test/key_generator_test.rb +++ b/activesupport/test/key_generator_test.rb @@ -9,9 +9,6 @@ rescue LoadError, NameError $stderr.puts "Skipping KeyGenerator test: broken OpenSSL install" else - require "active_support/time" - require "active_support/json" - class KeyGeneratorTest < ActiveSupport::TestCase def setup @secret = SecureRandom.hex(64) diff --git a/guides/assets/stylesheets/main.css b/guides/assets/stylesheets/main.css index cd355b1d1a..2657a84a91 100644 --- a/guides/assets/stylesheets/main.css +++ b/guides/assets/stylesheets/main.css @@ -283,8 +283,12 @@ body { #header .wrapper, #topNav .wrapper, #feature .wrapper {padding-left: 1em; max-width: 960px;} #feature .wrapper {max-width: 640px; padding-right: 23em; position: relative; z-index: 0;} +@media screen and (max-width: 960px) { + #container .wrapper { padding-right: 23em; } +} + @media screen and (max-width: 800px) { - #feature .wrapper { padding-right: 0; } + #feature .wrapper, #container .wrapper { padding-right: 0; } } /* Links diff --git a/guides/rails_guides/levenshtein.rb b/guides/rails_guides/levenshtein.rb index c48af797fa..2213ef754d 100644 --- a/guides/rails_guides/levenshtein.rb +++ b/guides/rails_guides/levenshtein.rb @@ -12,8 +12,8 @@ module RailsGuides n = s.length m = t.length - return m if (0 == n) - return n if (0 == m) + return m if 0 == n + return n if 0 == m d = (0..m).to_a x = nil diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index 6c5f03ab38..37cbf3f53d 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -422,6 +422,21 @@ use the rendered text for the text part. The render command is the same one used inside of Action Controller, so you can use all the same options, such as `:text`, `:inline` etc. +If you would like to render a template located outside of the default `app/views/mailer_name/` directory, you can apply the `prepend_view_path`, like so: + +```ruby +class UserMailer < ApplicationMailer + prepend_view_path "custom/path/to/mailer/view" + + # This will try to load "custom/path/to/mailer/view/welcome_email" template + def welcome_email + # ... + end +end +``` + +You can also consider using the [append_view_path](https://guides.rubyonrails.org/action_view_overview.html#view-paths) method. + #### Caching mailer view You can perform fragment caching in mailer views like in application views using the `cache` method. diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index a2890b9b7a..91cc175095 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -1277,16 +1277,6 @@ class Article < ApplicationRecord end ``` -This is exactly the same as defining a class method, and which you use is a matter of personal preference: - -```ruby -class Article < ApplicationRecord - def self.published - where(published: true) - end -end -``` - Scopes are also chainable within scopes: ```ruby diff --git a/guides/source/active_storage_overview.md b/guides/source/active_storage_overview.md index 6933717c2b..1c15d075b9 100644 --- a/guides/source/active_storage_overview.md +++ b/guides/source/active_storage_overview.md @@ -174,7 +174,7 @@ google: Add the [`google-cloud-storage`](https://github.com/GoogleCloudPlatform/google-cloud-ruby/tree/master/google-cloud-storage) gem to your `Gemfile`: ```ruby -gem "google-cloud-storage", "~> 1.8", require: false +gem "google-cloud-storage", "~> 1.11", require: false ``` ### Mirror Service diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md index 7a414f21fe..439797ddcb 100644 --- a/guides/source/development_dependencies_install.md +++ b/guides/source/development_dependencies_install.md @@ -350,31 +350,31 @@ prerequisite for installing this package manager is that On macOS, you can run: ```bash -brew install yarn +$ brew install yarn ``` On Ubuntu, you can run: ```bash -curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - -echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list +$ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - +$ echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list -sudo apt-get update && sudo apt-get install yarn +$ sudo apt-get update && sudo apt-get install yarn ``` On Fedora or CentOS, just run: ```bash -sudo wget https://dl.yarnpkg.com/rpm/yarn.repo -O /etc/yum.repos.d/yarn.repo +$ sudo wget https://dl.yarnpkg.com/rpm/yarn.repo -O /etc/yum.repos.d/yarn.repo -sudo yum install yarn +$ sudo yum install yarn ``` Finally, after installing Yarn, you will need to run the following command inside of the `activestorage` directory to install the dependencies: ```bash -yarn install +$ yarn install ``` Extracting previews, tested in Active Storage's test suite requires third-party diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 81cb5a6c9f..8904753e20 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,10 +1,18 @@ +* Deprecate `rake initializers` in favor of `rails initializers`. + + *Annie-Claude Côté* + +* Deprecate `rake dev:cache` in favor of `rails dev:cache`. + + *Annie-Claude Côté* + * Deprecate `rails notes` subcommands in favor of passing an `annotations` argument to `rails notes`. The following subcommands are replaced by passing `--annotations` or `-a` to `rails notes`: - - `rails notes:custom ANNOTATION=custom` is deprecated in favor of using `rails notes -a custom`. - - `rails notes:optimize` is deprecated in favor of using `rails notes -a OPTIMIZE`. - - `rails notes:todo` is deprecated in favor of using`rails notes -a TODO`. - - `rails notes:fixme` is deprecated in favor of using `rails notes -a FIXME`. + - `rails notes:custom ANNOTATION=custom` is deprecated in favor of using `rails notes -a custom`. + - `rails notes:optimize` is deprecated in favor of using `rails notes -a OPTIMIZE`. + - `rails notes:todo` is deprecated in favor of using`rails notes -a TODO`. + - `rails notes:fixme` is deprecated in favor of using `rails notes -a FIXME`. *Annie-Claude Côté* diff --git a/railties/lib/rails/backtrace_cleaner.rb b/railties/lib/rails/backtrace_cleaner.rb index 0e78959966..b1e3c923b7 100644 --- a/railties/lib/rails/backtrace_cleaner.rb +++ b/railties/lib/rails/backtrace_cleaner.rb @@ -16,19 +16,7 @@ module Rails add_filter { |line| line.sub(@root, EMPTY_STRING) } add_filter { |line| line.sub(RENDER_TEMPLATE_PATTERN, EMPTY_STRING) } add_filter { |line| line.sub(DOT_SLASH, SLASH) } # for tests - - add_gem_filters add_silencer { |line| !APP_DIRS_PATTERN.match?(line) } end - - private - def add_gem_filters - gems_paths = (Gem.path | [Gem.default_dir]).map { |p| Regexp.escape(p) } - return if gems_paths.empty? - - gems_regexp = %r{(#{gems_paths.join('|')})/gems/([^/]+)-([\w.]+)/(.*)} - gems_result = '\2 (\3) \4'.freeze - add_filter { |line| line.sub(gems_regexp, gems_result) } - end end end diff --git a/railties/lib/rails/command/spellchecker.rb b/railties/lib/rails/command/spellchecker.rb index 04485097fa..085d5b16df 100644 --- a/railties/lib/rails/command/spellchecker.rb +++ b/railties/lib/rails/command/spellchecker.rb @@ -24,8 +24,8 @@ module Rails n = s.length m = t.length - return m if (0 == n) - return n if (0 == m) + return m if 0 == n + return n if 0 == m d = (0..m).to_a x = nil diff --git a/railties/lib/rails/commands/dev/dev_command.rb b/railties/lib/rails/commands/dev/dev_command.rb new file mode 100644 index 0000000000..820dc4db9e --- /dev/null +++ b/railties/lib/rails/commands/dev/dev_command.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "rails/dev_caching" + +module Rails + module Command + class DevCommand < Base # :nodoc: + desc "Toggle development mode caching on/off" + def cache + Rails::DevCaching.enable_by_file + end + end + end +end diff --git a/railties/lib/rails/commands/initializers/initializers_command.rb b/railties/lib/rails/commands/initializers/initializers_command.rb new file mode 100644 index 0000000000..559546acea --- /dev/null +++ b/railties/lib/rails/commands/initializers/initializers_command.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Rails + module Command + class InitializersCommand < Base # :nodoc: + desc "Print out all defined initializers in the order they are invoked by Rails." + def perform + require_application_and_environment! + + Rails.application.initializers.tsort_each do |initializer| + say "#{initializer.context_class}.#{initializer.name}" + end + end + end + end +end diff --git a/railties/lib/rails/tasks/dev.rake b/railties/lib/rails/tasks/dev.rake index 5aea6f7dc5..8d75965294 100644 --- a/railties/lib/rails/tasks/dev.rake +++ b/railties/lib/rails/tasks/dev.rake @@ -1,10 +1,11 @@ # frozen_string_literal: true -require "rails/dev_caching" +require "rails/command" +require "active_support/deprecation" namespace :dev do - desc "Toggle development mode caching on/off" task :cache do - Rails::DevCaching.enable_by_file + ActiveSupport::Deprecation.warn("Using `bin/rake dev:cache` is deprecated and will be removed in Rails 6.1. Use `bin/rails dev:cache` instead.\n") + Rails::Command.invoke "dev:cache" end end diff --git a/railties/lib/rails/tasks/initializers.rake b/railties/lib/rails/tasks/initializers.rake index ae85cb0f86..7ccf7455bb 100644 --- a/railties/lib/rails/tasks/initializers.rake +++ b/railties/lib/rails/tasks/initializers.rake @@ -1,8 +1,10 @@ # frozen_string_literal: true +require "rails/command" +require "active_support/deprecation" + desc "Print out all defined initializers in the order they are invoked by Rails." -task initializers: :environment do - Rails.application.initializers.tsort_each do |initializer| - puts "#{initializer.context_class}.#{initializer.name}" - end +task :initializers do + ActiveSupport::Deprecation.warn("Using `bin/rake initializers` is deprecated and will be removed in Rails 6.1. Use `bin/rails initializers` instead.\n") + Rails::Command.invoke "initializers" end diff --git a/railties/test/application/rake/dev_test.rb b/railties/test/application/rake/dev_test.rb index 66e1ac9d99..e408760ecc 100644 --- a/railties/test/application/rake/dev_test.rb +++ b/railties/test/application/rake/dev_test.rb @@ -17,33 +17,46 @@ module ApplicationTests test "dev:cache creates file and outputs message" do Dir.chdir(app_path) do - output = rails("dev:cache") - assert File.exist?("tmp/caching-dev.txt") - assert_match(/Development mode is now being cached/, output) + stderr = capture(:stderr) do + output = run_rake_dev_cache + assert File.exist?("tmp/caching-dev.txt") + assert_match(/Development mode is now being cached/, output) + end + assert_match(/DEPRECATION WARNING: Using `bin\/rake dev:cache` is deprecated and will be removed in Rails 6.1/, stderr) end end test "dev:cache deletes file and outputs message" do Dir.chdir(app_path) do - rails "dev:cache" # Create caching file. - output = rails("dev:cache") # Delete caching file. - assert_not File.exist?("tmp/caching-dev.txt") - assert_match(/Development mode is no longer being cached/, output) + stderr = capture(:stderr) do + run_rake_dev_cache # Create caching file. + output = run_rake_dev_cache # Delete caching file. + assert_not File.exist?("tmp/caching-dev.txt") + assert_match(/Development mode is no longer being cached/, output) + end + assert_match(/DEPRECATION WARNING: Using `bin\/rake dev:cache` is deprecated and will be removed in Rails 6.1/, stderr) end end test "dev:cache touches tmp/restart.txt" do Dir.chdir(app_path) do - rails "dev:cache" - assert File.exist?("tmp/restart.txt") - - prev_mtime = File.mtime("tmp/restart.txt") - sleep(1) - rails "dev:cache" - curr_mtime = File.mtime("tmp/restart.txt") - assert_not_equal prev_mtime, curr_mtime + stderr = capture(:stderr) do + run_rake_dev_cache + assert File.exist?("tmp/restart.txt") + + prev_mtime = File.mtime("tmp/restart.txt") + run_rake_dev_cache + curr_mtime = File.mtime("tmp/restart.txt") + assert_not_equal prev_mtime, curr_mtime + end + assert_match(/DEPRECATION WARNING: Using `bin\/rake dev:cache` is deprecated and will be removed in Rails 6.1/, stderr) end end + + private + def run_rake_dev_cache + `bin/rake dev:cache` + end end end end diff --git a/railties/test/application/rake/initializers_test.rb b/railties/test/application/rake/initializers_test.rb new file mode 100644 index 0000000000..fb498e28ad --- /dev/null +++ b/railties/test/application/rake/initializers_test.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require "isolation/abstract_unit" + +module ApplicationTests + module RakeTests + class RakeInitializersTest < ActiveSupport::TestCase + setup :build_app + teardown :teardown_app + + test "`rake initializers` prints out defined initializers invoked by Rails" do + capture(:stderr) do + initial_output = run_rake_initializers + initial_output_length = initial_output.split("\n").length + + assert_operator initial_output_length, :>, 0 + assert_not initial_output.include?("set_added_test_module") + + add_to_config <<-RUBY + initializer(:set_added_test_module) { } + RUBY + + final_output = run_rake_initializers + final_output_length = final_output.split("\n").length + + assert_equal 1, (final_output_length - initial_output_length) + assert final_output.include?("set_added_test_module") + end + end + + test "`rake initializers` outputs a deprecation warning" do + stderr = capture(:stderr) { run_rake_initializers } + assert_match(/DEPRECATION WARNING: Using `bin\/rake initializers` is deprecated and will be removed in Rails 6.1/, stderr) + end + + private + def run_rake_initializers + Dir.chdir(app_path) { `bin/rake initializers` } + end + end + end +end diff --git a/railties/test/backtrace_cleaner_test.rb b/railties/test/backtrace_cleaner_test.rb index 8490f9eb10..90e084ddca 100644 --- a/railties/test/backtrace_cleaner_test.rb +++ b/railties/test/backtrace_cleaner_test.rb @@ -8,22 +8,6 @@ class BacktraceCleanerTest < ActiveSupport::TestCase @cleaner = Rails::BacktraceCleaner.new end - test "should format installed gems correctly" do - backtrace = [ "#{Gem.path[0]}/gems/nosuchgem-1.2.3/lib/foo.rb" ] - result = @cleaner.clean(backtrace, :all) - assert_equal "nosuchgem (1.2.3) lib/foo.rb", result[0] - end - - test "should format installed gems not in Gem.default_dir correctly" do - target_dir = Gem.path.detect { |p| p != Gem.default_dir } - # skip this test if default_dir is the only directory on Gem.path - if target_dir - backtrace = [ "#{target_dir}/gems/nosuchgem-1.2.3/lib/foo.rb" ] - result = @cleaner.clean(backtrace, :all) - assert_equal "nosuchgem (1.2.3) lib/foo.rb", result[0] - end - end - test "should consider traces from irb lines as User code" do backtrace = [ "(irb):1", "/Path/to/rails/railties/lib/rails/commands/console.rb:77:in `start'", diff --git a/railties/test/commands/dev_test.rb b/railties/test/commands/dev_test.rb new file mode 100644 index 0000000000..ae8516fe9a --- /dev/null +++ b/railties/test/commands/dev_test.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require "isolation/abstract_unit" +require "rails/command" + +class Rails::Command::DevTest < ActiveSupport::TestCase + setup :build_app + teardown :teardown_app + + test "`rails dev:cache` creates both caching and restart file when restart file doesn't exist and dev caching is currently off" do + Dir.chdir(app_path) do + assert_not File.exist?("tmp/caching-dev.txt") + assert_not File.exist?("tmp/restart.txt") + + assert_equal <<~OUTPUT, run_dev_cache_command + Development mode is now being cached. + OUTPUT + + assert File.exist?("tmp/caching-dev.txt") + assert File.exist?("tmp/restart.txt") + end + end + + test "`rails dev:cache` creates caching file and touches restart file when dev caching is currently off" do + Dir.chdir(app_path) do + app_file("tmp/restart.txt", "") + + assert_not File.exist?("tmp/caching-dev.txt") + assert File.exist?("tmp/restart.txt") + restart_file_time_before = File.mtime("tmp/restart.txt") + + assert_equal <<~OUTPUT, run_dev_cache_command + Development mode is now being cached. + OUTPUT + + assert File.exist?("tmp/caching-dev.txt") + restart_file_time_after = File.mtime("tmp/restart.txt") + assert_operator restart_file_time_before, :<, restart_file_time_after + end + end + + test "`rails dev:cache` removes caching file and touches restart file when dev caching is currently on" do + Dir.chdir(app_path) do + app_file("tmp/caching-dev.txt", "") + app_file("tmp/restart.txt", "") + + assert File.exist?("tmp/caching-dev.txt") + assert File.exist?("tmp/restart.txt") + restart_file_time_before = File.mtime("tmp/restart.txt") + + assert_equal <<~OUTPUT, run_dev_cache_command + Development mode is no longer being cached. + OUTPUT + + assert_not File.exist?("tmp/caching-dev.txt") + restart_file_time_after = File.mtime("tmp/restart.txt") + assert_operator restart_file_time_before, :<, restart_file_time_after + end + end + + private + def run_dev_cache_command + rails "dev:cache" + end +end diff --git a/railties/test/commands/initializers_test.rb b/railties/test/commands/initializers_test.rb new file mode 100644 index 0000000000..bdfbb3021c --- /dev/null +++ b/railties/test/commands/initializers_test.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require "isolation/abstract_unit" +require "rails/command" + +class Rails::Command::InitializersTest < ActiveSupport::TestCase + setup :build_app + teardown :teardown_app + + test "`rails initializers` prints out defined initializers invoked by Rails" do + initial_output = run_initializers_command + initial_output_length = initial_output.split("\n").length + + assert_operator initial_output_length, :>, 0 + assert_not initial_output.include?("set_added_test_module") + + add_to_config <<-RUBY + initializer(:set_added_test_module) { } + RUBY + + final_output = run_initializers_command + final_output_length = final_output.split("\n").length + + assert_equal 1, (final_output_length - initial_output_length) + assert final_output.include?("set_added_test_module") + end + + private + def run_initializers_command + rails "initializers" + end +end |