graph_mediator 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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