graph_mediator 0.2.1 → 0.2.2
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/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
|