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,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,4 @@
1
+ class Lodging < ActiveRecord::Base
2
+ belongs_to :reservation, :counter_cache => true
3
+ has_many :party_lodgings
4
+ end
@@ -0,0 +1,4 @@
1
+ class Party < ActiveRecord::Base
2
+ belongs_to :reservation, :counter_cache => true
3
+ has_many :party_lodgings
4
+ end
@@ -0,0 +1,4 @@
1
+ class PartyLodging < ActiveRecord::Base
2
+ belongs_to :party, :counter_cache => true
3
+ belongs_to :lodging, :counter_cache => true
4
+ 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
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format nested
3
+ --diff
@@ -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