graph_mediator 0.2.1

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.
@@ -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