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 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 +updated_at/on+ timestamp then
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 +update_at/on+ timestamp, then +lock_column+ cannot be
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
- +A lock_column by itself *without* a timestamp will not increment and will not provide
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 a StaleObject
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 updated with
100
- a direct SQL call to the database, so ActiveRecord instance remain unaware of the lock_version
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 as a dependency
104
- of the root node, since updates will also update the root nodes lock_version. So if another
105
- transaction updates a child, root.lock_version should increment, and the first transaction
106
- should raise a StaleObject error when it too tries to update the child.
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 is updated
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 currently mediating
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
- # per ActiveRecord (lock_column exists and lock_optimistically? true)
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
- # ActiveRecord and we are in the midst of the version bumping phase of the transaction.
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 dependent.
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 reloading.
106
- # After the first call, you hold a reference to an old Class which will no longer work as
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 them mediated instance. Throws an ActiveRecord::StaleObjectError
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"
@@ -1,3 +1,3 @@
1
1
  module GraphMediator
2
- VERSION = "0.2.1"
2
+ VERSION = "0.2.2"
3
3
  end
@@ -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 objects
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 mediation.
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 incremented
16
- # only once within a mediated_transaction and will serve as the optimistic locking check
17
- # for the entire graph so long as you have declared all your dependent models for mediation.
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 as usual.
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
- # instance.disable_mediation!
30
- # instance.save!
31
- # instance.enable_mediation!
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 mediation
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 target
47
- # was a method in a superclass (like save), so that the implementation class could
48
- # make a simple def foo; something; super; end override, but this is prevented by a bug
49
- # in ruby 1.8 with aliasing of methods that use super in a module.
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 class including
130
- # GraphMediator. This should be an ActiveSupport::BufferedLogger log level constant
131
- # such as ActiveSupport::BufferedLogger::DEBUG
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 for
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 = mediator.mediate(&block)
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 we are
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 can be
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
- # mediated_transaction. By default self is assumed.
338
+ # mediated_transaction. By default self is assumed.
332
339
  # * :track_changes => if true, the mediator will track changes such
333
- # that they can be reviewed after_mediation. The after_mediation
334
- # callbacks occur after dirty has completed and changes are normally lost.
335
- # False by default. Normally only applied to save and destroy methods.
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 further
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
- # mediate_reconciles :bar do |instance|
436
- # instance.something_else
437
- # end
438
- # mediate_reconciles :baz
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
- # mediated_transaction call)
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 after_mediation
466
- # reconcilation phase
467
- # * :when_cacheing => list of methods to execute during the after_mediation
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
- # mediate :update_children,
471
- # :dependencies => Child,
472
- # :when_reconciling => :reconcile,
473
- # :when_caching => :cache
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 mediated_transaction.
494
+ # mediation. The lock_column is incremented only once during a
495
+ # mediated_transaction.
487
496
  #
488
- # +Unless both these columns are present in the schema, versioning/locking
489
- # will not happen.+ A lock_column by itself will not be updated unless
490
- # there is an updated_at/on timestamp available to touch.
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!
@@ -200,7 +200,7 @@ describe "DingoPen" do
200
200
  dp.lock_version.should == 1
201
201
  end
202
202
 
203
- it "should succed without mediation" do
203
+ it "should succeed without mediation" do
204
204
  begin
205
205
  DingoPen.disable_all_mediation!
206
206
  dp = DingoPen.new(@dingo_pen_attributes)
@@ -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
- - 1
9
- version: 0.2.1
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-11-29 00:00:00 -08:00
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