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.
- data/lib/state_methods.rb +2 -2
- data/lib/state_methods/factory.rb +50 -0
- data/lib/state_methods/implementations.rb +90 -0
- data/lib/state_methods/implementations/classy.rb +148 -0
- data/lib/state_methods/implementations/functional.rb +21 -0
- data/lib/state_methods/partition.rb +8 -7
- data/lib/state_methods/version.rb +1 -1
- data/spec/state_methods_spec.rb +261 -193
- metadata +8 -5
- data/lib/state_methods/base.rb +0 -69
data/lib/state_methods.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require "state_methods/version"
|
2
|
-
require "state_methods/
|
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::
|
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
|
-
|
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
|
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
|
|
data/spec/state_methods_spec.rb
CHANGED
@@ -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
|
93
|
+
describe "partitions" do
|
59
94
|
it "default partition" do
|
60
|
-
partition =
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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 =
|
69
|
-
|
70
|
-
|
71
|
-
|
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 {
|
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 {
|
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
|
86
|
-
model_class.
|
87
|
-
model_class.
|
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.
|
92
|
-
model_class.
|
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 "
|
97
|
-
model_class.
|
98
|
-
|
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.
|
103
|
-
model_subclass
|
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.
|
109
|
-
model_subclass
|
110
|
-
model_subclass.
|
111
|
-
|
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.
|
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
|
-
|
173
|
+
shared_examples_for 'implementation' do
|
127
174
|
|
128
|
-
|
129
|
-
model_class.set_state_partition :state, :partition, :default
|
130
|
-
end
|
175
|
+
describe "state method declarations" do
|
131
176
|
|
132
|
-
|
133
|
-
model_class.
|
134
|
-
|
177
|
+
# before(:each) do
|
178
|
+
# model_class._state_partition_for :state, :partition => :default
|
179
|
+
# end
|
135
180
|
|
136
|
-
|
137
|
-
|
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 "
|
163
|
-
model_class.test
|
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 "
|
169
|
-
model_class.test
|
170
|
-
|
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
|
-
|
178
|
-
|
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
|
-
|
202
|
+
describe "state method behaviour" do
|
206
203
|
|
207
|
-
|
204
|
+
context "single method and state" do
|
208
205
|
|
209
|
-
|
206
|
+
before(:each) do
|
207
|
+
model_class.state_method :test, :state, :partition => [:a, :b]
|
208
|
+
model!.state!(:none)
|
209
|
+
end
|
210
210
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
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
|
-
|
342
|
+
context "multiple states and state sets" do
|
222
343
|
|
223
|
-
|
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
|
-
|
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
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
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
|
-
|
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
|
-
|
286
|
-
|
287
|
-
|
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
|
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-
|
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/
|
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:
|
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:
|
141
|
+
hash: 2652737864788483720
|
139
142
|
requirements: []
|
140
143
|
rubyforge_project:
|
141
144
|
rubygems_version: 1.8.24
|
data/lib/state_methods/base.rb
DELETED
@@ -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
|