1
0
Fork 0
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:
Rafael França 2018-04-20 16:45:38 -04:00 committed by GitHub
commit 7bdfc63cc2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 101 additions and 11 deletions

View file

@ -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*

View file

@ -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).

View file

@ -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)

View file

@ -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