mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Merge pull request #31513 from fatkodima/relation-touch_all
Add `touch_all` method to `ActiveRecord::Relation`
This commit is contained in:
commit
7bdfc63cc2
4 changed files with 101 additions and 11 deletions
|
@ -1,3 +1,11 @@
|
||||||
|
* Add `touch_all` method to `ActiveRecord::Relation`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
Person.where(name: "David").touch_all(time: Time.new(2020, 5, 16, 0, 0, 0))
|
||||||
|
|
||||||
|
*fatkodima*, *duggiefresh*
|
||||||
|
|
||||||
* Add `ActiveRecord::Base.base_class?` predicate.
|
* Add `ActiveRecord::Base.base_class?` predicate.
|
||||||
|
|
||||||
*Bogdan Gusiev*
|
*Bogdan Gusiev*
|
||||||
|
|
|
@ -369,6 +369,43 @@ module ActiveRecord
|
||||||
@klass.connection.update stmt, "#{@klass} Update All"
|
@klass.connection.update stmt, "#{@klass} Update All"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Touches all records in the current relation without instantiating records first with the updated_at/on attributes
|
||||||
|
# set to the current time or the time specified.
|
||||||
|
# This method can be passed attribute names and an optional time argument.
|
||||||
|
# If attribute names are passed, they are updated along with updated_at/on attributes.
|
||||||
|
# If no time argument is passed, the current time is used as default.
|
||||||
|
#
|
||||||
|
# === Examples
|
||||||
|
#
|
||||||
|
# # Touch all records
|
||||||
|
# Person.all.touch
|
||||||
|
# # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670'"
|
||||||
|
#
|
||||||
|
# # Touch multiple records with a custom attribute
|
||||||
|
# Person.all.touch(:created_at)
|
||||||
|
# # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670', \"created_at\" = '2018-01-04 22:55:23.132670'"
|
||||||
|
#
|
||||||
|
# # Touch multiple records with a specified time
|
||||||
|
# Person.all.touch(time: Time.new(2020, 5, 16, 0, 0, 0))
|
||||||
|
# # => "UPDATE \"people\" SET \"updated_at\" = '2020-05-16 00:00:00'"
|
||||||
|
#
|
||||||
|
# # Touch records with scope
|
||||||
|
# Person.where(name: 'David').touch
|
||||||
|
# # => "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 }
|
||||||
|
|
||||||
|
if klass.locking_enabled?
|
||||||
|
quoted_locking_column = connection.quote_column_name(klass.locking_column)
|
||||||
|
updates = sanitize_sql_for_assignment(updates) + ", #{quoted_locking_column} = COALESCE(#{quoted_locking_column}, 0) + 1"
|
||||||
|
end
|
||||||
|
|
||||||
|
update_all(updates)
|
||||||
|
end
|
||||||
|
|
||||||
# Destroys the records by instantiating each
|
# Destroys the records by instantiating each
|
||||||
# record and calling its {#destroy}[rdoc-ref:Persistence#destroy] method.
|
# record and calling its {#destroy}[rdoc-ref:Persistence#destroy] method.
|
||||||
# Each object's callbacks are executed (including <tt>:dependent</tt> association options).
|
# Each object's callbacks are executed (including <tt>:dependent</tt> association options).
|
||||||
|
|
|
@ -53,15 +53,19 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
module ClassMethods # :nodoc:
|
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
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def timestamp_attributes_for_create_in_model
|
def timestamp_attributes_for_create_in_model
|
||||||
timestamp_attributes_for_create.select { |c| column_names.include?(c) }
|
timestamp_attributes_for_create.select { |c| column_names.include?(c) }
|
||||||
end
|
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
|
def all_timestamp_attributes_in_model
|
||||||
timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model
|
timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model
|
||||||
end
|
end
|
||||||
|
@ -73,10 +77,6 @@ module ActiveRecord
|
||||||
def timestamp_attributes_for_update
|
def timestamp_attributes_for_update
|
||||||
["updated_at", "updated_on"]
|
["updated_at", "updated_on"]
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_time_from_proper_timezone
|
|
||||||
default_timezone == :utc ? Time.now.utc : Time.now
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -116,7 +116,7 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def timestamp_attributes_for_update_in_model
|
def timestamp_attributes_for_update_in_model
|
||||||
self.class.send(:timestamp_attributes_for_update_in_model)
|
self.class.timestamp_attributes_for_update_in_model
|
||||||
end
|
end
|
||||||
|
|
||||||
def all_timestamp_attributes_in_model
|
def all_timestamp_attributes_in_model
|
||||||
|
@ -124,7 +124,7 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_time_from_proper_timezone
|
def current_time_from_proper_timezone
|
||||||
self.class.send(:current_time_from_proper_timezone)
|
self.class.current_time_from_proper_timezone
|
||||||
end
|
end
|
||||||
|
|
||||||
def max_updated_column_timestamp(timestamp_names = timestamp_attributes_for_update_in_model)
|
def max_updated_column_timestamp(timestamp_names = timestamp_attributes_for_update_in_model)
|
||||||
|
|
|
@ -9,6 +9,7 @@ require "models/comment"
|
||||||
require "models/author"
|
require "models/author"
|
||||||
require "models/entrant"
|
require "models/entrant"
|
||||||
require "models/developer"
|
require "models/developer"
|
||||||
|
require "models/person"
|
||||||
require "models/computer"
|
require "models/computer"
|
||||||
require "models/reply"
|
require "models/reply"
|
||||||
require "models/company"
|
require "models/company"
|
||||||
|
@ -25,7 +26,7 @@ require "models/edge"
|
||||||
require "models/subscriber"
|
require "models/subscriber"
|
||||||
|
|
||||||
class RelationTest < ActiveRecord::TestCase
|
class RelationTest < ActiveRecord::TestCase
|
||||||
fixtures :authors, :author_addresses, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :categories_posts, :posts, :comments, :tags, :taggings, :cars, :minivans
|
fixtures :authors, :author_addresses, :topics, :entrants, :developers, :people, :companies, :developers_projects, :accounts, :categories, :categorizations, :categories_posts, :posts, :comments, :tags, :taggings, :cars, :minivans
|
||||||
|
|
||||||
class TopicWithCallbacks < ActiveRecord::Base
|
class TopicWithCallbacks < ActiveRecord::Base
|
||||||
self.table_name = :topics
|
self.table_name = :topics
|
||||||
|
@ -1525,6 +1526,50 @@ class RelationTest < ActiveRecord::TestCase
|
||||||
assert_equal posts(:welcome), comments(:greetings).post
|
assert_equal posts(:welcome), comments(:greetings).post
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_touch_all_updates_records_timestamps
|
||||||
|
david = developers(:david)
|
||||||
|
david_previously_updated_at = david.updated_at
|
||||||
|
jamis = developers(:jamis)
|
||||||
|
jamis_previously_updated_at = jamis.updated_at
|
||||||
|
Developer.where(name: "David").touch_all
|
||||||
|
|
||||||
|
assert_not_equal david_previously_updated_at, david.reload.updated_at
|
||||||
|
assert_equal jamis_previously_updated_at, jamis.reload.updated_at
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_touch_all_with_custom_timestamp
|
||||||
|
developer = developers(:david)
|
||||||
|
previously_created_at = developer.created_at
|
||||||
|
previously_updated_at = developer.updated_at
|
||||||
|
Developer.where(name: "David").touch_all(:created_at)
|
||||||
|
developer = developer.reload
|
||||||
|
|
||||||
|
assert_not_equal previously_created_at, developer.created_at
|
||||||
|
assert_not_equal previously_updated_at, developer.updated_at
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_touch_all_with_given_time
|
||||||
|
developer = developers(:david)
|
||||||
|
previously_created_at = developer.created_at
|
||||||
|
previously_updated_at = developer.updated_at
|
||||||
|
new_time = Time.utc(2015, 2, 16, 4, 54, 0)
|
||||||
|
Developer.where(name: "David").touch_all(:created_at, time: new_time)
|
||||||
|
developer = developer.reload
|
||||||
|
|
||||||
|
assert_not_equal previously_created_at, developer.created_at
|
||||||
|
assert_not_equal previously_updated_at, developer.updated_at
|
||||||
|
assert_equal new_time, developer.created_at
|
||||||
|
assert_equal new_time, developer.updated_at
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_touch_all_updates_locking_column
|
||||||
|
person = people(:david)
|
||||||
|
|
||||||
|
assert_difference -> { person.reload.lock_version }, +1 do
|
||||||
|
Person.where(first_name: "David").touch_all
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_update_on_relation
|
def test_update_on_relation
|
||||||
topic1 = TopicWithCallbacks.create! title: "arel", author_name: nil
|
topic1 = TopicWithCallbacks.create! title: "arel", author_name: nil
|
||||||
topic2 = TopicWithCallbacks.create! title: "activerecord", author_name: nil
|
topic2 = TopicWithCallbacks.create! title: "activerecord", author_name: nil
|
||||||
|
|
Loading…
Reference in a new issue