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,159 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ describe "GraphMediator change tracking scenarios" do
4
+
5
+ before(:all) do
6
+ create_schema do |conn|
7
+ conn.create_table(:changes_roots, :force => true) do |t|
8
+ t.string :name
9
+ t.string :state
10
+ t.integer :changes_dependents_count
11
+ end
12
+ conn.create_table(:changes_dependents, :force => true) do |t|
13
+ t.string :name
14
+ t.integer :number
15
+ t.belongs_to :changes_root
16
+ end
17
+ end
18
+
19
+ class ChangesDependent < ActiveRecord::Base
20
+ belongs_to :changes_root, :counter_cache => true
21
+ end
22
+ class ChangesRoot < ActiveRecord::Base
23
+ cattr_accessor :tests
24
+ has_many :changes_dependents
25
+ include GraphMediator
26
+ mediate :dependencies => [ChangesDependent]
27
+ mediate_reconciles :reconciles do |instance|
28
+ instance.tests.call(instance) unless instance.tests.nil?
29
+ end
30
+ def reconciles; :reconciles; end
31
+ end
32
+ end
33
+
34
+ before(:each) do
35
+ ChangesRoot.tests = nil
36
+ @today = Date.today
37
+ end
38
+
39
+ it "should track changes to mediated instance" do
40
+ r = ChangesRoot.new(:name => :foo)
41
+ r.save_without_mediation!
42
+ r.state = :one
43
+ changes = r.changes
44
+ run = false
45
+ r.tests = Proc.new do |instance|
46
+ instance.changes.should == {}
47
+ instance.mediated_changes.should == { ChangesRoot => { r.id => changes } }
48
+ run = true
49
+ end
50
+ r.save!
51
+ run.should be_true
52
+ end
53
+
54
+ it "should track changes to a newly created mediated instance" do
55
+ r = ChangesRoot.new(:name => :foo)
56
+ changes = r.changes
57
+ run = false
58
+ r.tests = Proc.new do |instance|
59
+ instance.should equal(r)
60
+ instance.changes.should == {}
61
+ instance.mediated_changes.should == { ChangesRoot => { :_created => [changes] } }
62
+ run = true
63
+ end
64
+ r.save!
65
+ run.should be_true
66
+ end
67
+
68
+ it "should track addition of a dependent" do
69
+ r = ChangesRoot.new(:name => :foo)
70
+ r.save_without_mediation!
71
+ d = r.changes_dependents.build(:name => :bar)
72
+ changes = d.changes
73
+ run = false
74
+ r.tests = Proc.new do |instance|
75
+ instance.mediated_changes.should == { ChangesDependent => { :_created => [ changes ] } }
76
+ run = true
77
+ end
78
+ d.save!
79
+ run.should be_true
80
+ end
81
+
82
+ it "should track changes to a dependent" do
83
+ r = ChangesRoot.new(:name => :foo)
84
+ r.save_without_mediation!
85
+ d = r.changes_dependents.create!(:name => :bar)
86
+ d.number = 2
87
+ changes = d.changes
88
+ run = false
89
+ r.tests = Proc.new do |instance|
90
+ instance.mediated_changes.should == { ChangesDependent => { d.id => changes } }
91
+ run = true
92
+ end
93
+ d.save!
94
+ run.should be_true
95
+ end
96
+
97
+ it "should track deletion of a dependent" do
98
+ r = ChangesRoot.new(:name => :foo)
99
+ r.save_without_mediation!
100
+ d = r.changes_dependents.create!(:name => :bar)
101
+ run = false
102
+ r.tests = Proc.new do |instance|
103
+ instance.mediated_changes.should == { ChangesDependent => { :_destroyed => [d.id] } }
104
+ run = true
105
+ end
106
+ d.destroy
107
+ run.should be_true
108
+ end
109
+
110
+ it "should differentiate changes to attributes with the same name in different classes." do
111
+ r = ChangesRoot.new(:name => :foo)
112
+ r.save_without_mediation!
113
+ d = r.changes_dependents.create!(:name => :bar)
114
+
115
+ d.number = 3
116
+ dep_changes = d.changes
117
+ r.name = 'different'
118
+ root_changes = r.changes
119
+ run = false
120
+ mediated_changes = nil
121
+ r.tests = Proc.new do |instance|
122
+ d.save!
123
+ mediated_changes = instance.mediated_changes
124
+ mediated_changes.should == { ChangesRoot => { r.id => root_changes }, ChangesDependent => { d.id => dep_changes } }
125
+ run = true
126
+ end
127
+ r.save!
128
+ run.should be_true
129
+ mediated_changes.changed_name?.should be_true
130
+ mediated_changes.attribute_changed?(:name, ChangesDependent).should be_false
131
+ mediated_changes.changes_dependent_changed_name?.should be_false
132
+ mediated_changes.changes_root_changed_name?.should be_true
133
+
134
+ r.state = :new_state
135
+ root_changes = r.changes
136
+ d.name = 'also different'
137
+ dep_changes = d.changes
138
+ r.save!
139
+ run.should be_true
140
+ mediated_changes.changed_name?.should be_true
141
+ mediated_changes.changes_dependent_changed_name?.should be_true
142
+ mediated_changes.changes_root_changed_name?.should be_false
143
+ end
144
+
145
+ it "should handle attribute queries for classes that have had no changes recorded" do
146
+ r = ChangesRoot.new(:name => :foo)
147
+ r.save_without_mediation!
148
+ run = false
149
+ r.tests = Proc.new do |instance|
150
+ mediated_changes = instance.mediated_changes
151
+ mediated_changes.should == { ChangesRoot => { r.id => {} }}
152
+ mediated_changes.changes_dependent_changed_name?.should be_false
153
+ run = true
154
+ end
155
+ r.save!
156
+ run.should be_true
157
+ end
158
+
159
+ end
@@ -0,0 +1,214 @@
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 locking scenarios" do
9
+
10
+ before(:all) do
11
+ load 'reservations/schema.rb'
12
+ end
13
+
14
+ before(:each) do
15
+ @today = Date.today
16
+ @handle1_r1 = Reservation.create!(:starts => @today, :ends => @today + 1, :name => 'foo')
17
+ @handle2_r1 = Reservation.find(@handle1_r1.id)
18
+ end
19
+
20
+ it "should be possible to create an unattached dependent object" do
21
+ Lodging.create!.should_not be_nil
22
+ end
23
+
24
+ it "should increment lock_version for save_without_mediation" do
25
+ lambda {
26
+ @handle1_r1.starts = @today - 1
27
+ @handle1_r1.save_without_mediation
28
+ }.should change(@handle1_r1, :lock_version).by(1)
29
+ end
30
+
31
+ context "with optimistic locking for the graph" do
32
+
33
+ it "should raise Stale for conflicts updating root" do
34
+ @handle2_r1.update_attributes(:name => 'bar')
35
+ lambda { @handle1_r1.update_attributes(:name => 'baz') }.should raise_error(ActiveRecord::StaleObjectError)
36
+ @handle1_r1.reload
37
+ @handle1_r1.name.should == 'bar'
38
+ end
39
+
40
+ # Creating root cannot conflict
41
+ # because the rows do not exist until the mediation transaction completes
42
+ # so no one else is in a position to write first
43
+
44
+ # not possible? ActiveRecord::Locking::Optimistic does not decorate :destroy
45
+ # it "should raise stale for conflicts deleting root" do
46
+
47
+ it "should raise Stale for conflicts creating children" do
48
+ @handle2_r1.parties.create(:name => 'Bob')
49
+ lambda { @handle1_r1.update_attributes(:name => 'baz') }.should raise_error(ActiveRecord::StaleObjectError)
50
+ end
51
+
52
+ context "with children" do
53
+
54
+ before(:each) do
55
+ @handle1_r1.mediated_transaction do
56
+ @party1 = @handle1_r1.parties.create(:name => 'Bob')
57
+ @party2 = @handle1_r1.parties.create(:name => 'Joe')
58
+ @room1 = @handle1_r1.lodgings.create(:room_number => 1)
59
+ @room2 = @handle1_r1.lodgings.create(:room_number => 2)
60
+ end
61
+ @handle2_r1.reload
62
+ end
63
+
64
+ it "should raise Stale for conflicts updating children and root" do
65
+ @handle2_r1.parties.first.update_attributes(:name => 'Frank')
66
+ lambda { @handle1_r1.update_attributes(:name => 'baz') }.should raise_error(ActiveRecord::StaleObjectError)
67
+ end
68
+
69
+ it "should raise Stale for conflicts updating just children" do
70
+ @handle2_r1.parties.first.update_attributes(:name => 'Frank')
71
+ # version has incremented because of update to party, so, :versioning touch fails
72
+ lambda { @handle1_r1.mediated_transaction {} }.should raise_error(ActiveRecord::StaleObjectError)
73
+ end
74
+
75
+ it "should raise Stale for conflicts deleting children and touching root" do
76
+ @handle2_r1.parties.first.destroy
77
+ lambda { @handle1_r1.update_attributes(:name => 'baz') }.should raise_error(ActiveRecord::StaleObjectError)
78
+ end
79
+
80
+ it "should not raise stale because of updates to its own children counter_caches" do
81
+ @handle1_r1.mediated_transaction do
82
+ @handle1_r1.lodge(@party1, :in => @room1)
83
+ @handle1_r1.parties.first.touch
84
+ end
85
+ end
86
+
87
+ it "should increment lock_version for the graph if a dependent is changed" do
88
+ lambda {
89
+ @handle1_r1.parties.first.touch
90
+ @handle1_r1.reload
91
+ }.should change(@handle1_r1, :lock_version).by(1)
92
+ end
93
+
94
+ context "with lock_version for dependent children" do
95
+
96
+ before(:all) do
97
+ create_schema do |conn|
98
+ conn.add_column(:parties, :lock_version, :integer)
99
+ end
100
+ Party.reset_column_information
101
+ end
102
+
103
+ after(:all) do
104
+ create_schema do |conn|
105
+ conn.remove_column(:parties, :lock_version)
106
+ end
107
+ Party.reset_column_information
108
+ end
109
+
110
+ it "will raise stale because of updates to its own children counter_caches" do
111
+ # Reservation::MediatorProxy._graph_mediator_logger = TestLogger.new
112
+ Reservation::MediatorProxy._graph_mediator_log_level = 0
113
+ r = Reservation.create!(:starts => @today, :ends => @today)
114
+ party, room = nil, nil
115
+ r.mediated_transaction do
116
+ party = r.parties.create(:name => 'Joe')
117
+ room = r.lodgings.create(:room_number => 1)
118
+ end
119
+ r.mediated_transaction do
120
+ r.lodge(party, :in => room)
121
+ lambda { party.touch }.should raise_error(ActiveRecord::StaleObjectError)
122
+ end
123
+ end
124
+
125
+ end
126
+ end
127
+
128
+ context "within a mediated transaction" do
129
+
130
+ it "should not raise stale for changes made within a mediated transaction" do
131
+ lambda { @handle1_r1.mediated_transaction do
132
+ @handle1_r1.parties.create(:name => 'Bob')
133
+ end }. should change(@handle1_r1, :lock_version).by(1)
134
+ end
135
+
136
+ it "should not raise stale for counter_caches within a mediated transaction" do
137
+ lambda { @handle1_r1.mediated_transaction do
138
+ @handle1_r1.parties.create(:name => 'Bob')
139
+ @handle1_r1.update_attributes(:name => 'baz')
140
+ end }.should change(@handle1_r1, :lock_version).by(1)
141
+ end
142
+ end
143
+ end
144
+
145
+ end
146
+
147
+ module GraphMediatorLocking # namespace to prevent helper class conflicts
148
+
149
+ describe "GraphMediator locking scenarios for classes without counter_caches" do
150
+
151
+ before(:all) do
152
+ create_schema do |connection|
153
+ connection.create_table(:foos, :force => true) do |t|
154
+ t.string :name
155
+ t.integer :lock_version, :default => 0
156
+ t.timestamps
157
+ end
158
+
159
+ connection.create_table(:bars, :force => true) do |t|
160
+ t.string :name
161
+ t.belongs_to :foo
162
+ t.timestamps
163
+ end
164
+ end
165
+ end
166
+
167
+ class Bar < ActiveRecord::Base
168
+ belongs_to :foo
169
+ end
170
+
171
+ class Foo < ActiveRecord::Base
172
+ include GraphMediator
173
+ mediate :dependencies => Bar
174
+ has_many :bars
175
+ end
176
+
177
+ before(:each) do
178
+ @h1_foo1 = Foo.create(:name => 'one')
179
+ @h2_foo1 = Foo.find(@h1_foo1.id)
180
+ end
181
+
182
+ it "should also raise Stale for conflicts updating root" do
183
+ # nothing to touch...
184
+ @h2_foo1.update_attributes(:name => 'bar')
185
+ lambda { @h1_foo1.update_attributes(:name => 'baz') }.should raise_error(ActiveRecord::StaleObjectError)
186
+ @h1_foo1.reload
187
+ @h1_foo1.name.should == 'bar'
188
+ end
189
+
190
+ it "should raise Stale for conflicts deleting children and touching root" do
191
+ @h1_foo1.bars << Bar.create!
192
+ @h2_foo1.bars.first.destroy
193
+ lambda { @h1_foo1.update_attributes(:name => 'baz') }.should raise_error(ActiveRecord::StaleObjectError)
194
+ end
195
+
196
+ it "should increment lock_version for the graph if a dependent is added" do
197
+ lambda {
198
+ @h1_foo1.bars << Bar.create!
199
+ @h1_foo1.reload
200
+ }.should change(@h1_foo1, :lock_version).by(1)
201
+ end
202
+
203
+ it "should increment lock_version for the graph if a dependent is deleted" do
204
+ @h1_foo1.bars << Bar.create!
205
+ @h1_foo1.reload
206
+ lambda {
207
+ @h1_foo1.bars.first.destroy
208
+ @h1_foo1.reload
209
+ }.should change(@h1_foo1, :lock_version).by(1)
210
+ end
211
+
212
+ end
213
+
214
+ end
@@ -0,0 +1,113 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ describe "Nesting of mediated_transactions" do
4
+
5
+ before(:all) do
6
+ #@old_logger = GraphMediator::Configuration.logger
7
+ #GraphMediator::Configuration.logger = TestLogger.new
8
+ @old_log_level = GraphMediator::Configuration.log_level
9
+ GraphMediator::Configuration.log_level = ActiveSupport::BufferedLogger::DEBUG
10
+ end
11
+
12
+ after(:all) do
13
+ #GraphMediator::Configuration.logger = @old_logger
14
+ GraphMediator::Configuration.log_level = @old_log_level
15
+ end
16
+
17
+ before(:each) do
18
+ load_traceable_callback_tester
19
+ @t = Traceable.new(:name => :gizmo)
20
+ @t.save_without_mediation!
21
+ end
22
+
23
+ after(:each) do
24
+ Object.__send__(:remove_const, :Traceable)
25
+ end
26
+
27
+ it "should only perform a single after_mediation even with nested mediated_transactions" do
28
+ @t.mediated_transaction do
29
+ @t.mediated_transaction {}
30
+ end
31
+ @traceables_callbacks.should == [:before, :reconcile, :cache]
32
+ end
33
+
34
+ it "should only perform a single after_mediation even with implicitly nested mediated transactions" do
35
+
36
+ @t.mediated_transaction do
37
+ @t.update_attributes(:name => 'foo')
38
+ end
39
+ @traceables_callbacks.should == [:before, :reconcile, :cache]
40
+ end
41
+
42
+ it "should handle deeper nesting" do
43
+ @t.mediated_transaction do
44
+ @t.mediated_transaction do
45
+ @t.update_attributes(:name => 'foo')
46
+ end
47
+ end
48
+ @t.__send__(:current_mediator).should be_nil
49
+ @traceables_callbacks.should == [:before, :reconcile, :cache]
50
+ end
51
+
52
+ it "should handle errors raised while deeply nested" do
53
+ lambda { @t.mediated_transaction do
54
+ @t.mediated_transaction do
55
+ @t.mediated_transaction { raise }
56
+ end
57
+ end }.should raise_error(RuntimeError)
58
+ @t.__send__(:current_mediator).should be_nil
59
+ @traceables_callbacks.should == [:before]
60
+ end
61
+
62
+ it "should handle errors raised and recovered while deeply nested" do
63
+ @t.mediated_transaction do
64
+ @t.mediated_transaction do
65
+ lambda { @t.mediated_transaction { raise } }.should raise_error(RuntimeError)
66
+ end
67
+ end
68
+ @t.__send__(:current_mediator).should be_nil
69
+ @traceables_callbacks.should == [:before, :reconcile, :cache]
70
+ end
71
+
72
+ it "should call after_mediation only once even if mediated_transaction is called on a new instance" do
73
+ Traceable.logger.debug "\n\n\nnew test"
74
+ new_t = Traceable.new(:name => 'new')
75
+ new_t.mediated_transaction { new_t.save }
76
+ @traceables_callbacks.should == [:before, :reconcile, :cache]
77
+ end
78
+
79
+ it "should clear mediator if unrecovered error raised in nested transaction" do
80
+ lambda { @t.mediated_transaction do
81
+ @t.mediated_transaction { raise }
82
+ end }.should raise_error(RuntimeError)
83
+ @t.__send__(:current_mediator).should be_nil
84
+ @traceables_callbacks.should == [:before]
85
+ end
86
+
87
+ it "should finish mediation if error raised and recovered in nested transaction" do
88
+ @t.mediated_transaction do
89
+ lambda { @t.mediated_transaction { raise } }.should raise_error(RuntimeError)
90
+ end
91
+ @t.__send__(:current_mediator).should be_nil
92
+ @traceables_callbacks.should == [:before, :reconcile, :cache]
93
+ end
94
+
95
+ it "should run no callbacks if disabled, regardless of nesting" do
96
+ @t.disable_mediation!
97
+ @t.mediated_transaction do
98
+ @t.mediated_transaction { }
99
+ end
100
+ @traceables_callbacks.should == []
101
+ end
102
+
103
+ it "should continue to be disabled if error raised and recovered in a nested transaction when mediation is disabled" do
104
+ @t.disable_mediation!
105
+ @t.mediated_transaction do
106
+ lambda { @t.mediated_transaction { raise } }.should raise_error(RuntimeError)
107
+ end
108
+ @t.__send__(:current_mediator).should be_nil
109
+ @traceables_callbacks.should == []
110
+ end
111
+
112
+
113
+ end