1
0
Fork 0
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:
Arthur Nogueira Neves 2015-04-08 13:31:55 -04:00
commit 991875fc6f
7 changed files with 172 additions and 7 deletions

View file

@ -49,6 +49,7 @@ module ActiveRecord
autoload :ModelSchema
autoload :NestedAttributes
autoload :NoTouching
autoload :TouchLater
autoload :Persistence
autoload :QueryCache
autoload :Querying

View file

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

View file

@ -309,6 +309,7 @@ module ActiveRecord #:nodoc:
include Aggregations
include Transactions
include NoTouching
include TouchLater
include Reflection
include Serialization
include Store

View file

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

View 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

View file

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

View 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