graph_mediator 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +4 -0
- data/.gitignore +26 -0
- data/LICENSE +20 -0
- data/README.rdoc +136 -0
- data/Rakefile +32 -0
- data/graph_mediator.gemspec +31 -0
- data/lib/graph_mediator.rb +509 -0
- data/lib/graph_mediator/locking.rb +50 -0
- data/lib/graph_mediator/mediator.rb +260 -0
- data/lib/graph_mediator/version.rb +3 -0
- data/spec/database.rb +12 -0
- data/spec/examples/course_example_spec.rb +91 -0
- data/spec/examples/dingo_pen_example_spec.rb +288 -0
- data/spec/graph_mediator_spec.rb +500 -0
- data/spec/integration/changes_spec.rb +159 -0
- data/spec/integration/locking_tests_spec.rb +214 -0
- data/spec/integration/nesting_spec.rb +113 -0
- data/spec/integration/threads_spec.rb +59 -0
- data/spec/integration/validation_spec.rb +19 -0
- data/spec/investigation/alias_method_chain_spec.rb +170 -0
- data/spec/investigation/insert_subclass_spec.rb +122 -0
- data/spec/investigation/insert_superclass_spec.rb +131 -0
- data/spec/investigation/module_super_spec.rb +88 -0
- data/spec/investigation/self_decorating.rb +55 -0
- data/spec/mediator_spec.rb +201 -0
- data/spec/reservations/lodging.rb +4 -0
- data/spec/reservations/party.rb +4 -0
- data/spec/reservations/party_lodging.rb +4 -0
- data/spec/reservations/reservation.rb +18 -0
- data/spec/reservations/schema.rb +33 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +65 -0
- metadata +173 -0
@@ -0,0 +1,88 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
|
2
|
+
|
3
|
+
module ModuleSuperSpec # namespacing
|
4
|
+
|
5
|
+
describe "module super" do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
module Baz
|
9
|
+
def baz
|
10
|
+
'baz'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module Bar
|
15
|
+
def bar
|
16
|
+
'bar'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class AbstractFoo
|
21
|
+
def deeper_foo
|
22
|
+
'deeper_foo'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Foo < AbstractFoo
|
27
|
+
include Baz
|
28
|
+
include Bar
|
29
|
+
def foo
|
30
|
+
'foo'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
after(:each) do
|
36
|
+
ModuleSuperSpec.__send__(:remove_const, :Baz)
|
37
|
+
ModuleSuperSpec.__send__(:remove_const, :Bar)
|
38
|
+
ModuleSuperSpec.__send__(:remove_const, :Foo)
|
39
|
+
ModuleSuperSpec.__send__(:remove_const, :AbstractFoo)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should basically work" do
|
43
|
+
f = Foo.new
|
44
|
+
f.foo.should == 'foo'
|
45
|
+
f.deeper_foo.should == 'deeper_foo'
|
46
|
+
f.bar.should == 'bar'
|
47
|
+
f.baz.should == 'baz'
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should override baz and retain a reference to original in a module" do
|
51
|
+
Bar.class_eval do
|
52
|
+
def baz
|
53
|
+
super
|
54
|
+
end
|
55
|
+
alias_method :baz_original, :baz
|
56
|
+
def baz
|
57
|
+
super + ' barred'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
f = Foo.new
|
61
|
+
f.foo.should == 'foo'
|
62
|
+
f.deeper_foo.should == 'deeper_foo'
|
63
|
+
f.bar.should == 'bar'
|
64
|
+
f.baz.should == 'baz barred'
|
65
|
+
# ruby 1.8 issue (see insert_subclass_spec.rb as well)
|
66
|
+
# http://redmine.ruby-lang.org/issues/show/734
|
67
|
+
lambda { f.baz_original }.should raise_error(NoMethodError)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "cannot alias a method higher up the chain from a module" do
|
71
|
+
lambda { Bar.class_eval do
|
72
|
+
alias_method :baz_original, :baz
|
73
|
+
end }.should raise_error(NameError)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "can call super from a method override in a module" do
|
77
|
+
Bar.class_eval do
|
78
|
+
def baz
|
79
|
+
super + ' barred from module'
|
80
|
+
end
|
81
|
+
end
|
82
|
+
f = Foo.new
|
83
|
+
f.baz.should == "baz barred from module"
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
class SelfDecorating
|
2
|
+
|
3
|
+
module Secret
|
4
|
+
@@methods_to_alias = []
|
5
|
+
def self.extended(base)
|
6
|
+
unless @@methods_to_alias.empty?
|
7
|
+
# only needs to be done once
|
8
|
+
@@methods_to_alias.each do |target|
|
9
|
+
without_method = "#{target}_without_secret"
|
10
|
+
klass = base.class
|
11
|
+
method_defined_here = (klass.instance_methods(false) + klass.private_instance_methods(false)).include?(RUBY_VERSION < '1.9' ? target.to_s : target)
|
12
|
+
unless method_defined_here
|
13
|
+
klass.send(:define_method, target) do |*args, &block|
|
14
|
+
super
|
15
|
+
end
|
16
|
+
end
|
17
|
+
unless klass.method_defined?(without_method)
|
18
|
+
klass.send(:alias_method, without_method, target)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
@@methods_to_alias.clear
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def foo
|
26
|
+
'foo'
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.methods_to_alias
|
30
|
+
@@methods_to_alias
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.new
|
35
|
+
c = super
|
36
|
+
return c.extend SelfDecorating::Secret
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.decorate(method)
|
40
|
+
Secret.class_eval do
|
41
|
+
# Error raised calling super from an aliased method included from a module where
|
42
|
+
# method is declared (Ruby 1.8)
|
43
|
+
# http://redmine.ruby-lang.org/issues/show/734
|
44
|
+
# define_method(method) do |*args,&block|
|
45
|
+
# super
|
46
|
+
# end
|
47
|
+
# alias_method "#{method}_without_secret", method
|
48
|
+
define_method(method) do |*args,&block|
|
49
|
+
super + ' with secret'
|
50
|
+
end
|
51
|
+
methods_to_alias << method
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
@@ -0,0 +1,201 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
|
3
|
+
describe "GraphMediator::Mediator" do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
load_traceable_callback_tester
|
7
|
+
@t = Traceable.new(:name => :gizmo)
|
8
|
+
@t.save_without_mediation!
|
9
|
+
@m = @t.__send__(:_get_mediator)
|
10
|
+
end
|
11
|
+
|
12
|
+
after(:each) do
|
13
|
+
Object.__send__(:remove_const, :Traceable)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should raise error if initialized with something that is not a GraphMediator" do
|
17
|
+
lambda { GraphMediator::Mediator.new(:foo) }.should raise_error(ArgumentError)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should transition through states" do
|
21
|
+
@m.should be_idle
|
22
|
+
@m.start!
|
23
|
+
@m.should be_mediating
|
24
|
+
@m.bump!
|
25
|
+
@m.should be_versioning
|
26
|
+
@m.done!
|
27
|
+
@m.should be_idle
|
28
|
+
@m.disable!
|
29
|
+
@m.should be_disabled
|
30
|
+
@m.done!
|
31
|
+
@m.should be_idle
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should reflect mediation_enabled of mediated_instance" do
|
35
|
+
@m.mediation_enabled?.should be_true
|
36
|
+
@t.disable_mediation!
|
37
|
+
@m.mediation_enabled?.should be_false
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should only perform a single after_mediation even with nested mediated_transactions" do
|
41
|
+
@m.mediate { @m.mediate { } }
|
42
|
+
@traceables_callbacks.should == [:before, :reconcile, :cache]
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should capture changes" do
|
46
|
+
@m.changes.should == {}
|
47
|
+
@t.name = :foo
|
48
|
+
@t.should be_changed
|
49
|
+
@m.track_changes_for(@t)
|
50
|
+
@m.changes.should == {Traceable => { @t.id => { 'name'=> [:gizmo, :foo] }}}
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should not blow the stack if attempt a transaction in before_create" do
|
54
|
+
begin
|
55
|
+
Traceable.before_create { |i| i.mediated_transaction { i.callbacks << :before_create } }
|
56
|
+
t = Traceable.new(:name => :foo)
|
57
|
+
# lambda { t.save! }.should raise_error(GraphMediator::MediatorException)
|
58
|
+
t.save!
|
59
|
+
@traceables_callbacks.should == [:before, :before_create, :reconcile, :cache]
|
60
|
+
ensure
|
61
|
+
Traceable.before_create_callback_chain.clear
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should reset to idle state if an error is thrown before mediation" do
|
66
|
+
class << @t
|
67
|
+
def before; raise; end
|
68
|
+
end
|
69
|
+
lambda { @m.mediate {} }.should raise_error(RuntimeError)
|
70
|
+
@m.aasm_current_state.should == :idle
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should reset to idle state if an error is thrown during mediation" do
|
74
|
+
lambda { @m.mediate { raise } }.should raise_error(RuntimeError)
|
75
|
+
@m.aasm_current_state.should == :idle
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should reset to idle state if an error is thrown after mediation" do
|
79
|
+
class << @t
|
80
|
+
def reconcile; raise; end
|
81
|
+
end
|
82
|
+
lambda { @m.mediate {} }.should raise_error(RuntimeError)
|
83
|
+
@m.aasm_current_state.should == :idle
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should reset to idle state if an error is thrown when cacheing" do
|
87
|
+
class << @t
|
88
|
+
def cache; raise; end
|
89
|
+
end
|
90
|
+
lambda { @m.mediate {} }.should raise_error(RuntimeError)
|
91
|
+
@m.aasm_current_state.should == :idle
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should reset to idle state if an error is thrown when versioning" do
|
95
|
+
class << @t
|
96
|
+
def touch; raise; end
|
97
|
+
end
|
98
|
+
lambda { @m.mediate {} }.should raise_error(RuntimeError)
|
99
|
+
@m.aasm_current_state.should == :idle
|
100
|
+
end
|
101
|
+
|
102
|
+
context "when enabling and disabling mediation" do
|
103
|
+
|
104
|
+
it "should continue to mediate if mediation disabled part way through" do
|
105
|
+
@m.mediation_enabled?.should be_true
|
106
|
+
@t.should_receive(:reconcile).once
|
107
|
+
@m.mediate do
|
108
|
+
@t.disable_mediation!
|
109
|
+
@m.mediate do
|
110
|
+
# should not begin a new transaction
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should continue as disabled if mediation enabled part way through" do
|
116
|
+
@t.disable_mediation!
|
117
|
+
@m.mediation_enabled?.should be_false
|
118
|
+
@t.should_not_receive(:reconcile)
|
119
|
+
@m.mediate do
|
120
|
+
@t.enable_mediation!
|
121
|
+
@m.mediate do
|
122
|
+
# should not begin a new transaction
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
context "with a GraphMediator::Mediator::ChangesHash" do
|
130
|
+
|
131
|
+
before(:each) do
|
132
|
+
@ch = GraphMediator::Mediator::ChangesHash.new
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should capture changes for new objects" do
|
136
|
+
@ch << Traceable.new
|
137
|
+
@ch.should == { Traceable => { :_created => [ {} ] } }
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should capture changes for changed objects" do
|
141
|
+
@t.name = :bar
|
142
|
+
@ch << @t
|
143
|
+
@ch.should == { Traceable => { @t.id => { 'name' => [:gizmo, :bar] } } }
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should capture changes for destroyed objects" do
|
147
|
+
@t.destroy
|
148
|
+
@ch << @t
|
149
|
+
@ch.should == { Traceable => { :_destroyed => [@t.id] } }
|
150
|
+
end
|
151
|
+
|
152
|
+
it "should answer questions about changed attributes" do
|
153
|
+
@t.name = :bar
|
154
|
+
@ch << @t
|
155
|
+
@ch.changed_name?.should be_true
|
156
|
+
@ch.changed_frotz?.should be_false
|
157
|
+
end
|
158
|
+
|
159
|
+
it "should answer questions about multiple changed attributes" do
|
160
|
+
@t.name = :bar
|
161
|
+
@t.state = :frotzed
|
162
|
+
@ch << @t
|
163
|
+
@ch.all_changed?(:name, :state).should be_true
|
164
|
+
@ch.all_changed?(:name, :number).should be_false
|
165
|
+
@ch.any_changed?(:name, :number).should be_true
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should answer questions about dependents not in the changes hash" do
|
169
|
+
@ch.should == {}
|
170
|
+
@ch.added_dependent?(Traceable).should == false
|
171
|
+
@ch.destroyed_dependent?(Traceable).should == false
|
172
|
+
@ch.altered_dependent?(Traceable).should == false
|
173
|
+
@ch.touched_any_dependent?(Traceable).should == false
|
174
|
+
end
|
175
|
+
|
176
|
+
it "should answer questions about added dependents" do
|
177
|
+
new_t = Traceable.new(:name => :dependent)
|
178
|
+
@ch << new_t
|
179
|
+
@ch.added_dependent?(Traceable).should be_true
|
180
|
+
@ch.added_or_destroyed_dependent?(Traceable).should be_true
|
181
|
+
@ch.touched_any_dependent?(Traceable).should be_true
|
182
|
+
end
|
183
|
+
|
184
|
+
it "should answer questions about deleted dependents" do
|
185
|
+
@t.destroy
|
186
|
+
@ch << @t
|
187
|
+
@ch.destroyed_dependent?(Traceable).should be_true
|
188
|
+
@ch.added_or_destroyed_dependent?(Traceable).should be_true
|
189
|
+
@ch.touched_any_dependent?(Traceable).should be_true
|
190
|
+
end
|
191
|
+
|
192
|
+
it "should answer questions about changed dependents" do
|
193
|
+
@t.name = :bar
|
194
|
+
@ch << @t
|
195
|
+
@ch.altered_dependent?(Traceable).should be_true
|
196
|
+
@ch.added_or_destroyed_dependent?(Traceable).should be_false
|
197
|
+
@ch.touched_any_dependent?(Traceable).should be_true
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Reservation < ActiveRecord::Base
|
2
|
+
has_many :parties
|
3
|
+
has_many :lodgings
|
4
|
+
has_many :party_lodgings, :through => :lodgings
|
5
|
+
|
6
|
+
include GraphMediator
|
7
|
+
mediate :dependencies => [Lodging, Party],
|
8
|
+
:when_reconciling => :reconcile,
|
9
|
+
:when_cacheing => :cache
|
10
|
+
|
11
|
+
def lodge(party, options = {})
|
12
|
+
lodging = options[:in]
|
13
|
+
party.party_lodgings.create(:lodging_id => lodging)
|
14
|
+
end
|
15
|
+
|
16
|
+
def reconcile; :reconcile; end
|
17
|
+
def cache; :cache; end
|
18
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
create_schema do |connection|
|
2
|
+
connection.create_table(:reservations, :force => true) do |t|
|
3
|
+
t.string :name
|
4
|
+
t.date :starts
|
5
|
+
t.date :ends
|
6
|
+
t.integer :parties_count
|
7
|
+
t.integer :lodgings_count
|
8
|
+
t.integer :lock_version, :default => 0
|
9
|
+
t.timestamps
|
10
|
+
end
|
11
|
+
|
12
|
+
connection.create_table(:parties, :force => true) do |t|
|
13
|
+
t.string :name
|
14
|
+
t.belongs_to :reservation
|
15
|
+
t.integer :party_lodgings_count
|
16
|
+
t.timestamps
|
17
|
+
end
|
18
|
+
|
19
|
+
connection.create_table(:lodgings, :force => true) do |t|
|
20
|
+
t.integer :room_number
|
21
|
+
t.decimal :rate
|
22
|
+
t.belongs_to :reservation
|
23
|
+
t.date :date
|
24
|
+
t.integer :party_lodgings_count
|
25
|
+
t.timestamps
|
26
|
+
end
|
27
|
+
|
28
|
+
connection.create_table(:party_lodgings, :force => true) do |t|
|
29
|
+
t.belongs_to :party
|
30
|
+
t.belongs_to :lodging
|
31
|
+
t.timestamps
|
32
|
+
end
|
33
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
require 'graph_mediator'
|
4
|
+
require 'spec'
|
5
|
+
require 'spec/autorun'
|
6
|
+
require 'pp'
|
7
|
+
|
8
|
+
require 'database'
|
9
|
+
|
10
|
+
class TestLogger
|
11
|
+
[:debug, :info, :warn, :error].each do |m|
|
12
|
+
define_method(m) { |message| puts "#{m.to_s.upcase}: #{message}" }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
Spec::Runner.configure do |config|
|
17
|
+
|
18
|
+
# Ensures each listed class is cleared from the objectspace and reloaded.
|
19
|
+
# RSpec does not reload classes between tests, so if you're testing class
|
20
|
+
# variables/class instance variables, they accumulate state between tests.
|
21
|
+
# Also a problem if you mock a class method.
|
22
|
+
def reload_classes(*classes)
|
23
|
+
classes.each do |constant|
|
24
|
+
md = constant.to_s.match(/^(.*?)(?:::)?([^:]+)$/)
|
25
|
+
klass = (matched_both = md.captures.size == 2) ? md[2] : md[1]
|
26
|
+
namespace = md[1] if matched_both
|
27
|
+
(namespace.try(:constantize) || Object).send(:remove_const, klass.to_s.to_sym)
|
28
|
+
end
|
29
|
+
classes.each do |constant|
|
30
|
+
load "#{constant.to_s.underscore}.rb"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Provides a class Traceable which records calls in @traceables_callbacks
|
35
|
+
def load_traceable_callback_tester
|
36
|
+
create_schema do |conn|
|
37
|
+
conn.create_table(:traceables, :force => true) do |t|
|
38
|
+
t.string :name
|
39
|
+
t.string :state
|
40
|
+
t.integer :number
|
41
|
+
t.integer :lock_version, :default => 0
|
42
|
+
t.timestamps
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# make sure we record all callback calls regardless of which instance we're in.
|
47
|
+
@traceables_callbacks = callbacks_ref = []
|
48
|
+
c = Class.new(ActiveRecord::Base)
|
49
|
+
Object.const_set(:Traceable, c)
|
50
|
+
c.class_eval do
|
51
|
+
include GraphMediator
|
52
|
+
|
53
|
+
mediate :when_reconciling => :reconcile, :when_cacheing => :cache
|
54
|
+
before_mediation :before
|
55
|
+
|
56
|
+
validates_presence_of :name
|
57
|
+
|
58
|
+
def before; callbacks << :before; end
|
59
|
+
def reconcile; callbacks << :reconcile; end
|
60
|
+
def cache; callbacks << :cache; end
|
61
|
+
define_method(:callbacks) { callbacks_ref }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|