diff options
19 files changed, 136 insertions, 69 deletions
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index d4620d085a..663832e9bb 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -224,7 +224,7 @@ XML end def test_params_round_trip - params = {"foo"=>{"contents"=>[{"name"=>"gorby", "id"=>"123"}, {"name"=>"puff", "d"=>"true"}]}} + 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" } diff --git a/actionpack/test/journey/route/definition/scanner_test.rb b/actionpack/test/journey/route/definition/scanner_test.rb index bcbe4388c3..092177d315 100644 --- a/actionpack/test/journey/route/definition/scanner_test.rb +++ b/actionpack/test/journey/route/definition/scanner_test.rb @@ -30,6 +30,12 @@ module ActionDispatch ["/~page", [[:SLASH, "/"], [:LITERAL, "~page"]]], ["/pa-ge", [[:SLASH, "/"], [:LITERAL, "pa-ge"]]], ["/:page", [[:SLASH, "/"], [:SYMBOL, ":page"]]], + ["/:page|*foo", [ + [:SLASH, "/"], + [:SYMBOL, ":page"], + [:OR, "|"], + [:STAR, "*foo"] + ]], ["/(:page)", [ [:SLASH, "/"], [:LPAREN, "("], diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 231837dcb7..fad09182c9 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,7 @@ +* Fix `touch` option to behave consistently with `Persistence#touch` method. + + *Ryuta Kamizono* + * Migrations raise when duplicate column definition. Fixes #33024. diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb index faaedf1d4a..0d8748d7e6 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -47,8 +47,12 @@ module ActiveRecord reflection = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? } counter_name = reflection.counter_cache_column - updates = { counter_name.to_sym => object.send(counter_association).count(:all) } - updates.merge!(touch_updates(touch)) if touch + updates = { counter_name => object.send(counter_association).count(:all) } + + if touch + names = touch if touch != true + updates.merge!(touch_attributes_with_time(*names)) + end unscoped.where(primary_key => object.id).update_all(updates) end @@ -68,8 +72,8 @@ module ActiveRecord # * +counters+ - A Hash containing the names of the fields # to update as keys and the amount to update the field by as values. # * <tt>:touch</tt> option - Touch timestamp columns when updating. - # Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to - # touch that column or an array of symbols to touch just those ones. + # If attribute names are passed, they are updated along with updated_at/on + # attributes. # # ==== Examples # @@ -107,7 +111,8 @@ module ActiveRecord end if touch - touch_updates = touch_updates(touch) + names = touch if touch != true + touch_updates = touch_attributes_with_time(*names) updates << sanitize_sql_for_assignment(touch_updates) unless touch_updates.empty? end @@ -171,13 +176,6 @@ module ActiveRecord def decrement_counter(counter_name, id, touch: nil) update_counters(id, counter_name => -1, touch: touch) end - - private - def touch_updates(touch) - touch = timestamp_attributes_for_update_in_model if touch == true - touch_time = current_time_from_proper_timezone - Array(touch).map { |column| [ column, touch_time ] }.to_h - end end private diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 025201c20b..6ace973c29 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -831,10 +831,14 @@ module ActiveRecord write "== %s %s" % [text, "=" * length] end + # Takes a message argument and outputs it as is. + # A second boolean argument can be passed to specify whether to indent or not. def say(message, subitem = false) write "#{subitem ? " ->" : "--"} #{message}" end + # Outputs text along with how long it took to run its block. + # If the block returns an integer it assumes it is the number of rows affected. def say_with_time(message) say(message) result = nil @@ -844,6 +848,7 @@ module ActiveRecord result end + # Takes a block as an argument and suppresses any output generated by the block. def suppress_messages save, self.verbose = verbose, false yield diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index c055b97061..c911cbe5ec 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -393,10 +393,7 @@ module ActiveRecord # Person.where(name: 'David').touch_all # # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670' WHERE \"people\".\"name\" = 'David'" def touch_all(*names, time: nil) - attributes = Array(names) + klass.timestamp_attributes_for_update_in_model - time ||= klass.current_time_from_proper_timezone - updates = {} - attributes.each { |column| updates[column] = time } + updates = touch_attributes_with_time(*names, time: time) if klass.locking_enabled? quoted_locking_column = connection.quote_column_name(klass.locking_column) diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index e47f06bf3a..d32f971ad1 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -53,12 +53,10 @@ module ActiveRecord end module ClassMethods # :nodoc: - def timestamp_attributes_for_update_in_model - timestamp_attributes_for_update.select { |c| column_names.include?(c) } - end - - def current_time_from_proper_timezone - default_timezone == :utc ? Time.now.utc : Time.now + def touch_attributes_with_time(*names, time: nil) + attribute_names = timestamp_attributes_for_update_in_model + attribute_names |= names.map(&:to_s) + attribute_names.index_with(time ||= current_time_from_proper_timezone) end private @@ -66,6 +64,10 @@ module ActiveRecord timestamp_attributes_for_create.select { |c| column_names.include?(c) } end + def timestamp_attributes_for_update_in_model + timestamp_attributes_for_update.select { |c| column_names.include?(c) } + end + def all_timestamp_attributes_in_model timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model end @@ -77,6 +79,10 @@ module ActiveRecord def timestamp_attributes_for_update ["updated_at", "updated_on"] end + + def current_time_from_proper_timezone + default_timezone == :utc ? Time.now.utc : Time.now + end end private @@ -116,7 +122,7 @@ module ActiveRecord end def timestamp_attributes_for_update_in_model - self.class.timestamp_attributes_for_update_in_model + self.class.send(:timestamp_attributes_for_update_in_model) end def all_timestamp_attributes_in_model @@ -124,7 +130,7 @@ module ActiveRecord end def current_time_from_proper_timezone - self.class.current_time_from_proper_timezone + self.class.send(:current_time_from_proper_timezone) end def max_updated_column_timestamp(timestamp_names = timestamp_attributes_for_update_in_model) diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb index e0948f90ac..99d286dc52 100644 --- a/activerecord/test/cases/counter_cache_test.rb +++ b/activerecord/test/cases/counter_cache_test.rb @@ -280,38 +280,38 @@ class CounterCacheTest < ActiveRecord::TestCase end test "update counters with touch: :written_on" do - assert_touching @topic, :written_on do + assert_touching @topic, :updated_at, :written_on do Topic.update_counters(@topic.id, replies_count: -1, touch: :written_on) end end test "update multiple counters with touch: :written_on" do - assert_touching @topic, :written_on do + assert_touching @topic, :updated_at, :written_on do Topic.update_counters(@topic.id, replies_count: 2, unique_replies_count: 2, touch: :written_on) end end test "reset counters with touch: :written_on" do - assert_touching @topic, :written_on do + assert_touching @topic, :updated_at, :written_on do Topic.reset_counters(@topic.id, :replies, touch: :written_on) end end test "reset multiple counters with touch: :written_on" do - assert_touching @topic, :written_on do + assert_touching @topic, :updated_at, :written_on do Topic.update_counters(@topic.id, replies_count: 1, unique_replies_count: 1) Topic.reset_counters(@topic.id, :replies, :unique_replies, touch: :written_on) end end test "increment counters with touch: :written_on" do - assert_touching @topic, :written_on do + assert_touching @topic, :updated_at, :written_on do Topic.increment_counter(:replies_count, @topic.id, touch: :written_on) end end test "decrement counters with touch: :written_on" do - assert_touching @topic, :written_on do + assert_touching @topic, :updated_at, :written_on do Topic.decrement_counter(:replies_count, @topic.id, touch: :written_on) end end diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index 8513edb0ab..33bd74e114 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -445,32 +445,38 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_equal 0, car.wheels_count assert_equal 0, car.lock_version - previously_car_updated_at = car.updated_at - travel(2.second) do + previously_updated_at = car.updated_at + previously_wheels_owned_at = car.wheels_owned_at + travel(1.second) do Wheel.create!(wheelable: car) end assert_equal 1, car.reload.wheels_count - assert_not_equal previously_car_updated_at, car.updated_at assert_equal 1, car.lock_version + assert_operator previously_updated_at, :<, car.updated_at + assert_operator previously_wheels_owned_at, :<, car.wheels_owned_at - previously_car_updated_at = car.updated_at - travel(1.day) do + previously_updated_at = car.updated_at + previously_wheels_owned_at = car.wheels_owned_at + travel(2.second) do car.wheels.first.update(size: 42) end assert_equal 1, car.reload.wheels_count - assert_not_equal previously_car_updated_at, car.updated_at assert_equal 2, car.lock_version + assert_operator previously_updated_at, :<, car.updated_at + assert_operator previously_wheels_owned_at, :<, car.wheels_owned_at - previously_car_updated_at = car.updated_at - travel(2.second) do + previously_updated_at = car.updated_at + previously_wheels_owned_at = car.wheels_owned_at + travel(3.second) do car.wheels.first.destroy! end assert_equal 0, car.reload.wheels_count - assert_not_equal previously_car_updated_at, car.updated_at assert_equal 3, car.lock_version + assert_operator previously_updated_at, :<, car.updated_at + assert_operator previously_wheels_owned_at, :<, car.wheels_owned_at end def test_polymorphic_destroy_with_dependencies_and_lock_version diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 4c332e30aa..7348a22dd3 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -206,12 +206,28 @@ class PersistenceTest < ActiveRecord::TestCase assert_equal initial_credit + 2, a1.reload.credit_limit end - def test_increment_updates_timestamps + def test_increment_with_touch_updates_timestamps topic = topics(:first) - topic.update_columns(updated_at: 5.minutes.ago) - previous_updated_at = topic.updated_at - topic.increment!(:replies_count, touch: true) - assert_operator previous_updated_at, :<, topic.reload.updated_at + assert_equal 1, topic.replies_count + previously_updated_at = topic.updated_at + travel(1.second) do + topic.increment!(:replies_count, touch: true) + end + assert_equal 2, topic.reload.replies_count + assert_operator previously_updated_at, :<, topic.updated_at + end + + def test_increment_with_touch_an_attribute_updates_timestamps + topic = topics(:first) + assert_equal 1, topic.replies_count + previously_updated_at = topic.updated_at + previously_written_on = topic.written_on + travel(1.second) do + topic.increment!(:replies_count, touch: :written_on) + end + assert_equal 2, topic.reload.replies_count + assert_operator previously_updated_at, :<, topic.updated_at + assert_operator previously_written_on, :<, topic.written_on end def test_destroy_all @@ -333,12 +349,28 @@ class PersistenceTest < ActiveRecord::TestCase assert_equal 41, accounts(:signals37, :reload).credit_limit end - def test_decrement_updates_timestamps + def test_decrement_with_touch_updates_timestamps topic = topics(:first) - topic.update_columns(updated_at: 5.minutes.ago) - previous_updated_at = topic.updated_at - topic.decrement!(:replies_count, touch: true) - assert_operator previous_updated_at, :<, topic.reload.updated_at + assert_equal 1, topic.replies_count + previously_updated_at = topic.updated_at + travel(1.second) do + topic.decrement!(:replies_count, touch: true) + end + assert_equal 0, topic.reload.replies_count + assert_operator previously_updated_at, :<, topic.updated_at + end + + def test_decrement_with_touch_an_attribute_updates_timestamps + topic = topics(:first) + assert_equal 1, topic.replies_count + previously_updated_at = topic.updated_at + previously_written_on = topic.written_on + travel(1.second) do + topic.decrement!(:replies_count, touch: :written_on) + end + assert_equal 0, topic.reload.replies_count + assert_operator previously_updated_at, :<, topic.updated_at + assert_operator previously_written_on, :<, topic.written_on end def test_create diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb index 3d6a7a96c2..8614926626 100644 --- a/activerecord/test/models/car.rb +++ b/activerecord/test/models/car.rb @@ -20,6 +20,8 @@ class Car < ActiveRecord::Base scope :incl_engines, -> { includes(:engines) } scope :order_using_new_style, -> { order("name asc") } + + attribute :wheels_owned_at, :datetime, default: -> { Time.now } end class CoolCar < Car diff --git a/activerecord/test/models/wheel.rb b/activerecord/test/models/wheel.rb index 8db57d181e..22fc74995f 100644 --- a/activerecord/test/models/wheel.rb +++ b/activerecord/test/models/wheel.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true class Wheel < ActiveRecord::Base - belongs_to :wheelable, polymorphic: true, counter_cache: true, touch: true + belongs_to :wheelable, polymorphic: true, counter_cache: true, touch: :wheels_owned_at end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 8bf2f93c26..266e55f682 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -36,6 +36,7 @@ ActiveRecord::Schema.define do create_table :aircraft, force: true do |t| t.string :name t.integer :wheels_count, default: 0, null: false + t.datetime :wheels_owned_at end create_table :articles, force: true do |t| @@ -126,7 +127,8 @@ ActiveRecord::Schema.define do create_table :cars, force: true do |t| t.string :name t.integer :engines_count - t.integer :wheels_count, default: 0 + t.integer :wheels_count, default: 0, null: false + t.datetime :wheels_owned_at t.column :lock_version, :integer, null: false, default: 0 t.timestamps null: false end diff --git a/activestorage/test/service/gcs_service_test.rb b/activestorage/test/service/gcs_service_test.rb index fc2d9d0fa7..2f212b5811 100644 --- a/activestorage/test/service/gcs_service_test.rb +++ b/activestorage/test/service/gcs_service_test.rb @@ -44,7 +44,7 @@ if SERVICE_CONFIGURATIONS[:gcs] url = @service.url(key, expires_in: 2.minutes, disposition: :inline, filename: ActiveStorage::Filename.new("test.txt"), content_type: "text/plain") response = Net::HTTP.get_response(URI(url)) - assert_equal "text/plain", response.header["Content-Type"] + assert_equal "text/plain", response.content_type ensure @service.delete key end diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index 60a19542e6..b912265754 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -334,26 +334,24 @@ with a `has_many` association: params.require(:book).permit(:title, chapters_attributes: [:title]) ``` -#### Outside the Scope of Strong Parameters - -The strong parameter API was designed with the most common use cases -in mind. It is not meant as a silver bullet to handle all of your -whitelisting problems. However, you can easily mix the API with your -own code to adapt to your situation. - Imagine a scenario where you have parameters representing a product name and a hash of arbitrary data associated with that product, and you want to whitelist the product name attribute and also the whole -data hash. The strong parameters API doesn't let you directly -whitelist the whole of a nested hash with any keys, but you can use -the keys of your nested hash to declare what to whitelist: +data hash: ```ruby def product_params - params.require(:product).permit(:name, data: params[:product][:data].try(:keys)) + params.require(:product).permit(:name, data: {}) end ``` +#### Outside the Scope of Strong Parameters + +The strong parameter API was designed with the most common use cases +in mind. It is not meant as a silver bullet to handle all of your +whitelisting problems. However, you can easily mix the API with your +own code to adapt to your situation. + Session ------- diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 7136205b75..6b634028c4 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -667,6 +667,12 @@ There are a number of settings available on `config.action_mailer`: config.action_mailer.interceptors = ["MailInterceptor"] ``` +* `config.action_mailer.preview_interceptors` registers interceptors which will be called before mail is previewed. + + ```ruby + config.action_mailer.preview_interceptors = ["MyPreviewMailInterceptor"] + ``` + * `config.action_mailer.preview_path` specifies the location of mailer previews. ```ruby @@ -805,13 +811,13 @@ text/javascript image/svg+xml application/postscript application/x-shockwave-fla * `config.active_storage.queue` can be used to set the name of the Active Job queue used to perform jobs like analyzing the content of a blob or purging a blog. ```ruby - config.active_job.queue = :low_priority + config.active_storage.queue = :low_priority ``` * `config.active_storage.logger` can be used to set the logger used by Active Storage. Accepts a logger conforming to the interface of Log4r or the default Ruby Logger class. ```ruby - config.active_job.logger = ActiveSupport::Logger.new(STDOUT) + config.active_storage.logger = ActiveSupport::Logger.new(STDOUT) ``` ### Configuring a Database diff --git a/railties/lib/rails/commands/server/server_command.rb b/railties/lib/rails/commands/server/server_command.rb index 5593840c71..cf17f0ef12 100644 --- a/railties/lib/rails/commands/server/server_command.rb +++ b/railties/lib/rails/commands/server/server_command.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "fileutils" -require "optparse" require "action_dispatch" require "rails" require "active_support/deprecation" diff --git a/railties/lib/rails/generators/rails/app/templates/ruby-version.tt b/railties/lib/rails/generators/rails/app/templates/ruby-version.tt index 19f0d7f202..bac1339923 100644 --- a/railties/lib/rails/generators/rails/app/templates/ruby-version.tt +++ b/railties/lib/rails/generators/rails/app/templates/ruby-version.tt @@ -1 +1 @@ -<%= "#{RUBY_ENGINE}-#{RUBY_ENGINE_VERSION}" -%> +<%= ENV["RBENV_VERSION"] || ENV["rvm_ruby_string"] || "#{RUBY_ENGINE}-#{RUBY_ENGINE_VERSION}" -%> diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 3cb7d66bbb..d8e9ae3369 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -891,7 +891,13 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_match(/ruby '#{RUBY_VERSION}'/, content) end assert_file ".ruby-version" do |content| - assert_match(/#{RUBY_ENGINE}-#{RUBY_ENGINE_VERSION}/, content) + if ENV["RBENV_VERSION"] + assert_match(/#{ENV["RBENV_VERSION"]}/, content) + elsif ENV["rvm_ruby_string"] + assert_match(/#{ENV["rvm_ruby_string"]}/, content) + else + assert_match(/#{RUBY_ENGINE}-#{RUBY_ENGINE_VERSION}/, content) + end end end |