Skip to content

Commit 3d2e8cb

Browse files
committed
Merge pull request rails#12772 from dmathieu/no_touching
Add No Touching
2 parents c994e10 + b32ba36 commit 3d2e8cb

File tree

5 files changed

+112
-0
lines changed

5 files changed

+112
-0
lines changed

activerecord/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
* Added `ActiveRecord::Base.no_touching`, which allows ignoring touch on models.
2+
3+
Examples:
4+
5+
Post.no_touching do
6+
Post.first.touch
7+
end
8+
9+
*Sam Stephenson*, *Damien Mathieu*
10+
111
* Prevent the counter cache from being decremented twice when destroying
212
a record on a has_many :through association.
313

activerecord/lib/active_record.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ module ActiveRecord
4545
autoload :Migrator, 'active_record/migration'
4646
autoload :ModelSchema
4747
autoload :NestedAttributes
48+
autoload :NoTouching
4849
autoload :Persistence
4950
autoload :QueryCache
5051
autoload :Querying

activerecord/lib/active_record/base.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ class Base
295295
extend Delegation::DelegateCache
296296

297297
include Persistence
298+
include NoTouching
298299
include ReadonlyAttributes
299300
include ModelSchema
300301
include Inheritance
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
module ActiveRecord
2+
# = Active Record No Touching
3+
module NoTouching
4+
extend ActiveSupport::Concern
5+
6+
module ClassMethods
7+
# Lets you selectively disable calls to `touch` for the
8+
# duration of a block.
9+
#
10+
# ==== Examples
11+
# ActiveRecord::Base.no_touching do
12+
# Project.first.touch # does nothing
13+
# Message.first.touch # does nothing
14+
# end
15+
#
16+
# Project.no_touching do
17+
# Project.first.touch # does nothing
18+
# Message.first.touch # works, but does not touch the associated project
19+
# end
20+
#
21+
def no_touching(&block)
22+
NoTouching.apply_to(self, &block)
23+
end
24+
end
25+
26+
class << self
27+
def apply_to(klass) #:nodoc:
28+
klasses.push(klass)
29+
yield
30+
ensure
31+
klasses.pop
32+
end
33+
34+
def applied_to?(klass) #:nodoc:
35+
klasses.any? { |k| k >= klass }
36+
end
37+
38+
private
39+
def klasses
40+
Thread.current[:no_touching_classes] ||= []
41+
end
42+
end
43+
44+
def no_touching?
45+
NoTouching.applied_to?(self.class)
46+
end
47+
48+
def touch(*)
49+
super unless no_touching?
50+
end
51+
end
52+
end

activerecord/test/cases/timestamp_test.rb

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class TimestampTest < ActiveRecord::TestCase
1111

1212
def setup
1313
@developer = Developer.first
14+
@owner = Owner.first
1415
@developer.update_columns(updated_at: Time.now.prev_month)
1516
@previously_updated_at = @developer.updated_at
1617
end
@@ -92,6 +93,53 @@ def test_touching_a_record_without_timestamps_is_unexceptional
9293
assert_nothing_raised { Car.first.touch }
9394
end
9495

96+
def test_touching_a_no_touching_object
97+
Developer.no_touching do
98+
assert @developer.no_touching?
99+
assert !@owner.no_touching?
100+
@developer.touch
101+
end
102+
103+
assert !@developer.no_touching?
104+
assert !@owner.no_touching?
105+
assert_equal @previously_updated_at, @developer.updated_at
106+
end
107+
108+
def test_touching_related_objects
109+
@owner = Owner.first
110+
@previously_updated_at = @owner.updated_at
111+
112+
Owner.no_touching do
113+
@owner.pets.first.touch
114+
end
115+
116+
assert_equal @previously_updated_at, @owner.updated_at
117+
end
118+
119+
def test_global_no_touching
120+
ActiveRecord::Base.no_touching do
121+
assert @developer.no_touching?
122+
assert @owner.no_touching?
123+
@developer.touch
124+
end
125+
126+
assert !@developer.no_touching?
127+
assert !@owner.no_touching?
128+
assert_equal @previously_updated_at, @developer.updated_at
129+
end
130+
131+
def test_no_touching_threadsafe
132+
Thread.new do
133+
Developer.no_touching do
134+
assert @developer.no_touching?
135+
136+
sleep(1)
137+
end
138+
end
139+
140+
assert !@developer.no_touching?
141+
end
142+
95143
def test_saving_a_record_with_a_belongs_to_that_specifies_touching_the_parent_should_update_the_parent_updated_at
96144
pet = Pet.first
97145
owner = pet.owner

0 commit comments

Comments
 (0)