state_methods 0.0.1 → 0.1.0

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