graph_mediator 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +14 -13
- data/lib/graph_mediator/locking.rb +7 -6
- data/lib/graph_mediator/mediator.rb +6 -5
- data/lib/graph_mediator/version.rb +1 -1
- data/lib/graph_mediator.rb +61 -51
- data/spec/examples/dingo_pen_example_spec.rb +1 -1
- data/spec/integration/transactions_spec.rb +39 -0
- metadata +5 -3
data/README.rdoc
CHANGED
@@ -31,14 +31,14 @@ The after_mediation callback is itself broken down into three phases:
|
|
31
31
|
but which do not themselves alter the graph (in that they are reproducible
|
32
32
|
from existing state) should be made in the cacheing phase.
|
33
33
|
* :bumping - if the class has a +lock_column+ set
|
34
|
-
(ActiveRecord::Locking::Optimistic) and has on
|
34
|
+
(ActiveRecord::Locking::Optimistic) and has on <b>updated_at/on</b> timestamp then
|
35
35
|
the instance will be touched, bumping the +lock_column+ and checking for stale
|
36
36
|
data.
|
37
37
|
|
38
38
|
During a mediated_transaction, the +lock_column+ will only update during the
|
39
39
|
+bumping+ phase of the after_mediation callback.
|
40
40
|
|
41
|
-
But if there is no
|
41
|
+
But if there is no <b>update_at/on</b> timestamp, then +lock_column+ cannot be
|
42
42
|
incremented when dependent objects are updated. This is because there is
|
43
43
|
nothing to touch on the root record to trigger the +lock_column+ update.
|
44
44
|
|
@@ -90,20 +90,21 @@ See spec/examples for real, dingo-free examples.
|
|
90
90
|
A lock_column and timestamp are not required, but without both columns in your schema
|
91
91
|
there will be no versioning.
|
92
92
|
|
93
|
-
|
94
|
-
any optimistic locking in a class including GraphMediator
|
93
|
+
<em>A lock_column by itself *without* a timestamp will not increment and will not provide
|
94
|
+
any optimistic locking in a class including GraphMediator!</em>
|
95
95
|
|
96
|
-
Using a lock_column along with a counter_cache in a dependent child will raise
|
97
|
-
error during a mediated_transaction if you touch the dependent.
|
96
|
+
Using a lock_column along with a counter_cache in a dependent child will raise
|
97
|
+
a StaleObject error during a mediated_transaction if you touch the dependent.
|
98
98
|
|
99
|
-
The cache_counters do not play well with optimistic locking because they are
|
100
|
-
a direct SQL call to the database, so ActiveRecord instance remain
|
101
|
-
change and assume it came from another transaction.
|
99
|
+
The cache_counters do not play well with optimistic locking because they are
|
100
|
+
updated with a direct SQL call to the database, so ActiveRecord instance remain
|
101
|
+
unaware of the lock_version change and assume it came from another transaction.
|
102
102
|
|
103
|
-
You should not need to declare lock_version for any children that are declared
|
104
|
-
of the root node, since updates will also update the root nodes
|
105
|
-
transaction updates a child, root.lock_version
|
106
|
-
should
|
103
|
+
You should not need to declare lock_version for any children that are declared
|
104
|
+
as a dependency of the root node, since updates will also update the root nodes
|
105
|
+
lock_version. So if another transaction updates a child, root.lock_version
|
106
|
+
should increment, and the first transaction should raise a StaleObject error
|
107
|
+
when it too tries to update the child.
|
107
108
|
|
108
109
|
If you override super in the model hierarchy you are mediating, you must pass your
|
109
110
|
override as a block to super or it will occur outside of mediation:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module GraphMediator
|
2
|
-
# Overrides to ActiveRecord::Optimistic::Locking to ensure that lock_column
|
3
|
-
# during the +versioning+ phase of a mediated transaction.
|
2
|
+
# Overrides to ActiveRecord::Optimistic::Locking to ensure that lock_column
|
3
|
+
# is updated during the +versioning+ phase of a mediated transaction.
|
4
4
|
module Locking
|
5
5
|
|
6
6
|
def self.included(base)
|
@@ -10,8 +10,8 @@ module GraphMediator
|
|
10
10
|
end
|
11
11
|
|
12
12
|
module ClassMethods
|
13
|
-
# Overrides ActiveRecord::Base.update_counters to skip locking if
|
14
|
-
# the passed id.
|
13
|
+
# Overrides ActiveRecord::Base.update_counters to skip locking if
|
14
|
+
# currently mediating the passed id.
|
15
15
|
def update_counters(ids, counters)
|
16
16
|
# id may be an array of ids...
|
17
17
|
unless currently_mediating?(ids)
|
@@ -32,9 +32,10 @@ module GraphMediator
|
|
32
32
|
# Overrides ActiveRecord::Locking::Optimistic#locking_enabled?
|
33
33
|
#
|
34
34
|
# * True if we are not in a mediated_transaction and lock_enabled? is true
|
35
|
-
#
|
35
|
+
# per ActiveRecord (lock_column exists and lock_optimistically? true)
|
36
36
|
# * True if we are in a mediated_transaction and lock_enabled? is true per
|
37
|
-
#
|
37
|
+
# ActiveRecord and we are in the midst of the version bumping phase of
|
38
|
+
# the transaction.
|
38
39
|
#
|
39
40
|
# Effectively this ensures that an optimistic lock check and version bump
|
40
41
|
# occurs as usual outside of mediation but only at the end of the
|
@@ -63,7 +63,8 @@ module GraphMediator
|
|
63
63
|
attributes.all? { |a| attribute_changed?(a) }
|
64
64
|
end
|
65
65
|
|
66
|
-
# True if any of the passed attributes were changed in root or a
|
66
|
+
# True if any of the passed attributes were changed in root or a
|
67
|
+
# dependent.
|
67
68
|
def any_changed?(*attributes)
|
68
69
|
attributes.any? { |a| attribute_changed?(a) }
|
69
70
|
end
|
@@ -102,9 +103,9 @@ module GraphMediator
|
|
102
103
|
klass = $1
|
103
104
|
klass = klass.classify.constantize if klass
|
104
105
|
attribute = $2
|
105
|
-
# XXX Don't define a method here, or you run into issues with Rails class
|
106
|
-
# After the first call, you hold a reference to an old Class which
|
107
|
-
# a key in a new changes hash.
|
106
|
+
# XXX Don't define a method here, or you run into issues with Rails class
|
107
|
+
# reloading. After the first call, you hold a reference to an old Class which
|
108
|
+
# will no longer work as a key in a new changes hash.
|
108
109
|
# self.class.__send__(:define_method, method) do
|
109
110
|
return attribute_changed?(attribute, klass)
|
110
111
|
# end
|
@@ -200,7 +201,7 @@ module GraphMediator
|
|
200
201
|
end
|
201
202
|
end
|
202
203
|
|
203
|
-
# Reload
|
204
|
+
# Reload the mediated instance. Throws an ActiveRecord::StaleObjectError
|
204
205
|
# if lock_column has been updated outside of transaction.
|
205
206
|
def refresh_mediated_instance
|
206
207
|
debug "refresh_mediated_instance called"
|
data/lib/graph_mediator.rb
CHANGED
@@ -3,20 +3,23 @@ require 'graph_mediator/mediator'
|
|
3
3
|
require 'graph_mediator/locking'
|
4
4
|
require 'graph_mediator/version'
|
5
5
|
|
6
|
-
# = GraphMediator
|
6
|
+
# = GraphMediator
|
7
7
|
#
|
8
|
-
# GraphMediator is used to coordinate changes between a graph of ActiveRecord
|
9
|
-
# related to a root node. See README.rdoc for details.
|
8
|
+
# GraphMediator is used to coordinate changes between a graph of ActiveRecord
|
9
|
+
# objects related to a root node. See README.rdoc for details.
|
10
10
|
#
|
11
|
-
# GraphMediator::Base::DSL - is the simple class macro language used to set up
|
11
|
+
# GraphMediator::Base::DSL - is the simple class macro language used to set up
|
12
|
+
# mediation.
|
12
13
|
#
|
13
14
|
# == Versioning and Optimistic Locking
|
14
15
|
#
|
15
|
-
# If you include an integer +lock_version+ column in your class, it will be
|
16
|
-
# only once within a mediated_transaction and will serve as the
|
17
|
-
# for the entire graph so long as you have declared
|
16
|
+
# If you include an integer +lock_version+ column in your class, it will be
|
17
|
+
# incremented only once within a mediated_transaction and will serve as the
|
18
|
+
# optimistic locking check for the entire graph so long as you have declared
|
19
|
+
# all your dependent models for mediation.
|
18
20
|
#
|
19
|
-
# Outside of a mediated_transaction, +lock_version+ will increment per update
|
21
|
+
# Outside of a mediated_transaction, +lock_version+ will increment per update
|
22
|
+
# as usual.
|
20
23
|
#
|
21
24
|
# == Convenience Methods for Save Without Mediation
|
22
25
|
#
|
@@ -26,14 +29,14 @@ require 'graph_mediator/version'
|
|
26
29
|
#
|
27
30
|
# For example, save_without_mediation! is equivalent to:
|
28
31
|
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
+
# instance.disable_mediation!
|
33
|
+
# instance.save!
|
34
|
+
# instance.enable_mediation!
|
32
35
|
#
|
33
36
|
# == Overriding
|
34
37
|
#
|
35
|
-
# GraphMediator overrides ActiveRecord's save_without_transaction to slip in
|
36
|
-
# just before the save process is wrapped in a transaction.
|
38
|
+
# GraphMediator overrides ActiveRecord's save_without_transaction to slip in
|
39
|
+
# mediation just before the save process is wrapped in a transaction.
|
37
40
|
#
|
38
41
|
# * save_without_transaction
|
39
42
|
# * save_without_transaction_with_mediation
|
@@ -43,11 +46,11 @@ require 'graph_mediator/version'
|
|
43
46
|
# defined locally by GraphMediator, so you can override with something like
|
44
47
|
# alias_method_chain, but will need to be in a subclass to use super.
|
45
48
|
#
|
46
|
-
# My original intention was to define aliased overrides in MediatorProxy if the
|
47
|
-
# was a method in a superclass (like save), so that the implementation
|
48
|
-
# make a simple def foo; something; super; end override, but this
|
49
|
-
# in ruby 1.8 with aliasing of methods that use super in
|
50
|
-
# http://redmine.ruby-lang.org/issues/show/734
|
49
|
+
# My original intention was to define aliased overrides in MediatorProxy if the
|
50
|
+
# target was a method in a superclass (like save), so that the implementation
|
51
|
+
# class could make a simple def foo; something; super; end override, but this
|
52
|
+
# is prevented by a bug in ruby 1.8 with aliasing of methods that use super in
|
53
|
+
# a module. http://redmine.ruby-lang.org/issues/show/734
|
51
54
|
#
|
52
55
|
module GraphMediator
|
53
56
|
|
@@ -126,9 +129,10 @@ module GraphMediator
|
|
126
129
|
# GraphMediator::Configuration.logger overrides this.
|
127
130
|
mattr_accessor :logger
|
128
131
|
|
129
|
-
# Log level may be adjusted just for GraphMediator globally, or for each
|
130
|
-
# GraphMediator. This should be an
|
131
|
-
# such as
|
132
|
+
# Log level may be adjusted just for GraphMediator globally, or for each
|
133
|
+
# class including GraphMediator. This should be an
|
134
|
+
# ActiveSupport::BufferedLogger log level constant such as
|
135
|
+
# ActiveSupport::BufferedLogger::DEBUG
|
132
136
|
mattr_accessor :log_level
|
133
137
|
self.log_level = ActiveSupport::BufferedLogger::INFO
|
134
138
|
end
|
@@ -179,8 +183,8 @@ module GraphMediator
|
|
179
183
|
# (This is overwritten by GraphMediator._include_new_proxy)
|
180
184
|
def mediator_hash_key; end
|
181
185
|
|
182
|
-
# Unique key to access a thread local array of mediators of new records
|
183
|
-
# specific #{base}::MediatorProxy type.
|
186
|
+
# Unique key to access a thread local array of mediators of new records
|
187
|
+
# for specific #{base}::MediatorProxy type.
|
184
188
|
#
|
185
189
|
# (This is overwritten by GraphMediator._include_new_proxy)
|
186
190
|
def mediator_new_array_key; end
|
@@ -212,7 +216,10 @@ module GraphMediator
|
|
212
216
|
def mediated_transaction(&block)
|
213
217
|
m_debug("#{self}.mediated_transaction called")
|
214
218
|
mediator = _get_mediator
|
215
|
-
result =
|
219
|
+
result = nil
|
220
|
+
transaction do
|
221
|
+
result = mediator.mediate(&block)
|
222
|
+
end
|
216
223
|
m_debug("#{self}.mediated_transaction completed successfully")
|
217
224
|
return result
|
218
225
|
ensure
|
@@ -288,8 +295,8 @@ module GraphMediator
|
|
288
295
|
self.class.mediators_for_new_records
|
289
296
|
end
|
290
297
|
|
291
|
-
# Accessor for the mediator associated with this instance's id, or nil if
|
292
|
-
# not currently mediating.
|
298
|
+
# Accessor for the mediator associated with this instance's id, or nil if
|
299
|
+
# we are not currently mediating.
|
293
300
|
def current_mediator
|
294
301
|
m_debug("#{self}.current_mediator called")
|
295
302
|
mediator = mediators[self.id]
|
@@ -323,16 +330,17 @@ module GraphMediator
|
|
323
330
|
private
|
324
331
|
|
325
332
|
# Wraps each method in a mediated_transaction call.
|
326
|
-
# The original method is aliased as :method_without_mediation so that it
|
327
|
-
# overridden separately if needed.
|
333
|
+
# The original method is aliased as :method_without_mediation so that it
|
334
|
+
# can be overridden separately if needed.
|
328
335
|
#
|
329
336
|
# * options:
|
330
337
|
# * :through => root node accessor that will be the target of the
|
331
|
-
#
|
338
|
+
# mediated_transaction. By default self is assumed.
|
332
339
|
# * :track_changes => if true, the mediator will track changes such
|
333
|
-
#
|
334
|
-
#
|
335
|
-
#
|
340
|
+
# that they can be reviewed after_mediation. The after_mediation
|
341
|
+
# callbacks occur after dirty has completed and changes are normally
|
342
|
+
# lost. False by default. Normally only applied to save and destroy
|
343
|
+
# methods.
|
336
344
|
def _register_for_mediation(*methods)
|
337
345
|
options = methods.extract_options!
|
338
346
|
root_node_accessor = options[:through]
|
@@ -426,16 +434,16 @@ module GraphMediator
|
|
426
434
|
#
|
427
435
|
# * before_mediation - runs before mediation is begun
|
428
436
|
# * - mediate and save
|
429
|
-
# * mediate_reconciles - after saveing the instance, run any routines to make
|
430
|
-
# adjustments to the structure of the graph or non-cache attributes
|
437
|
+
# * mediate_reconciles - after saveing the instance, run any routines to make
|
438
|
+
# further adjustments to the structure of the graph or non-cache attributes
|
431
439
|
# * mediate_caches - routines for updating cache values
|
432
440
|
#
|
433
441
|
# Example:
|
434
442
|
#
|
435
|
-
#
|
436
|
-
#
|
437
|
-
#
|
438
|
-
#
|
443
|
+
# mediate_reconciles :bar do |instance|
|
444
|
+
# instance.something_else
|
445
|
+
# end
|
446
|
+
# mediate_reconciles :baz
|
439
447
|
#
|
440
448
|
# will ensure that [:bar, <block>, :baz] are run in
|
441
449
|
# sequence after :foo is done saveing within the context of a mediated
|
@@ -448,7 +456,7 @@ module GraphMediator
|
|
448
456
|
# for mediation.
|
449
457
|
#
|
450
458
|
# * :methods => list of methods to mediate (automatically wrap in a
|
451
|
-
#
|
459
|
+
# mediated_transaction call)
|
452
460
|
#
|
453
461
|
# ActiveRecord::Base.save is decorated for mediation when GraphMediator
|
454
462
|
# is included into your model. If you have additional methods which
|
@@ -462,15 +470,15 @@ module GraphMediator
|
|
462
470
|
# * :options => hash of options
|
463
471
|
# * :dependencies => list of dependent member classes whose save methods
|
464
472
|
# should be decorated for mediation as well.
|
465
|
-
# * :when_reconciling => list of methods to execute during the
|
466
|
-
# reconcilation phase
|
467
|
-
# * :when_cacheing => list of methods to execute during the
|
468
|
-
# cacheing phase
|
473
|
+
# * :when_reconciling => list of methods to execute during the
|
474
|
+
# after_mediation reconcilation phase
|
475
|
+
# * :when_cacheing => list of methods to execute during the
|
476
|
+
# after_mediation cacheing phase
|
469
477
|
#
|
470
|
-
#
|
471
|
-
#
|
472
|
-
#
|
473
|
-
#
|
478
|
+
# mediate :update_children,
|
479
|
+
# :dependencies => Child,
|
480
|
+
# :when_reconciling => :reconcile,
|
481
|
+
# :when_caching => :cache
|
474
482
|
#
|
475
483
|
# = Dependent Classes
|
476
484
|
#
|
@@ -483,11 +491,13 @@ module GraphMediator
|
|
483
491
|
#
|
484
492
|
# GraphMediator uses the class's lock_column (default +lock_version+) and
|
485
493
|
# +updated_at+ or +updated_on+ for versioning and locks checks during
|
486
|
-
# mediation. The lock_column is incremented only once during a
|
494
|
+
# mediation. The lock_column is incremented only once during a
|
495
|
+
# mediated_transaction.
|
487
496
|
#
|
488
|
-
#
|
489
|
-
# will not happen
|
490
|
-
# there is an updated_at/on timestamp available to
|
497
|
+
# <em>Unless both these columns are present in the schema,
|
498
|
+
# versioning/locking will not happen.</em> A lock_column by itself will
|
499
|
+
# not be updated unless there is an updated_at/on timestamp available to
|
500
|
+
# touch.
|
491
501
|
#
|
492
502
|
def mediate(*methods)
|
493
503
|
options = methods.extract_options!
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
|
2
|
+
|
3
|
+
require 'reservations/party_lodging'
|
4
|
+
require 'reservations/lodging'
|
5
|
+
require 'reservations/party'
|
6
|
+
require 'reservations/reservation'
|
7
|
+
|
8
|
+
describe "GraphMediator transaction scenarios" do
|
9
|
+
|
10
|
+
before(:all) do
|
11
|
+
load 'reservations/schema.rb'
|
12
|
+
end
|
13
|
+
|
14
|
+
before(:each) do
|
15
|
+
@today = Date.today
|
16
|
+
@r1 = Reservation.create!(:starts => @today, :ends => @today + 1, :name => 'foo')
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should implicitly provide an activerecord transaction for mediated_transaction" do
|
20
|
+
lambda {
|
21
|
+
@r1.mediated_transaction do
|
22
|
+
@r1.parties.create(:name => 'Bob')
|
23
|
+
raise('should cause transaction rollback')
|
24
|
+
end
|
25
|
+
}.should raise_error(RuntimeError)
|
26
|
+
@r1.reload
|
27
|
+
@r1.parties.should be_empty
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should handle rollback in a mediated_transaction" do
|
31
|
+
@r1.mediated_transaction do
|
32
|
+
@r1.parties.create(:name => 'Bob')
|
33
|
+
raise(ActiveRecord::Rollback)
|
34
|
+
end
|
35
|
+
@r1.reload
|
36
|
+
@r1.parties.should be_empty
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 2
|
8
|
-
-
|
9
|
-
version: 0.2.
|
8
|
+
- 2
|
9
|
+
version: 0.2.2
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Josh Partlow
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-
|
17
|
+
date: 2010-12-08 00:00:00 -08:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -101,6 +101,7 @@ files:
|
|
101
101
|
- spec/integration/locking_tests_spec.rb
|
102
102
|
- spec/integration/nesting_spec.rb
|
103
103
|
- spec/integration/threads_spec.rb
|
104
|
+
- spec/integration/transactions_spec.rb
|
104
105
|
- spec/integration/validation_spec.rb
|
105
106
|
- spec/investigation/alias_method_chain_spec.rb
|
106
107
|
- spec/investigation/insert_subclass_spec.rb
|
@@ -157,6 +158,7 @@ test_files:
|
|
157
158
|
- spec/integration/locking_tests_spec.rb
|
158
159
|
- spec/integration/nesting_spec.rb
|
159
160
|
- spec/integration/threads_spec.rb
|
161
|
+
- spec/integration/transactions_spec.rb
|
160
162
|
- spec/integration/validation_spec.rb
|
161
163
|
- spec/investigation/alias_method_chain_spec.rb
|
162
164
|
- spec/investigation/insert_subclass_spec.rb
|