graph_mediator 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +4 -0
- data/.gitignore +26 -0
- data/LICENSE +20 -0
- data/README.rdoc +136 -0
- data/Rakefile +32 -0
- data/graph_mediator.gemspec +31 -0
- data/lib/graph_mediator.rb +509 -0
- data/lib/graph_mediator/locking.rb +50 -0
- data/lib/graph_mediator/mediator.rb +260 -0
- data/lib/graph_mediator/version.rb +3 -0
- data/spec/database.rb +12 -0
- data/spec/examples/course_example_spec.rb +91 -0
- data/spec/examples/dingo_pen_example_spec.rb +288 -0
- data/spec/graph_mediator_spec.rb +500 -0
- data/spec/integration/changes_spec.rb +159 -0
- data/spec/integration/locking_tests_spec.rb +214 -0
- data/spec/integration/nesting_spec.rb +113 -0
- data/spec/integration/threads_spec.rb +59 -0
- data/spec/integration/validation_spec.rb +19 -0
- data/spec/investigation/alias_method_chain_spec.rb +170 -0
- data/spec/investigation/insert_subclass_spec.rb +122 -0
- data/spec/investigation/insert_superclass_spec.rb +131 -0
- data/spec/investigation/module_super_spec.rb +88 -0
- data/spec/investigation/self_decorating.rb +55 -0
- data/spec/mediator_spec.rb +201 -0
- data/spec/reservations/lodging.rb +4 -0
- data/spec/reservations/party.rb +4 -0
- data/spec/reservations/party_lodging.rb +4 -0
- data/spec/reservations/reservation.rb +18 -0
- data/spec/reservations/schema.rb +33 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +65 -0
- metadata +173 -0
@@ -0,0 +1,500 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
|
3
|
+
module GraphMediatorSpec # name space so these helper classes don't collide with another running spec
|
4
|
+
|
5
|
+
class Foo < ActiveRecord::Base
|
6
|
+
include GraphMediator
|
7
|
+
end
|
8
|
+
|
9
|
+
class UntimestampedThing < ActiveRecord::Base
|
10
|
+
include GraphMediator
|
11
|
+
end
|
12
|
+
|
13
|
+
class UnlockedThing < ActiveRecord::Base
|
14
|
+
include GraphMediator
|
15
|
+
end
|
16
|
+
|
17
|
+
class PlainThing < ActiveRecord::Base
|
18
|
+
include GraphMediator
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "GraphMediator" do
|
22
|
+
|
23
|
+
before(:all) do
|
24
|
+
create_schema do |conn|
|
25
|
+
conn.create_table(:foos, :force => true) do |t|
|
26
|
+
t.string :foo
|
27
|
+
t.integer :lock_version, :default => 0
|
28
|
+
t.timestamps
|
29
|
+
end
|
30
|
+
|
31
|
+
conn.create_table(:bars, :force => true) do |t|
|
32
|
+
t.string :bar
|
33
|
+
t.integer :lock_version, :default => 0
|
34
|
+
t.timestamps
|
35
|
+
end
|
36
|
+
|
37
|
+
conn.create_table(:untimestamped_things, :force => true) do |t|
|
38
|
+
t.string :name
|
39
|
+
t.integer :lock_version, :default => 0
|
40
|
+
end
|
41
|
+
|
42
|
+
conn.create_table(:unlocked_things, :force => true) do |t|
|
43
|
+
t.string :name
|
44
|
+
t.timestamps
|
45
|
+
end
|
46
|
+
|
47
|
+
conn.create_table(:plain_things, :force => true) do |t|
|
48
|
+
t.string :name
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should provide a module attribute accessor for turning mediation on or off" do
|
54
|
+
GraphMediator::Configuration.enable_mediation.should == true
|
55
|
+
GraphMediator::Configuration.enable_mediation = false
|
56
|
+
GraphMediator::Configuration.enable_mediation.should == false
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should be able to disable and enable mediation globally"
|
60
|
+
|
61
|
+
it "should insert a MediatorProxy class when included" do
|
62
|
+
Foo::MediatorProxy.should include(GraphMediator::Proxy)
|
63
|
+
Foo.should include(Foo::MediatorProxy)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should provide the mediate class macro" do
|
67
|
+
Foo.should respond_to(:mediate)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should provide the mediate_reconciles class macro" do
|
71
|
+
Foo.should respond_to(:mediate_reconciles)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should provide the mediate_caches class macro" do
|
75
|
+
Foo.should respond_to(:mediate_caches)
|
76
|
+
end
|
77
|
+
|
78
|
+
context "testing logger" do
|
79
|
+
class LoggerTest < ActiveRecord::Base
|
80
|
+
include GraphMediator
|
81
|
+
def log_me
|
82
|
+
logger.debug('hi')
|
83
|
+
end
|
84
|
+
def log_me_graph_mediator
|
85
|
+
m_info('gm')
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
before(:all) do
|
90
|
+
create_schema do |conn|
|
91
|
+
conn.create_table(:logger_tests, :force => true) do |t|
|
92
|
+
t.string :name
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
before(:each) do
|
98
|
+
@lt = LoggerTest.create!
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should have the base logger" do
|
102
|
+
LoggerTest.logger.should_not be_nil
|
103
|
+
LoggerTest.logger.should == ActiveRecord::Base.logger
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should have a MediatorProxy logger" do
|
107
|
+
LoggerTest::MediatorProxy._graph_mediator_logger.should_not be_nil
|
108
|
+
LoggerTest::MediatorProxy._graph_mediator_logger.should == LoggerTest.logger
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should have the base logger for instances" do
|
112
|
+
@lt.logger.should_not be_nil
|
113
|
+
@lt.logger.should == ActiveRecord::Base.logger
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should have the mediator logger for instances" do
|
117
|
+
@lt.logger.should_not be_nil
|
118
|
+
@lt.logger.should == ActiveRecord::Base.logger
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should be possible to override base logger" do
|
122
|
+
begin
|
123
|
+
mock_logger = mock("logger")
|
124
|
+
mock_logger.should_receive(:debug).once.with('hi')
|
125
|
+
current_logger = ActiveRecord::Base.logger
|
126
|
+
LoggerTest.logger = mock_logger
|
127
|
+
@lt.log_me
|
128
|
+
ensure
|
129
|
+
LoggerTest.logger = current_logger
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should be possible to override graph logger" do
|
134
|
+
mock_logger = mock("logger")
|
135
|
+
mock_logger.should_receive(:info).once.with('gm')
|
136
|
+
LoggerTest::MediatorProxy._graph_mediator_logger = mock_logger
|
137
|
+
@lt.log_me_graph_mediator
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
context "with a fresh class" do
|
142
|
+
|
143
|
+
def load_bar
|
144
|
+
c = Class.new(ActiveRecord::Base)
|
145
|
+
Object.const_set(:Bar, c)
|
146
|
+
c.__send__(:include, GraphMediator)
|
147
|
+
end
|
148
|
+
|
149
|
+
before(:each) do
|
150
|
+
load_bar
|
151
|
+
end
|
152
|
+
|
153
|
+
after(:each) do
|
154
|
+
Object.__send__(:remove_const, :Bar)
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should get the when_reconciling option" do
|
158
|
+
# Bar.__graph_mediator_reconciliation_callbacks.should == []
|
159
|
+
Bar.mediate :when_reconciling => :foo
|
160
|
+
Bar.mediate_reconciles_callback_chain.should == [:foo]
|
161
|
+
# Bar.__graph_mediator_reconciliation_callbacks.size.should == 1
|
162
|
+
# Bar.__graph_mediator_reconciliation_callbacks.first.should be_kind_of(Proc)
|
163
|
+
end
|
164
|
+
|
165
|
+
it "should collect methods through mediate_reconciles" do
|
166
|
+
# Bar.__graph_mediator_reconciliation_callbacks.should == []
|
167
|
+
Bar.mediate :when_reconciling => [:foo, :bar]
|
168
|
+
Bar.mediate_reconciles :baz do
|
169
|
+
biscuit
|
170
|
+
end
|
171
|
+
Bar.mediate_reconciles_callback_chain.should include(:foo, :bar, :baz)
|
172
|
+
Bar.mediate_reconciles_callback_chain.should have(4).elements
|
173
|
+
# Bar.__graph_mediator_reconciliation_callbacks.should have3
|
174
|
+
# Bar.__graph_mediator_reconciliation_callbacks.each { |e| e.should be_kind_of(Proc) }
|
175
|
+
end
|
176
|
+
|
177
|
+
it "should get the when_cacheing option" do
|
178
|
+
Bar.mediate :when_cacheing => :foo
|
179
|
+
Bar.mediate_caches_callback_chain.should == [:foo]
|
180
|
+
end
|
181
|
+
|
182
|
+
it "should collect methods through mediate_caches" do
|
183
|
+
Bar.mediate :when_cacheing => [:foo, :bar]
|
184
|
+
Bar.mediate_caches :baz do
|
185
|
+
biscuit
|
186
|
+
end
|
187
|
+
Bar.mediate_caches_callback_chain.should include(:foo, :bar, :baz)
|
188
|
+
Bar.mediate_caches_callback_chain.should have(4).elements
|
189
|
+
end
|
190
|
+
|
191
|
+
it "should get the dependencies option" do
|
192
|
+
begin
|
193
|
+
class ::Child < ActiveRecord::Base; end
|
194
|
+
Bar.mediate :dependencies => Child
|
195
|
+
ensure
|
196
|
+
Object.__send__(:remove_const, :Child)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
|
202
|
+
context "with a defined mediation" do
|
203
|
+
|
204
|
+
before(:each) do
|
205
|
+
load_traceable_callback_tester
|
206
|
+
@t = Traceable.new(:name => :gizmo)
|
207
|
+
end
|
208
|
+
|
209
|
+
after(:each) do
|
210
|
+
Object.__send__(:remove_const, :Traceable)
|
211
|
+
end
|
212
|
+
|
213
|
+
it "should be able to disable and enable mediation for the whole class" do
|
214
|
+
Traceable.disable_all_mediation!
|
215
|
+
@t.save
|
216
|
+
@t.save!
|
217
|
+
@traceables_callbacks.should == []
|
218
|
+
Traceable.enable_all_mediation!
|
219
|
+
@t.save
|
220
|
+
@t.save!
|
221
|
+
@traceables_callbacks.should == [:before, :reconcile, :cache, :before, :reconcile, :cache,]
|
222
|
+
end
|
223
|
+
|
224
|
+
it "should disable and enable mediation for an instance" do
|
225
|
+
@t.disable_mediation!
|
226
|
+
@t.save
|
227
|
+
@t.save!
|
228
|
+
@traceables_callbacks.should == []
|
229
|
+
@t.enable_mediation!
|
230
|
+
@t.save
|
231
|
+
@t.save!
|
232
|
+
@traceables_callbacks.should == [:before, :reconcile, :cache, :before, :reconcile, :cache,]
|
233
|
+
end
|
234
|
+
|
235
|
+
it "should have save_without_mediation convenience methods" do
|
236
|
+
@t.save_without_mediation
|
237
|
+
@t.save_without_mediation!
|
238
|
+
@traceables_callbacks.should == []
|
239
|
+
end
|
240
|
+
|
241
|
+
it "should have update_attributes_without_mediation convenience methods" do
|
242
|
+
@t.update_attributes_without_mediation(:name => :foo)
|
243
|
+
@t.update_attributes_without_mediation!(:name => :bar)
|
244
|
+
@traceables_callbacks.should == []
|
245
|
+
end
|
246
|
+
|
247
|
+
it "should handle saving a new record" do
|
248
|
+
n = Traceable.new(:name => 'new')
|
249
|
+
n.save!
|
250
|
+
@traceables_callbacks.should == [:before, :reconcile, :cache,]
|
251
|
+
end
|
252
|
+
|
253
|
+
it "should handle updating an existing record" do
|
254
|
+
e = Traceable.create!(:name => 'exists')
|
255
|
+
@traceables_callbacks.clear
|
256
|
+
e.save!
|
257
|
+
@traceables_callbacks.should == [:before, :reconcile, :cache,]
|
258
|
+
end
|
259
|
+
|
260
|
+
it "should nest mediated transactions" do
|
261
|
+
Traceable.class_eval do
|
262
|
+
after_create do |instance|
|
263
|
+
instance.mediated_transaction do
|
264
|
+
instance.callbacks << :nested_create!
|
265
|
+
end
|
266
|
+
end
|
267
|
+
after_save do |instance|
|
268
|
+
instance.mediated_transaction do
|
269
|
+
instance.callbacks << :nested_save!
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
nested = Traceable.create!(:name => :nested!)
|
274
|
+
@traceables_callbacks.should == [:before, :nested_create!, :nested_save!, :reconcile, :cache, :nested_save!]
|
275
|
+
# The final nested save is the touch and lock_version bump
|
276
|
+
end
|
277
|
+
|
278
|
+
# can't nest before_create. The second mediated_transaction will occur
|
279
|
+
# before instance has an id, so we have no way to look up a mediator.
|
280
|
+
# XXX actually, it appears you can?
|
281
|
+
it "cannot nest mediated transactions before_create if versioning" do
|
282
|
+
Traceable.class_eval do
|
283
|
+
before_create do |instance|
|
284
|
+
instance.mediated_transaction do
|
285
|
+
instance.callbacks << :nested_before_create!
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
#lambda { nested = Traceable.create!(:name => :nested!) }.should raise_error(GraphMediator::MediatorException)
|
290
|
+
nested = Traceable.create!(:name => :nested!)
|
291
|
+
@traceables_callbacks.should == [:before, :nested_before_create!, :reconcile, :cache]
|
292
|
+
end
|
293
|
+
|
294
|
+
it "should cull mediator after an exception in mediation" do
|
295
|
+
lambda { @t.mediated_transaction do
|
296
|
+
raise
|
297
|
+
end }.should raise_error(RuntimeError)
|
298
|
+
@t.__send__(:current_mediator).should be_nil
|
299
|
+
end
|
300
|
+
|
301
|
+
it "should override save" do
|
302
|
+
@t.save
|
303
|
+
@traceables_callbacks.should == [:before, :reconcile, :cache,]
|
304
|
+
@t.new_record?.should be_false
|
305
|
+
end
|
306
|
+
|
307
|
+
it "should override save bang" do
|
308
|
+
@t.save!
|
309
|
+
@traceables_callbacks.should == [:before, :reconcile, :cache,]
|
310
|
+
@t.new_record?.should be_false
|
311
|
+
end
|
312
|
+
|
313
|
+
it "should allow me to override save locally" do
|
314
|
+
Traceable.class_eval do
|
315
|
+
def save
|
316
|
+
callbacks << '...saving...'
|
317
|
+
super
|
318
|
+
end
|
319
|
+
end
|
320
|
+
@t.save
|
321
|
+
@traceables_callbacks.should == ['...saving...', :before, :reconcile, :cache,]
|
322
|
+
@t.new_record?.should be_false
|
323
|
+
end
|
324
|
+
|
325
|
+
it "should allow me to decorate save_with_mediation" do
|
326
|
+
Traceable.class_eval do
|
327
|
+
alias_method :save_without_transactions_with_mediation_without_logging, :save_without_transactions_with_mediation
|
328
|
+
def save_without_transactions_with_mediation(*args)
|
329
|
+
callbacks << '...saving...'
|
330
|
+
save_without_transactions_with_mediation_without_logging(*args)
|
331
|
+
end
|
332
|
+
end
|
333
|
+
@t.save
|
334
|
+
@traceables_callbacks.should == ['...saving...', :before, :reconcile, :cache,]
|
335
|
+
@t.new_record?.should be_false
|
336
|
+
end
|
337
|
+
|
338
|
+
it "should allow me to decorate save_without_mediation" do
|
339
|
+
Traceable.class_eval do
|
340
|
+
alias_method :save_without_transactions_without_mediation_without_logging, :save_without_transactions_without_mediation
|
341
|
+
def save_without_transactions_without_mediation(*args)
|
342
|
+
callbacks << '...saving...'
|
343
|
+
save_without_transactions_without_mediation_without_logging(*args)
|
344
|
+
end
|
345
|
+
end
|
346
|
+
@t.save
|
347
|
+
@traceables_callbacks.should == [:before, '...saving...', :reconcile, :cache,]
|
348
|
+
@t.new_record?.should be_false
|
349
|
+
end
|
350
|
+
|
351
|
+
end
|
352
|
+
|
353
|
+
context "with an instance" do
|
354
|
+
|
355
|
+
before(:each) do
|
356
|
+
@f = Foo.new
|
357
|
+
end
|
358
|
+
|
359
|
+
it "cannot update lock_version without timestamps" do
|
360
|
+
t = UntimestampedThing.create!(:name => 'one')
|
361
|
+
t.lock_version.should == 0
|
362
|
+
t.touch
|
363
|
+
t.lock_version.should == 0
|
364
|
+
t.mediated_transaction {}
|
365
|
+
t.lock_version.should == 0
|
366
|
+
end
|
367
|
+
|
368
|
+
it "should update lock_version on touch if instance has timestamps" do
|
369
|
+
@f.save!
|
370
|
+
@f.lock_version.should == 1
|
371
|
+
@f.touch
|
372
|
+
@f.lock_version.should == 2
|
373
|
+
end
|
374
|
+
|
375
|
+
context "with mediation disabled" do
|
376
|
+
before(:each) do
|
377
|
+
Foo.disable_all_mediation!
|
378
|
+
end
|
379
|
+
|
380
|
+
after(:each) do
|
381
|
+
Foo.enable_all_mediation!
|
382
|
+
end
|
383
|
+
|
384
|
+
it "should update lock_version normally if mediation is off" do
|
385
|
+
@f.save!
|
386
|
+
@f.lock_version.should == 0
|
387
|
+
@f.update_attributes(:foo => 'foo')
|
388
|
+
@f.lock_version.should == 1
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
it "should get a mediator" do
|
393
|
+
begin
|
394
|
+
mediator = @f.__send__(:_get_mediator)
|
395
|
+
mediator.should be_kind_of(GraphMediator::Mediator)
|
396
|
+
mediator.mediated_instance.should == @f
|
397
|
+
ensure
|
398
|
+
@f.__send__(:mediators_for_new_records).clear
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
it "should get the same mediator for a new record if called from the same instance" do
|
403
|
+
begin
|
404
|
+
@f.new_record?.should be_true
|
405
|
+
mediator1 = @f.__send__(:_get_mediator)
|
406
|
+
mediator2 = @f.__send__(:_get_mediator)
|
407
|
+
mediator1.should equal(mediator2)
|
408
|
+
ensure
|
409
|
+
@f.__send__(:mediators_for_new_records).clear
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
it "should get the same mediator for a saved record" do
|
414
|
+
begin
|
415
|
+
@f.save_without_mediation
|
416
|
+
@f.new_record?.should be_false
|
417
|
+
mediator1 = @f.__send__(:_get_mediator)
|
418
|
+
mediator2 = @f.__send__(:_get_mediator)
|
419
|
+
mediator1.should equal(mediator2)
|
420
|
+
ensure
|
421
|
+
@f.__send__(:mediators).clear
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
# @f.create -> calls save, which engages mediation on a new record which has no id.
|
426
|
+
# During the creation process (after_create) @f will have an id.
|
427
|
+
# Other callbacks may create dependent objects, which will attempt to mediate, or
|
428
|
+
# other mediated methods, and these should receive the original mediator if we have
|
429
|
+
# reached after_create stage.
|
430
|
+
it "should get the same mediator for a new record that is saved during mediation" do
|
431
|
+
begin
|
432
|
+
@f.new_record?.should be_true
|
433
|
+
mediator1 = @f.__send__(:_get_mediator)
|
434
|
+
@f.mediated_transaction do
|
435
|
+
@f.save
|
436
|
+
mediator2 = @f.__send__(:_get_mediator)
|
437
|
+
mediator1.should equal(mediator2)
|
438
|
+
end
|
439
|
+
ensure
|
440
|
+
@f.__send__(:mediators_for_new_records).clear
|
441
|
+
@f.__send__(:mediators).clear
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
it "should indicate if currently mediating for a new instance" do
|
446
|
+
@f.currently_mediating?.should be_false
|
447
|
+
@f.mediated_transaction do
|
448
|
+
@f.currently_mediating?.should be_true
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
it "should indicate if currently mediating for an existing instance" do
|
453
|
+
@f.save!
|
454
|
+
@f.currently_mediating?.should be_false
|
455
|
+
@f.mediated_transaction do
|
456
|
+
@f.currently_mediating?.should be_true
|
457
|
+
end
|
458
|
+
end
|
459
|
+
|
460
|
+
it "should expose the current phase of mediation" do
|
461
|
+
@f.current_mediation_phase.should be_nil
|
462
|
+
@f.mediated_transaction do
|
463
|
+
@f.current_mediation_phase.should == :mediating
|
464
|
+
end
|
465
|
+
@f.save!
|
466
|
+
@f.current_mediation_phase.should be_nil
|
467
|
+
@f.mediated_transaction do
|
468
|
+
@f.current_mediation_phase.should == :mediating
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
472
|
+
it "should expose mediated_changes" do
|
473
|
+
@f.mediated_changes.should be_nil
|
474
|
+
@f.mediated_transaction do
|
475
|
+
@f.mediated_changes.should == {}
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
# TODO - may need to move this up to the class
|
480
|
+
|
481
|
+
it "should generate a unique mediator_hash_key for each MediatorProxy" do
|
482
|
+
@f.class.mediator_hash_key.should == 'GRAPH_MEDIATOR_GRAPH_MEDIATOR_SPEC_FOO_HASH_KEY'
|
483
|
+
end
|
484
|
+
|
485
|
+
it "should generate a unique mediator_new_array_key for each MediatorProxy" do
|
486
|
+
@f.class.mediator_new_array_key.should == 'GRAPH_MEDIATOR_GRAPH_MEDIATOR_SPEC_FOO_NEW_ARRAY_KEY'
|
487
|
+
end
|
488
|
+
|
489
|
+
it "should access an array of mediators for new records" do
|
490
|
+
@f.__send__(:mediators_for_new_records).should == []
|
491
|
+
end
|
492
|
+
|
493
|
+
it "should access a hash of mediators" do
|
494
|
+
@f.__send__(:mediators).should == {}
|
495
|
+
end
|
496
|
+
|
497
|
+
end
|
498
|
+
end
|
499
|
+
|
500
|
+
end
|