state_methods 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  require "state_methods/version"
2
- require "state_methods/base"
2
+ require "state_methods/implementations"
3
3
  require "state_methods/method_utils"
4
4
 
5
5
  module StateMethods
@@ -8,7 +8,7 @@ module StateMethods
8
8
 
9
9
  def self.included(base)
10
10
  base.class_eval do
11
- include ::StateMethods::Base
11
+ include ::StateMethods::Implementations
12
12
  end
13
13
  end
14
14
 
@@ -0,0 +1,50 @@
1
+ require "state_methods/partition"
2
+
3
+ module StateMethods
4
+
5
+ class Factory
6
+
7
+ def initialize(klass, state_accessor, partition)
8
+ @klass = klass
9
+ @state_accessor = state_accessor
10
+ @partition = partition
11
+ @keys = [state_accessor]
12
+ init
13
+ end
14
+
15
+ def init
16
+ end
17
+
18
+ def check(method_name, force = false)
19
+ end
20
+
21
+ def factory_for(klass)
22
+ self
23
+ end
24
+
25
+ def declare(method_name)
26
+ this = self
27
+ ::StateMethods::MethodUtils.define_class_method(@klass, method_name) do |*states, &block|
28
+ factory = this.factory_for(self)
29
+ states.each do |state|
30
+ factory.set(self, method_name, state, &block)
31
+ end
32
+ end
33
+ check(method_name, force=true)
34
+
35
+ state_accessor = @state_accessor
36
+ ::StateMethods::MethodUtils.define_instance_method(@klass, method_name) do |*args|
37
+ state = send(state_accessor) || :*
38
+ factory = this.factory_for(self.class)
39
+ begin
40
+ factory.get(self, method_name, state, *args)
41
+ rescue Undefined
42
+ nil
43
+ end
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+
50
+ end
@@ -0,0 +1,90 @@
1
+ require "active_support/core_ext/class/attribute_accessors"
2
+ require 'active_support/core_ext/module/attribute_accessors'
3
+ require 'active_support/core_ext/string/inflections.rb'
4
+ require "state_methods/factory"
5
+ require "state_methods/implementations/functional"
6
+ require "state_methods/implementations/classy"
7
+
8
+ module StateMethods
9
+
10
+ class CannotOverrideError < ArgumentError; end
11
+ class UndeclaredState < ArgumentError; end
12
+ class PartitionNotFound < StandardError; end
13
+ class Undefined < StandardError; end
14
+ IMPLEMENTATION = 'Functional'
15
+ # IMPLEMENTATION = 'Classy'
16
+ mattr_accessor :implementation
17
+ @@implementation = IMPLEMENTATION
18
+
19
+ module Implementations
20
+
21
+ def self.included(base)
22
+ base.class_eval do
23
+ class_attribute :_state_partitions
24
+ self._state_partitions = {}
25
+ include InstanceMethods
26
+ end
27
+ base.extend ClassMethods
28
+ end
29
+
30
+ module InstanceMethods
31
+ end
32
+
33
+ module ClassMethods
34
+
35
+ def state_method(method_name, state_accessor, options = {})
36
+ raise ArgumentError, "'#{method_name}' already defined" if respond_to?(method_name)
37
+ factory = _state_method_factory_for(state_accessor, options)
38
+ factory.declare(method_name)
39
+ end
40
+
41
+ def _state_method_factory_for(state_accessor, options = {})
42
+ @_state_method_factories ||= {}
43
+ factory = @_state_method_factories[state_accessor]
44
+ unless factory
45
+ partition = _state_partition_for(state_accessor, options) or raise(PartitionNotFound)
46
+ factory_class = begin
47
+ implementation = options[:implementation] || ::StateMethods.implementation
48
+ "::StateMethods::Implementations::#{implementation}".constantize
49
+ rescue NameError
50
+ raise ArgumentError, "implementation '#{implementation}' not found"
51
+ end
52
+ factory = @_state_method_factories[state_accessor] ||= factory_class.new(self, state_accessor, partition)
53
+ end
54
+ factory
55
+ end
56
+
57
+ def _state_partition_for(state_accessor, options = {})
58
+ orig = _state_partitions[state_accessor]
59
+ extension = options[:extend]
60
+ spec = options[:partition]
61
+ if orig
62
+ raise ArgumentError, "partition for '#{state_accessor}' already defined" if spec
63
+ else
64
+ raise ArgumentError, "partition for '#{state_accessor}' not defined" if extension
65
+ end
66
+ spec ||= extension
67
+ return orig unless spec
68
+ raise ArgumentError, "partition for '#{state_accessor}' should be set before state_method calls within a class" if
69
+ @_state_method_factories && @_state_method_factories[state_accessor]
70
+ begin
71
+ new_partition = ::StateMethods::Partition.new(spec, orig)
72
+ ::StateMethods::MethodUtils.define_instance_method(self, :"#{state_accessor}_is_a?") do |s|
73
+ current_state = send(state_accessor)
74
+ current_state == s or
75
+ new_partition.ancestors(current_state||:*).include?(s)
76
+ end
77
+ self._state_partitions = _state_partitions.merge(state_accessor => new_partition)
78
+ new_partition
79
+ rescue ::StateMethods::DuplicateStateError => exc
80
+ if orig
81
+ raise CannotOverrideError, exc.to_s
82
+ else
83
+ raise exc
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,148 @@
1
+ require 'active_support/core_ext/class/attribute.rb'
2
+
3
+ module StateMethods
4
+ module Implementations
5
+ class Proxy
6
+ class_attribute :_keys
7
+
8
+ def initialize(base)
9
+ @base = base
10
+ end
11
+
12
+ def base_proxy_for(state)
13
+ @base.class._state_method_factory_for(*_keys).class_for(state).new(@base)
14
+ end
15
+
16
+ end
17
+
18
+ class Classy < StateMethods::Factory
19
+
20
+ def init
21
+ proxy_const = [:proxy, @state_accessor].join('_').camelize
22
+ @proxy = if Object.const_defined?(proxy_const)
23
+ proxy_const.constantize
24
+ else
25
+ keys = @keys
26
+ make!(proxy_const, Proxy) do
27
+ self._keys = keys
28
+ end
29
+ end
30
+
31
+ @superclass = @klass.superclass
32
+ if @superclass.respond_to?(:_state_method_factory_for)
33
+ begin
34
+ @super_proxy = @superclass._state_method_factory_for(*@keys)
35
+ rescue PartitionNotFound
36
+ end
37
+ end
38
+ @partition.index.each do |state, ancestors|
39
+ superstate = ancestors[1]
40
+ make(state, superstate)
41
+ end
42
+ end
43
+
44
+ def check(method_name, force = false)
45
+ ok = begin
46
+ Proxy.new(nil).send(method_name)
47
+ false
48
+ rescue NoMethodError
49
+ true
50
+ rescue
51
+ false
52
+ end
53
+ unless ok
54
+ if force
55
+ @proxy.send(:undef_method, method_name)
56
+ else
57
+ raise ArgumentError, "method '#{method_name}' won't work"
58
+ end
59
+ end
60
+ set(@klass, method_name, :all) { raise Undefined }
61
+ end
62
+
63
+ def factory_for(klass)
64
+ if klass == @klass
65
+ self
66
+ else
67
+ klass._state_method_factory_for(*@keys)
68
+ end
69
+ end
70
+
71
+ def set(klass, method_name, state, &block)
72
+ # klass.should == @klass
73
+ klass = begin
74
+ class_for(state)
75
+ rescue NameError
76
+ raise UndeclaredState
77
+ end
78
+ ::StateMethods::MethodUtils.define_instance_method(klass, method_name) do |*args|
79
+ @base.instance_exec(*args, &block)
80
+ end
81
+ end
82
+
83
+ def get(instance, method_name, state, *args)
84
+ # instance.class.should == @klass
85
+ klass = nil
86
+ [state, :*, :all].find do |s|
87
+ begin klass = class_for(s)
88
+ rescue NameError
89
+ end
90
+ end
91
+ if klass
92
+ base = klass.new(instance)
93
+ # if base.respond_to?(method_name)
94
+ base.send(method_name, *args)
95
+ # else
96
+ # raise Undefined
97
+ # end
98
+ end
99
+ end
100
+
101
+ def state_superclass_for(state)
102
+ begin
103
+ @super_proxy && @super_proxy.class_for(state)
104
+ rescue NameError
105
+ end
106
+ end
107
+
108
+ def class_for(state)
109
+ const_for(state).constantize
110
+ end
111
+
112
+ def const_for(state)
113
+ [@klass, @state_accessor, state].join('_').sub('*','star').camelize
114
+ end
115
+
116
+ def make!(const, state_superclass, &block)
117
+ new_class = Class.new(state_superclass,&block)
118
+ Object.send(:remove_const, const) if Object.const_defined?(const)
119
+ Object.const_set(const, new_class, true)
120
+ new_class
121
+ end
122
+
123
+ def make(state, superstate)
124
+ const = const_for(state)
125
+ unless Object.const_defined?(const)
126
+ if state_superclass = state_superclass_for(state)
127
+ make!(const, state_superclass)
128
+ elsif superstate
129
+ make!(const, @proxy) do
130
+ define_method :method_missing do |method_name, *args, &block|
131
+ klass = base_proxy_for(superstate)
132
+ klass.send(method_name, *args, &block)
133
+ end
134
+ define_method :respond_to_missing? do |name, include_private = false|
135
+ name == method_name or super
136
+ end
137
+ end
138
+ else
139
+ make!(const, @proxy)
140
+ end
141
+ end
142
+ const.constantize
143
+ end
144
+
145
+ end
146
+
147
+ end
148
+ end
@@ -0,0 +1,21 @@
1
+ module StateMethods
2
+ module Implementations
3
+ class Functional < StateMethods::Factory
4
+
5
+ def set(klass, method_name, state, &block)
6
+ ::StateMethods::MethodUtils.define_instance_method(klass, [method_name, state], &block)
7
+ end
8
+
9
+ def get(instance, method_name, state, *args)
10
+ partition = instance.class._state_partition_for(@state_accessor)
11
+ keys = partition.ancestors(state)
12
+ if m = ::StateMethods::MethodUtils.find_defined(instance, method_name, *keys)
13
+ instance.send(m, *args)
14
+ else
15
+ raise Undefined
16
+ end
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -1,4 +1,6 @@
1
1
  module StateMethods
2
+ class DuplicateStateError < ArgumentError; end
3
+
2
4
  class Partition
3
5
  def ==(p)
4
6
  index == p.index
@@ -6,8 +8,7 @@ module StateMethods
6
8
 
7
9
  def initialize(partition = {}, ancestor = nil)
8
10
  partition = {} if partition == [] || partition == :default
9
- raise ArgumentError, "partition must be a Hash" unless partition.is_a?(Hash)
10
- partition = { :all => partition } unless partition[:all] || partition['all']
11
+ partition = { :all => partition } unless partition.is_a?(Hash) && (ancestor || partition[:all] || partition['all'])
11
12
  @index = process(partition, ancestor ? ancestor.index.dup : {}, ancestor ? nil : [])
12
13
  end
13
14
 
@@ -17,12 +18,12 @@ module StateMethods
17
18
  index[s] || (index[:*] && [s, *(index[:*])]) || [s, :all]
18
19
  end
19
20
 
20
- protected
21
-
22
21
  def index
23
22
  @index
24
23
  end
25
24
 
25
+ protected
26
+
26
27
  def process(partition, index, prefix)
27
28
  partition.each do |k, v|
28
29
  k = k.to_sym
@@ -31,13 +32,12 @@ module StateMethods
31
32
  if prefix
32
33
  new_prefix = [k, *prefix]
33
34
  if orig_prefix
34
- raise ArgumentError, "duplicate state or partition '#{k}'" unless new_prefix == orig_prefix
35
- else
36
- index[k] = new_prefix
35
+ raise DuplicateStateError, k unless new_prefix == orig_prefix
37
36
  end
38
37
  else
39
38
  new_prefix = orig_prefix || [k, :all]
40
39
  end
40
+ index[k] = new_prefix
41
41
  case v
42
42
  when Hash then process(v, index, new_prefix) unless v.empty?
43
43
  when Symbol, String then process({ v => {} }, index, new_prefix)
@@ -46,6 +46,7 @@ module StateMethods
46
46
  raise ArgumentError, "invalid partition specification for '#{k}' => '#{v.inspect}'"
47
47
  end
48
48
  end
49
+ # puts index
49
50
  index
50
51
  end
51
52
 
@@ -1,3 +1,3 @@
1
1
  module StateMethods
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -1,8 +1,35 @@
1
1
  require 'spec_helper'
2
2
  require 'state_methods'
3
3
 
4
+ class Object
5
+ @_consts_to_remove = []
6
+ class << self
7
+ alias :orig_const_set :const_set
8
+ end
9
+ def self.const_set(const, new_class, remove = false)
10
+ @_consts_to_remove << const if remove
11
+ orig_const_set(const, new_class)
12
+ end
13
+ def self.remove_consts_to_remove!
14
+ @_consts_to_remove.each { |c| remove_const(c) }
15
+ @_consts_to_remove = []
16
+ end
17
+ end
18
+
4
19
  describe "state methods" do
5
20
 
21
+ after(:each) do
22
+ Object.remove_consts_to_remove!
23
+ end
24
+
25
+ def state_partition!(*spec)
26
+ ::StateMethods::Partition.new(*spec)
27
+ end
28
+
29
+ def state_partition
30
+ @state_partition
31
+ end
32
+
6
33
  class TestModel
7
34
  include ::StateMethods
8
35
  def state
@@ -33,6 +60,10 @@ describe "state methods" do
33
60
 
34
61
  def model_class!
35
62
  @model_class = Class.new(TestModel)
63
+ const = 'TestModel1'
64
+ Object.send(:remove_const, const) if Object.const_defined?(const)
65
+ Object.const_set(const, @model_class)
66
+ @model_class
36
67
  end
37
68
 
38
69
  def model_subclass
@@ -41,6 +72,10 @@ describe "state methods" do
41
72
 
42
73
  def model_subclass!
43
74
  @model_subclass = Class.new(model_class)
75
+ const = 'TestModel2'
76
+ Object.send(:remove_const, const) if Object.const_defined?(const)
77
+ Object.const_set(const, @model_subclass)
78
+ @model_subclass
44
79
  end
45
80
 
46
81
  def model!
@@ -55,265 +90,298 @@ describe "state methods" do
55
90
  model_class!
56
91
  end
57
92
 
58
- describe ::StateMethods::Partition do
93
+ describe "partitions" do
59
94
  it "default partition" do
60
- partition = model_class.new_state_partition(:default)
61
- model_class.new_state_partition(:all => []).should == partition
62
- model_class.new_state_partition({}).should == partition
63
- model_class.new_state_partition().should == partition
64
- model_class.new_state_partition([]).should == partition
95
+ partition = state_partition!(:default)
96
+ state_partition!(:all => []).should == partition
97
+ state_partition!({}).should == partition
98
+ state_partition!().should == partition
99
+ state_partition!([]).should == partition
65
100
  end
66
101
 
67
102
  it "partition can mix string/symbol" do
68
- partition = model_class.new_state_partition(:a => :b)
69
- model_class.new_state_partition(:a => 'b').should == partition
70
- model_class.new_state_partition('a' => 'b').should == partition
71
- model_class.new_state_partition('a' => :b).should == partition
103
+ partition = state_partition!(:a => :b)
104
+ state_partition!(:a => 'b').should == partition
105
+ state_partition!('a' => 'b').should == partition
106
+ state_partition!('a' => :b).should == partition
72
107
  end
73
108
 
74
109
  it "partition does not allow duplicate states" do
75
- lambda { model_class.new_state_partition(:a => 'a') }.should raise_error(ArgumentError, "duplicate state or partition 'a'")
110
+ lambda { state_partition!(:a => 'a') }.should raise_error(::StateMethods::DuplicateStateError, "a")
76
111
  end
77
112
 
78
113
  it "partition does not invalid state specification" do
79
- lambda { model_class.new_state_partition(:a => nil) }.should raise_error(ArgumentError, "invalid partition specification for 'a' => 'nil'")
114
+ lambda { state_partition!(:a => nil) }.should raise_error(ArgumentError, "invalid partition specification for 'a' => 'nil'")
80
115
  end
81
116
  end
82
117
 
83
118
  describe "state partition declarations are allowed" do
84
119
 
85
- it "with state, partition name and partition" do
86
- model_class.set_state_partition :state, :partition, :a => :b
87
- model_class.get_state_partition(:state, :partition).should == model_class.new_state_partition(:a => :b)
120
+ it "with state and no option they retrieve correct partition" do
121
+ model_class._state_partition_for(:state).should be_nil
122
+ model_class._state_partition_for(:state, :partition => { :a => :b })
123
+ model_class._state_partition_for(:state).should == state_partition!(:a => :b)
124
+ end
125
+
126
+ it "with state, partition option and retrieve correct partition" do
127
+ model_class._state_partition_for(:state, :partition => { :a => :b }).should == state_partition!(:a => :b)
128
+ end
129
+
130
+ it "with partition option only once for a state accessor" do
131
+ model_class._state_partition_for :state, :partition => { :a => :b }
132
+ lambda { model_class._state_partition_for :state, :partition => { :a => :b } }.should raise_error(ArgumentError, "partition for 'state' already defined")
88
133
  end
89
134
 
90
- it "if they extend earlier declarations" do
91
- model_class.set_state_partition :state, :partition, :a => :b
92
- model_class.set_state_partition :state, :partition, :a => :c
93
- model_class.get_state_partition(:state, :partition).should == model_class.new_state_partition(:a => [:b, :c])
135
+ it "with extend option only if they extend earlier declarations" do
136
+ model_class._state_partition_for :state, :partition => { :a => :b }
137
+ lambda { model_class._state_partition_for :state, :extend => { :c => :a } }.should raise_error(::StateMethods::CannotOverrideError, "a")
94
138
  end
95
139
 
96
- it "unless they contradict earlier declarations" do
97
- model_class.set_state_partition :state, :partition, :a => :b
98
- lambda { model_class.set_state_partition :state, :partition, :c => :a }.should raise_error(::StateMethods::CannotOverrideError)
140
+ it "with extend option only if partition is already defined for the state accessor" do
141
+ lambda { model_class._state_partition_for :state, :extend => { :a => :b } }.should raise_error(ArgumentError, "partition for 'state' not defined")
142
+ end
143
+
144
+ it "with extend option after state accessor is defined" do
145
+ model_class._state_partition_for :state, :partition => { :a => :b }
146
+ model_class._state_partition_for(:state, :extend => { :a => :c }).should == state_partition!(:a => [:b, :c])
99
147
  end
100
148
 
101
149
  it "and are inherited" do
102
- model_class.set_state_partition :state, :partition, :a => :b
103
- model_subclass = Class.new(model_class)
104
- model_subclass.get_state_partition(:state, :partition).should == model_class.new_state_partition(:a => :b)
150
+ model_class._state_partition_for :state, :partition => { :a => :b }
151
+ model_subclass!._state_partition_for(:state).should == state_partition!(:a => :b)
105
152
  end
106
153
 
107
154
  it "and are extensible in subclass, not overwritten in superclass" do
108
- model_class.set_state_partition :state, :partition, :a => :b
109
- model_subclass = Class.new(model_class)
110
- model_subclass.set_state_partition :state, :partition, :a => :c
111
- model_subclass.get_state_partition(:state, :partition).should == model_class.new_state_partition(:a => [:b, :c])
112
- model_class.get_state_partition(:state, :partition).should == model_class.new_state_partition(:a => :b)
155
+ model_class._state_partition_for :state, :partition => { :a => :b }
156
+ model_subclass!._state_partition_for :state, :extend => { :a => :c }
157
+ model_subclass._state_partition_for(:state).should == state_partition!(:a => [:b, :c])
158
+ model_class._state_partition_for(:state).should == state_partition!(:a => :b)
113
159
  end
114
160
 
115
161
  it "and define state_is_a? instance method" do
116
- model_class.set_state_partition :state, :partition, :a => :b
162
+ model_class._state_partition_for :state, :partition => { :a => [:b, :c] }
117
163
  model!.state!(:b)
118
164
  model.state_is_a?(:b).should be_true
119
165
  model.state_is_a?(:a).should be_true
120
166
  model.state_is_a?(:all).should be_true
121
167
  model.state_is_a?(:c).should be_false
168
+ model.state_is_a?(:none).should be_false
122
169
  end
123
170
 
124
171
  end
125
172
 
126
- describe "state method declarations" do
173
+ shared_examples_for 'implementation' do
127
174
 
128
- before(:each) do
129
- model_class.set_state_partition :state, :partition, :default
130
- end
175
+ describe "state method declarations" do
131
176
 
132
- it "take state method, partition as arguments" do
133
- model_class.state_method :test, :state, :partition
134
- end
177
+ # before(:each) do
178
+ # model_class._state_partition_for :state, :partition => :default
179
+ # end
135
180
 
136
- it "raise PartitionNotFound error if partition is not set up" do
137
- lambda { model_class.state_method :test, :state, :nopartition }.should raise_error(::StateMethods::PartitionNotFound)
138
- end
139
-
140
- it "should define class and instance method" do
141
- model_class.state_method :test, :state, :partition
142
- model_class.should respond_to(:test)
143
- model!.should respond_to(:test)
144
- end
145
-
146
- end
147
-
148
- describe "state method behaviour" do
149
-
150
- before(:each) do
151
- model_class.set_state_partition :state, :partition, :default
152
- model_class.state_method :test, :state, :partition
153
- model!.state!(:none)
154
- end
155
-
156
- shared_examples_for 'singular specification' do
157
- it "#test -> nil if none set if state=nil" do
158
- model.state!(nil).state.should be_nil
159
- model.test.should be_nil
181
+ it "take state method, partition option as arguments" do
182
+ model_class.state_method :test, :state, :partition => :default
160
183
  end
161
184
 
162
- it "#test -> 1 if all set to 1 if state=nil" do
163
- model_class.test(:all) { 1 }
164
- model.state!(nil).state.should be_nil
165
- model.test.should == 1
185
+ it "raise PartitionNotFound error if partition is not set up" do
186
+ lambda { model_class.state_method :test, :state }.should raise_error(::StateMethods::PartitionNotFound)
166
187
  end
167
188
 
168
- it "#test -> 1 if all set to 1 if state=a" do
169
- model_class.test(:all) { 1 }
170
- model.state!(:a).state.should == :a
171
- model.test.should == 1
189
+ it "raise PartitionNotFound error if partition is not set up" do
190
+ model_class.state_method :test, :state, :partition => :default
191
+ lambda { model_class.state_method :test, :state }.should raise_error(ArgumentError, "'test' already defined")
172
192
  end
173
- end
174
-
175
- shared_examples_for 'same-class specification' do
176
193
 
177
- before(:each) do
178
- model!.state!(:none)
194
+ it "should define class and instance method" do
195
+ model_class.state_method :test, :state, :partition => :default
196
+ model_class.should respond_to(:test)
197
+ model!.should respond_to(:test)
179
198
  end
180
199
 
181
- include_examples 'singular specification'
182
-
183
- it "#test -> 1 if all set to 0 and a set to 1 if state=a" do
184
- model_class.test(:all) { 0 }
185
- model_class.test(:a) { 1 }
186
- model.state!(:a).state.should == :a
187
- model.test.should == 1
188
- end
189
-
190
- it "#test -> 0 if all set to 0 and a set to 1 if state=b" do
191
- model_class.test(:all) { 0 }
192
- model_class.test(:a) { 1 }
193
- model.state!(:b).state.should == :b
194
- model.test.should == 0
195
- end
196
-
197
- it "#test -> 1 if a set to 1 and THEN all set to 0 if state=a" do
198
- model_class.test(:a) { 1 }
199
- model_class.test(:all) { 0 }
200
- model.state!(:a).state.should == :a
201
- model.test.should == 1
202
- end
203
200
  end
204
201
 
205
- context "with same-class specification" do
202
+ describe "state method behaviour" do
206
203
 
207
- include_examples 'same-class specification'
204
+ context "single method and state" do
208
205
 
209
- end
206
+ before(:each) do
207
+ model_class.state_method :test, :state, :partition => [:a, :b]
208
+ model!.state!(:none)
209
+ end
210
210
 
211
- context "with multiple state_methods with multiple specification" do
212
-
213
- before(:each) do
214
- model_class.set_state_partition :other_state, :partition, :default
215
- model_class.state_method :other_test, :other_state, :partition
216
- model_class.other_test(:all) { 2 }
217
- model!.other_state!(:a)
218
- model!.state!(:none)
211
+ shared_examples_for 'singular specification' do
212
+ it "#test -> nil if none set if state=nil" do
213
+ model.state!(nil).state.should be_nil
214
+ model.test.should be_nil
215
+ end
216
+
217
+ it "#test -> 1 if all set to 1 if state=nil" do
218
+ model_class.test(:all) { 1 }
219
+ model.state!(nil).state.should be_nil
220
+ model.test.should == 1
221
+ end
222
+
223
+ it "#test -> 1 if all set to 1 if state=a" do
224
+ model_class.test(:all) { 1 }
225
+ model.state!(:a).state.should == :a
226
+ model.test.should == 1
227
+ end
228
+ end
229
+
230
+ shared_examples_for 'same-class specification' do
231
+
232
+ before(:each) do
233
+ model!.state!(:none)
234
+ end
235
+
236
+ include_examples 'singular specification'
237
+
238
+ it "#test -> 1 if all set to 0 and a set to 1 if state=a" do
239
+ model_class.test(:all) { 0 }
240
+ model_class.test(:a) { 1 }
241
+ model.state!(:a).state.should == :a
242
+ model.test.should == 1
243
+ end
244
+
245
+ it "#test -> 0 if all set to 0 and a set to 1 if state=b" do
246
+ model_class.test(:all) { 0 }
247
+ model_class.test(:a) { 1 }
248
+ model.state!(:b).state.should == :b
249
+ model.test.should == 0
250
+ end
251
+
252
+ it "#test -> 1 if a set to 1 and THEN all set to 0 if state=a" do
253
+ model_class.test(:a) { 1 }
254
+ model_class.test(:all) { 0 }
255
+ model.state!(:a).state.should == :a
256
+ model.test.should == 1
257
+ end
258
+ end
259
+
260
+ context "with same-class specification" do
261
+
262
+ include_examples 'same-class specification'
263
+
264
+ end
265
+
266
+ context "with multiple state_methods with multiple specification" do
267
+
268
+ before(:each) do
269
+ model_class.state_method :other_test, :other_state, :partition => :default
270
+ model_class.other_test(:all) { 2 }
271
+ model!.other_state!(:a)
272
+ model!.state!(:none)
273
+ end
274
+
275
+ include_examples 'same-class specification'
276
+
277
+ end
278
+
279
+
280
+ context "with specification across superclass and subclass" do
281
+
282
+ before(:each) do
283
+ model_subclass!
284
+ model!.state!(:none)
285
+ end
286
+
287
+ include_examples 'singular specification'
288
+
289
+ it "#test -> 1 if all set to 0 and a set to 1 (in subclass) if state=a" do
290
+ model_class.test(:all) { 0 }
291
+ model_subclass.test(:a) { 1 }
292
+ model.state!(:a).state.should == :a
293
+ model.test.should == 1
294
+ end
295
+
296
+ it "#test -> 0 if all set to 0 and a set to 1 (in subclass) if state=b" do
297
+ model_class.test(:all) { 0 }
298
+ model_subclass.test(:a) { 1 }
299
+ model.state!(:b).state.should == :b
300
+ model.test.should == 0
301
+ end
302
+
303
+ it "#test -> 1 if a set to 1 and THEN all set to 0 (in subclass) if state=a" do
304
+ model_class.test(:a) { 1 }
305
+ model_subclass.test(:all) { 0 }
306
+ model.state!(:a).state.should == :a
307
+ model.test.should == 1
308
+ end
309
+
310
+ it "#test -> 1 if all set to 0 (in subclass) and a set to 1 if state=a" do
311
+ model_subclass.test(:all) { 0 }
312
+ model_class.test(:a) { 1 }
313
+ model.state!(:a).state.should == :a
314
+ model.test.should == 1
315
+ end
316
+
317
+ it "#test -> 0 if all set to 0 (in subclass) and a set to 1 if state=b" do
318
+ model_subclass.test(:all) { 0 }
319
+ model_class.test(:a) { 1 }
320
+ model.state!(:b).state.should == :b
321
+ model.test.should == 0
322
+ end
323
+
324
+ it "#test -> 1 if a set to 1 (in subclass) and THEN all set to 0 if state=a" do
325
+ model_subclass.test(:a) { 1 }
326
+ model_class.test(:all) { 0 }
327
+ model.state!(:a).state.should == :a
328
+ model.test.should == 1
329
+ end
330
+
331
+ it "#test -> 0 if a set to 1 (in subclass) and all set to 0 if state=a in superclass" do
332
+ model_subclass.test(:a) { 1 }
333
+ model_class.test(:all) { 0 }
334
+ m = model_class.new.state!(:a)
335
+ m.state.should == :a
336
+ m.test.should == 0
337
+ end
338
+
339
+ end
219
340
  end
220
341
 
221
- include_examples 'same-class specification'
342
+ context "multiple states and state sets" do
222
343
 
223
- end
344
+ before(:each) do
345
+ model_class.state_method :test, :state, :partition => { :ab => [:a, :b] }
346
+ model_subclass!._state_partition_for :state, :extend => { :cd => [:c, :d], :ab => { :b => [:b0, :b1] } }
347
+ model!.state!(:none)
348
+ end
224
349
 
350
+ it "inherit suprestate spec from superclass even if state is only explicit in subclass partition" do
351
+ model_class.test(:b) { 1 }
352
+ model_subclass.test(:ab) { 0 }
353
+ model.state!(:b0).state.should == :b0
354
+ model.test.should == 1
355
+ end
225
356
 
226
- context "with specification across superclass and subclass" do
357
+ it "specification block is executed in model instance scope" do
358
+ model_class.test(:all) { state }
359
+ model.state!(:a).test.should == :a
360
+ model.state!(:c).state.should == :c
361
+ model.test.should == :c
362
+ end
227
363
 
228
- before(:each) do
229
- model_subclass!
230
- model!.state!(:none)
231
- end
364
+ it "specification block arguments are passed correctly" do
365
+ model_class.test(:a) { |first, second, *rest| "state: #{state}, first: #{first}, second: #{second}, rest: #{rest.join(', ')}" }
366
+ model.state!(:a).state.should == :a
367
+ model.test(1, 2, 3, 4).should == "state: a, first: 1, second: 2, rest: 3, 4"
368
+ end
232
369
 
233
- include_examples 'singular specification'
370
+ include_examples 'singular specification'
234
371
 
235
- it "#test -> 1 if all set to 0 and a set to 1 (in subclass) if state=a" do
236
- model_class.test(:all) { 0 }
237
- model_subclass.test(:a) { 1 }
238
- model.state!(:a).state.should == :a
239
- model.test.should == 1
240
372
  end
241
-
242
- it "#test -> 0 if all set to 0 and a set to 1 (in subclass) if state=b" do
243
- model_class.test(:all) { 0 }
244
- model_subclass.test(:a) { 1 }
245
- model.state!(:b).state.should == :b
246
- model.test.should == 0
247
- end
248
-
249
- it "#test -> 1 if a set to 1 and THEN all set to 0 (in subclass) if state=a" do
250
- model_class.test(:a) { 1 }
251
- model_subclass.test(:all) { 0 }
252
- model.state!(:a).state.should == :a
253
- model.test.should == 1
254
- end
255
-
256
- it "#test -> 1 if all set to 0 (in subclass) and a set to 1 if state=a" do
257
- model_subclass.test(:all) { 0 }
258
- model_class.test(:a) { 1 }
259
- model.state!(:a).state.should == :a
260
- model.test.should == 1
261
- end
262
-
263
- it "#test -> 0 if all set to 0 (in subclass) and a set to 1 if state=b" do
264
- model_subclass.test(:all) { 0 }
265
- model_class.test(:a) { 1 }
266
- model.state!(:b).state.should == :b
267
- model.test.should == 0
268
- end
269
-
270
- it "#test -> 1 if a set to 1 (in subclass) and THEN all set to 0 if state=a" do
271
- model_subclass.test(:a) { 1 }
272
- model_class.test(:all) { 0 }
273
- model.state!(:a).state.should == :a
274
- model.test.should == 1
275
- end
276
-
277
- it "#test -> 0 if a set to 1 (in subclass) and all set to 0 if state=a in superclass" do
278
- model_subclass.test(:a) { 1 }
279
- model_class.test(:all) { 0 }
280
- model_class.new.state!(:a).test.should == 0
281
- end
282
-
283
373
  end
374
+ end
284
375
 
285
- context "multiple states and state sets" do
286
-
287
- before(:each) do
288
- model_class.set_state_partition :state, :partition, { :ab => [:a, :b] }
289
- model_class.state_method :test, :state, :partition
290
- model_subclass!.set_state_partition :state, :partition, { :cd => [:c, :d], :ab => { :b => [:b0, :b1] } }
291
- model!.state!(:none)
292
- end
293
-
294
- it "inherit spec from superclass even if state is only explicit in subclass partition" do
295
- model_class.test(:c) { 1 }
296
- model.state!(:c).state.should == :c
297
- model.test.should == 1
298
- end
299
-
300
- it "specification block is executed in model instance scope" do
301
- model_class.test(:all) { state }
302
- model.state!(:a).test.should == :a
303
- model.state!(:c).state.should == :c
304
- model.test.should == :c
305
- end
306
-
307
- it "specification block arguments are passed correctly" do
308
- model_class.test(:a) { |first, second, *rest| "state: #{state}, first: #{first}, second: #{second}, rest: #{rest.join(', ')}" }
309
- model.state!(:a).state.should == :a
310
- model.test(1, 2, 3, 4).should == "state: a, first: 1, second: 2, rest: 3, 4"
311
- end
312
-
313
- include_examples 'singular specification'
376
+ context "functional implementation" do
377
+ include_examples 'implementation'
378
+ end
314
379
 
380
+ context "classy implementation" do
381
+ before(:all) do
382
+ ::StateMethods.implementation = 'Classy'
315
383
  end
316
-
384
+ include_examples 'implementation'
317
385
  end
318
386
 
319
387
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: state_methods
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-21 00:00:00.000000000 Z
12
+ date: 2013-01-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: debugger
@@ -105,7 +105,10 @@ files:
105
105
  - README.md
106
106
  - Rakefile
107
107
  - lib/state_methods.rb
108
- - lib/state_methods/base.rb
108
+ - lib/state_methods/factory.rb
109
+ - lib/state_methods/implementations.rb
110
+ - lib/state_methods/implementations/classy.rb
111
+ - lib/state_methods/implementations/functional.rb
109
112
  - lib/state_methods/method_utils.rb
110
113
  - lib/state_methods/partition.rb
111
114
  - lib/state_methods/version.rb
@@ -126,7 +129,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
126
129
  version: '0'
127
130
  segments:
128
131
  - 0
129
- hash: 3006866352214158636
132
+ hash: 2652737864788483720
130
133
  required_rubygems_version: !ruby/object:Gem::Requirement
131
134
  none: false
132
135
  requirements:
@@ -135,7 +138,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
135
138
  version: '0'
136
139
  segments:
137
140
  - 0
138
- hash: 3006866352214158636
141
+ hash: 2652737864788483720
139
142
  requirements: []
140
143
  rubyforge_project:
141
144
  rubygems_version: 1.8.24
@@ -1,69 +0,0 @@
1
- require "state_methods/partition"
2
- require "active_support/core_ext/class/attribute_accessors"
3
-
4
- module StateMethods
5
-
6
- class CannotOverrideError < StandardError; end
7
- class PartitionNotFound < StandardError; end
8
-
9
- module Base
10
-
11
-
12
- def self.included(base)
13
- base.extend StateMethodsClassMethods
14
- base.class_eval do
15
- include StateMethodsInstanceMethods
16
- end
17
- end
18
-
19
- module StateMethodsInstanceMethods
20
- end
21
-
22
- module StateMethodsClassMethods
23
-
24
- def new_state_partition(*spec)
25
- ::StateMethods::Partition.new(*spec)
26
- end
27
-
28
- def set_state_partition(state_accessor, partition_name, spec)
29
- orig = get_state_partition(state_accessor, partition_name)
30
- begin
31
- new_partition = new_state_partition(spec, orig)
32
- ::StateMethods::MethodUtils.define_class_method(self, [:partition, state_accessor, partition_name], new_partition)
33
- ::StateMethods::MethodUtils.define_instance_method(self, :"#{state_accessor}_is_a?") do |s|
34
- current_state = send(state_accessor)
35
- current_state == s or
36
- self.class.get_state_partition(state_accessor, partition_name).ancestors(current_state||:*).include?(s)
37
- end
38
- rescue ArgumentError
39
- raise CannotOverrideError
40
- end
41
- end
42
-
43
- def get_state_partition(*args)
44
- ::StateMethods::MethodUtils.call(self, [:partition, *args])
45
- end
46
-
47
- def state_method(method_name, state_accessor, partition_name)
48
- get_state_partition(state_accessor, partition_name) or raise PartitionNotFound
49
-
50
- ::StateMethods::MethodUtils.define_class_method(self, method_name) do |*states, &block|
51
- states.each do |s|
52
- ::StateMethods::MethodUtils.define_instance_method(self, [method_name, s], &block)
53
- end
54
- end
55
-
56
- ::StateMethods::MethodUtils.define_instance_method(self, method_name) do |*args|
57
- keys = self.class.get_state_partition(state_accessor, partition_name).ancestors(send(state_accessor)||:*)
58
- if m = ::StateMethods::MethodUtils.find_defined(self, method_name, *keys)
59
- send(m, *args)
60
- end
61
- end
62
-
63
- end
64
-
65
-
66
- end
67
-
68
- end
69
- end