mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Merge pull request #19324 from arthurnn/batch_touch
Batch touch parent records
This commit is contained in:
commit
991875fc6f
7 changed files with 172 additions and 7 deletions
|
@ -49,6 +49,7 @@ module ActiveRecord
|
|||
autoload :ModelSchema
|
||||
autoload :NestedAttributes
|
||||
autoload :NoTouching
|
||||
autoload :TouchLater
|
||||
autoload :Persistence
|
||||
autoload :QueryCache
|
||||
autoload :Querying
|
||||
|
|
|
@ -60,7 +60,7 @@ module ActiveRecord::Associations::Builder
|
|||
klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
|
||||
end
|
||||
|
||||
def self.touch_record(o, foreign_key, name, touch) # :nodoc:
|
||||
def self.touch_record(o, foreign_key, name, touch, touch_method) # :nodoc:
|
||||
old_foreign_id = o.changed_attributes[foreign_key]
|
||||
|
||||
if old_foreign_id
|
||||
|
@ -75,9 +75,9 @@ module ActiveRecord::Associations::Builder
|
|||
|
||||
if old_record
|
||||
if touch != true
|
||||
old_record.touch touch
|
||||
old_record.send(touch_method, touch)
|
||||
else
|
||||
old_record.touch
|
||||
old_record.send(touch_method)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -85,9 +85,9 @@ module ActiveRecord::Associations::Builder
|
|||
record = o.send name
|
||||
if record && record.persisted?
|
||||
if touch != true
|
||||
record.touch touch
|
||||
record.send(touch_method, touch)
|
||||
else
|
||||
record.touch
|
||||
record.send(touch_method)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -98,7 +98,8 @@ module ActiveRecord::Associations::Builder
|
|||
touch = reflection.options[:touch]
|
||||
|
||||
callback = lambda { |record|
|
||||
BelongsTo.touch_record(record, foreign_key, n, touch)
|
||||
touch_method = touching_delayed_records? ? :touch : :touch_later
|
||||
BelongsTo.touch_record(record, foreign_key, n, touch, touch_method)
|
||||
}
|
||||
|
||||
model.after_save callback, if: :changed?
|
||||
|
|
|
@ -309,6 +309,7 @@ module ActiveRecord #:nodoc:
|
|||
include Aggregations
|
||||
include Transactions
|
||||
include NoTouching
|
||||
include TouchLater
|
||||
include Reflection
|
||||
include Serialization
|
||||
include Store
|
||||
|
|
|
@ -462,9 +462,10 @@ module ActiveRecord
|
|||
# ball = Ball.new
|
||||
# ball.touch(:updated_at) # => raises ActiveRecordError
|
||||
#
|
||||
def touch(*names, time: current_time_from_proper_timezone)
|
||||
def touch(*names, time: nil)
|
||||
raise ActiveRecordError, "cannot touch on a new record object" unless persisted?
|
||||
|
||||
time ||= current_time_from_proper_timezone
|
||||
attributes = timestamp_attributes_for_update_in_model
|
||||
attributes.concat(names)
|
||||
|
||||
|
|
50
activerecord/lib/active_record/touch_later.rb
Normal file
50
activerecord/lib/active_record/touch_later.rb
Normal file
|
@ -0,0 +1,50 @@
|
|||
module ActiveRecord
|
||||
# = Active Record Touch Later
|
||||
module TouchLater
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_commit_without_transaction_enrollment :touch_deferred_attributes
|
||||
end
|
||||
|
||||
def touch_later(*names) # :nodoc:
|
||||
raise ActiveRecordError, "cannot touch on a new record object" unless persisted?
|
||||
|
||||
@_defer_touch_attrs ||= timestamp_attributes_for_update_in_model
|
||||
@_defer_touch_attrs |= names
|
||||
@_touch_time = current_time_from_proper_timezone
|
||||
|
||||
surreptitiously_touch @_defer_touch_attrs
|
||||
self.class.connection.add_transaction_record self
|
||||
end
|
||||
|
||||
def touch(*names, time: nil) # :nodoc:
|
||||
if has_defer_touch_attrs?
|
||||
names |= @_defer_touch_attrs
|
||||
end
|
||||
super(*names, time: time)
|
||||
end
|
||||
|
||||
private
|
||||
def surreptitiously_touch(attrs)
|
||||
attrs.each { |attr| write_attribute attr, @_touch_time }
|
||||
clear_attribute_changes attrs
|
||||
end
|
||||
|
||||
def touch_deferred_attributes
|
||||
if has_defer_touch_attrs? && persisted?
|
||||
@_touching_delayed_records = true
|
||||
touch(*@_defer_touch_attrs, time: @_touch_time)
|
||||
@_touching_delayed_records, @_defer_touch_attrs, @_touch_time = nil, nil, nil
|
||||
end
|
||||
end
|
||||
|
||||
def has_defer_touch_attrs?
|
||||
defined?(@_defer_touch_attrs) && @_defer_touch_attrs.present?
|
||||
end
|
||||
|
||||
def touching_delayed_records?
|
||||
defined?(@_touching_delayed_records) && @_touching_delayed_records
|
||||
end
|
||||
end
|
||||
end
|
|
@ -422,6 +422,24 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
|
|||
assert_queries(1) { line_item.touch }
|
||||
end
|
||||
|
||||
def test_belongs_to_with_touch_on_multiple_records
|
||||
line_item = LineItem.create!(amount: 1)
|
||||
line_item2 = LineItem.create!(amount: 2)
|
||||
Invoice.create!(line_items: [line_item, line_item2])
|
||||
|
||||
assert_queries(1) do
|
||||
LineItem.transaction do
|
||||
line_item.touch
|
||||
line_item2.touch
|
||||
end
|
||||
end
|
||||
|
||||
assert_queries(2) do
|
||||
line_item.touch
|
||||
line_item2.touch
|
||||
end
|
||||
end
|
||||
|
||||
def test_belongs_to_with_touch_option_on_touch_without_updated_at_attributes
|
||||
assert_not LineItem.column_names.include?("updated_at")
|
||||
|
||||
|
|
93
activerecord/test/cases/touch_later_test.rb
Normal file
93
activerecord/test/cases/touch_later_test.rb
Normal file
|
@ -0,0 +1,93 @@
|
|||
require 'cases/helper'
|
||||
require 'models/invoice'
|
||||
require 'models/line_item'
|
||||
require 'models/topic'
|
||||
|
||||
class TouchLaterTest < ActiveRecord::TestCase
|
||||
|
||||
def test_touch_laster_raise_if_non_persisted
|
||||
invoice = Invoice.new
|
||||
Invoice.transaction do
|
||||
refute invoice.persisted?
|
||||
assert_raises(ActiveRecord::ActiveRecordError) do
|
||||
invoice.touch_later
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_touch_later_dont_set_dirty_attributes
|
||||
invoice = Invoice.create!
|
||||
invoice.touch_later
|
||||
refute invoice.changed?
|
||||
end
|
||||
|
||||
def test_touch_later_update_the_attributes
|
||||
time = Time.now.utc - 25.days
|
||||
topic = Topic.create!(updated_at: time, created_at: time)
|
||||
assert_equal time.to_i, topic.updated_at.to_i
|
||||
assert_equal time.to_i, topic.created_at.to_i
|
||||
|
||||
Topic.transaction do
|
||||
topic.touch_later(:created_at)
|
||||
assert_not_equal time.to_i, topic.updated_at.to_i
|
||||
assert_not_equal time.to_i, topic.created_at.to_i
|
||||
|
||||
assert_equal time.to_i, topic.reload.updated_at.to_i
|
||||
assert_equal time.to_i, topic.reload.created_at.to_i
|
||||
end
|
||||
assert_not_equal time.to_i, topic.reload.updated_at.to_i
|
||||
assert_not_equal time.to_i, topic.reload.created_at.to_i
|
||||
end
|
||||
|
||||
def test_touch_touches_immediately
|
||||
time = Time.now.utc - 25.days
|
||||
topic = Topic.create!(updated_at: time, created_at: time)
|
||||
assert_equal time.to_i, topic.updated_at.to_i
|
||||
assert_equal time.to_i, topic.created_at.to_i
|
||||
|
||||
Topic.transaction do
|
||||
topic.touch_later(:created_at)
|
||||
topic.touch
|
||||
|
||||
assert_not_equal time, topic.reload.updated_at
|
||||
assert_not_equal time, topic.reload.created_at
|
||||
end
|
||||
end
|
||||
|
||||
def test_touch_later_an_association_dont_autosave_parent
|
||||
time = Time.now.utc - 25.days
|
||||
line_item = LineItem.create!(amount: 1)
|
||||
invoice = Invoice.create!(line_items: [line_item])
|
||||
invoice.touch(time: time)
|
||||
|
||||
Invoice.transaction do
|
||||
line_item.update(amount: 2)
|
||||
assert_equal time.to_i, invoice.reload.updated_at.to_i
|
||||
end
|
||||
|
||||
assert_not_equal time.to_i, invoice.updated_at.to_i
|
||||
end
|
||||
|
||||
def test_touch_touches_immediately_with_a_custom_time
|
||||
time = Time.now.utc - 25.days
|
||||
topic = Topic.create!(updated_at: time, created_at: time)
|
||||
assert_equal time, topic.updated_at
|
||||
assert_equal time, topic.created_at
|
||||
|
||||
Topic.transaction do
|
||||
topic.touch_later(:created_at)
|
||||
time = Time.now.utc - 2.days
|
||||
topic.touch(time: time)
|
||||
|
||||
assert_equal time.to_i, topic.reload.updated_at.to_i
|
||||
assert_equal time.to_i, topic.reload.created_at.to_i
|
||||
end
|
||||
end
|
||||
|
||||
def test_touch_later_dont_hit_the_db
|
||||
invoice = Invoice.create!
|
||||
assert_queries(0) do
|
||||
invoice.touch_later
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue