forked from godaddy/activerecord-delay_touching
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathdelay_touching_spec.rb
More file actions
260 lines (222 loc) · 7.44 KB
/
Copy pathdelay_touching_spec.rb
File metadata and controls
260 lines (222 loc) · 7.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
require 'spec_helper'
describe Activerecord::DelayTouching do
let(:person) { Person.create name: "Rosey" }
let(:pet1) { Pet.create(name: "Bones") }
let(:pet2) { Pet.create(name: "Ema") }
# Rails 7.2 deprecated ActiveRecord::Base.connection in favor of .lease_connection and .with_connection. By default,
# rspec should already have established an active connection around each test which gets reused by each downstream
# call to .lease_connection & .with_connection within ActiveRecord. However, Base.lease_connection doesn't make it
# clear that the same connection is reused, so we codify this expectation a little more explicitly by using this
# `let!`, which is eagerly evaluated for each test and gives us a value to use for assertions.
let!(:connection) do
expect(ActiveRecord::Base.connection_pool.active_connection?).to be_truthy, "rspec isn't connected to the db"
ActiveRecord::Base.connection_pool.active_connection
end
it 'has a version number' do
expect(Activerecord::DelayTouching::VERSION).not_to be nil
end
it 'touch returns true' do
ActiveRecord::Base.delay_touching do
expect(person.touch).to eq(true)
end
end
it 'consolidates touches on a single record' do
expect_updates ["people"] do
ActiveRecord::Base.delay_touching do
person.touch
person.touch
end
end
end
it 'sets updated_at on the in-memory instance when it eventually touches the record' do
original_time = new_time = nil
Timecop.freeze(2014, 7, 4, 12, 0, 0) do
original_time = Time.current
person.touch
end
Timecop.freeze(2014, 7, 10, 12, 0, 0) do
new_time = Time.current
ActiveRecord::Base.delay_touching do
person.touch
expect(person.updated_at).to eq(original_time)
expect(person.changed?).to be_falsey
end
end
expect(person.updated_at).to eq(new_time)
expect(person.changed?).to be_falsey
end
it 'does not mark the instance as changed when touch is called' do
ActiveRecord::Base.delay_touching do
person.touch
expect(person).not_to be_changed
end
end
it 'consolidates touches for all instances in a single table' do
expect_updates ["pets"] do
ActiveRecord::Base.delay_touching do
pet1.touch
pet2.touch
end
end
end
it 'does nothing if no_touching is on' do
if ActiveRecord::Base.respond_to?(:no_touching)
expect_updates [] do
ActiveRecord::Base.no_touching do
ActiveRecord::Base.delay_touching do
person.touch
end
end
end
end
end
it 'only applies touches for which no_touching is off' do
if Person.respond_to?(:no_touching)
expect_updates ["pets"] do
Person.no_touching do
ActiveRecord::Base.delay_touching do
person.touch
pet1.touch
end
end
end
end
end
it 'does not apply nested touches if no_touching was turned on inside delay_touching' do
if ActiveRecord::Base.respond_to?(:no_touching)
expect_updates [ "people" ] do
ActiveRecord::Base.delay_touching do
person.touch
ActiveRecord::Base.no_touching do
pet1.touch
end
end
end
end
end
it 'can update nonstandard columns' do
expect_updates [ "pets" => [ "updated_at", "neutered_at" ] ] do
ActiveRecord::Base.delay_touching do
pet1.touch :neutered_at
end
end
end
it 'splits up nonstandard column touches and standard column touches' do
expect_updates [ { "pets" => [ "updated_at", "neutered_at" ] }, { "pets" => [ "updated_at" ] } ] do
ActiveRecord::Base.delay_touching do
pet1.touch :neutered_at
pet2.touch
end
end
end
it 'can update multiple nonstandard columns of a single record in different calls to touch' do
expect_updates [ { "pets" => [ "updated_at", "neutered_at" ] }, { "pets" => [ "updated_at", "fed_at" ] } ] do
ActiveRecord::Base.delay_touching do
pet1.touch :neutered_at
pet1.touch :fed_at
end
end
end
it 'will not connect to the database if there are no touches' do
expect(ActiveRecord::Base).not_to receive(:lease_connection)
expect(ActiveRecord::Base).not_to receive(:with_connection)
expect_updates [] do
ActiveRecord::Base.delay_touching do
# no touches, so no reason to fetch a connection
end
end
end
it 'will not connect to the database if none of the touches are persisted' do
expect(ActiveRecord::Base).not_to receive(:lease_connection)
expect(ActiveRecord::Base).not_to receive(:with_connection)
expect_updates [] do
ActiveRecord::Base.delay_touching do
Pet.new.touch # not persisted, so won't cause updates
end
end
end
it 'still connects to the database for other queries, even if there are no touches' do
expect(ActiveRecord::Base).to receive(:with_connection).at_least(:once).and_call_original
expect_updates [] do
ActiveRecord::Base.delay_touching do
expect(Pet.count).to be >= 0 # query should still work
end
end
end
context 'touch: true' do
before do
person.pets << pet1
person.pets << pet2
end
it 'consolidates touch: true touches' do
expect_updates [ "pets", "people" ] do
ActiveRecord::Base.delay_touching do
pet1.touch
pet2.touch
end
end
end
it 'does not touch the owning record via touch: true if it was already touched explicitly' do
expect_updates [ "pets", "people" ] do
ActiveRecord::Base.delay_touching do
person.touch
pet1.touch
pet2.touch
end
end
end
end
context 'dependent deletes' do
let(:post) { Post.create! }
let(:user) { User.create! }
let(:comment) { Comment.create! }
before do
post.comments << comment
user.comments << comment
end
it 'does not attempt to touch deleted records' do
expect do
ActiveRecord::Base.delay_touching do
post.destroy
end
end.not_to raise_error
expect(post.destroyed?).to eq true
end
end
context 'persistence fails and rolls back transaction' do
it 'does not infinitely loop' do
updates = 0
allow(connection).to receive(:update).and_wrap_original do |m, *args|
updates = updates + 1
raise StandardError, 'Too many updates - likely infinite loop detected' if updates > 1
m.call(*args)
end
ActiveRecord::Base.delay_touching do
ActiveRecord::Base.transaction do
# write and touch any new record
record = Post.create!
record.touch
raise ActiveRecord::Rollback
end
end
end
end
def expect_updates(tables)
expected_sql = tables.map do |entry|
if entry.kind_of?(Hash)
entry.map do |table, columns|
Regexp.new(%Q{UPDATE "#{table}" SET #{columns.map { |column| %Q{"#{column}" =.+} }.join(", ") } })
end
else
Regexp.new(%Q{UPDATE "#{entry}" SET "updated_at" = })
end
end.flatten
expect(connection).to receive(:update).exactly(expected_sql.length).times do |stmt, _, _|
index = expected_sql.index { |sql| stmt.to_sql =~ sql}
expect(index).to be, "An unexpected touch occurred: #{stmt.to_sql}"
expected_sql.delete_at(index)
end
yield
expect(expected_sql).to be_empty, "Some of the expected updates were not executed."
end
end