state_methods 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/.rvmrc +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +106 -0
- data/Rakefile +1 -0
- data/lib/state_methods.rb +15 -0
- data/lib/state_methods/base.rb +69 -0
- data/lib/state_methods/method_utils.rb +54 -0
- data/lib/state_methods/partition.rb +53 -0
- data/lib/state_methods/version.rb +3 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/state_methods_spec.rb +319 -0
- data/state_methods.gemspec +28 -0
- metadata +147 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 zelig
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
# StateMethods
|
2
|
+
|
3
|
+
|
4
|
+
Suppose you wish to define method which executes code dependent on a state of an instance:
|
5
|
+
|
6
|
+
```ruby
|
7
|
+
def state_method(*args)
|
8
|
+
case state
|
9
|
+
when :a then ...
|
10
|
+
when :b then ...
|
11
|
+
...
|
12
|
+
end
|
13
|
+
```
|
14
|
+
|
15
|
+
Now suppose you would like this generic behaviour (say coming from an included module or subclass) but would like to override it in some states. You need to rewrite the whole function and fall back to super.
|
16
|
+
|
17
|
+
This implementation of state dependence makes it unavoidable that an instance invocation matches the state against all cases especially if there is many of them and falling back to super makes it worse. Apart from this, while explicit, the case statements may be too verbose.
|
18
|
+
|
19
|
+
The [state machine gem](https://github.com/pluginaweek/state_machine) offers such state-driven instance behaviour:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
class Vehicle
|
23
|
+
# ...
|
24
|
+
state_machine :state, :initial => :parked do
|
25
|
+
|
26
|
+
#... describe transitions
|
27
|
+
|
28
|
+
state :parked do
|
29
|
+
def speed
|
30
|
+
0
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
state :idling, :first_gear do
|
35
|
+
def speed
|
36
|
+
10
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
state all - [:parked, :stalled, :idling] do
|
41
|
+
def moving?
|
42
|
+
true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
state :parked, :stalled, :idling do
|
47
|
+
def moving?
|
48
|
+
false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
- Call state-driven behavior that's undefined for the state raises a NoMethodError
|
56
|
+
- does not allow for open ended state values
|
57
|
+
- order-dependent unsafe specification, but 'all' does not override any specific state, behaves as default
|
58
|
+
|
59
|
+
- allow partitions and declarative order-independent specification
|
60
|
+
- open ended values
|
61
|
+
- extension via inheritance
|
62
|
+
|
63
|
+
class Model
|
64
|
+
|
65
|
+
|
66
|
+
source("sm.rb")
|
67
|
+
vehicle = Vehicle.new # => #<Vehicle:0xb7cf4eac @state="parked", @seatbelt_on=false>
|
68
|
+
vehicle.state # => "parked"
|
69
|
+
vehicle.speed # => 0
|
70
|
+
vehicle.moving? # => false
|
71
|
+
|
72
|
+
vehicle.ignite # => true
|
73
|
+
vehicle.state # => "idling"
|
74
|
+
vehicle.speed # => 0
|
75
|
+
vehicle.moving? # => false
|
76
|
+
|
77
|
+
vehicle.shift_up # => true
|
78
|
+
vehicle.state # => "first_gear"
|
79
|
+
vehicle.speed # => 10
|
80
|
+
vehicle.moving? # => true
|
81
|
+
|
82
|
+
## Installation
|
83
|
+
|
84
|
+
Add this line to your application's Gemfile:
|
85
|
+
|
86
|
+
gem 'state_methods'
|
87
|
+
|
88
|
+
And then execute:
|
89
|
+
|
90
|
+
$ bundle
|
91
|
+
|
92
|
+
Or install it yourself as:
|
93
|
+
|
94
|
+
$ gem install state_methods
|
95
|
+
|
96
|
+
## Usage
|
97
|
+
|
98
|
+
TODO: Write usage instructions here
|
99
|
+
|
100
|
+
## Contributing
|
101
|
+
|
102
|
+
1. Fork it
|
103
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
104
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
105
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
106
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "state_methods/version"
|
2
|
+
require "state_methods/base"
|
3
|
+
require "state_methods/method_utils"
|
4
|
+
|
5
|
+
module StateMethods
|
6
|
+
|
7
|
+
# extend ::StateMethods::MethodUtils
|
8
|
+
|
9
|
+
def self.included(base)
|
10
|
+
base.class_eval do
|
11
|
+
include ::StateMethods::Base
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,69 @@
|
|
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
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module StateMethods
|
2
|
+
|
3
|
+
module MethodUtilsClassMethods
|
4
|
+
|
5
|
+
def find_defined(object, name, *keys)
|
6
|
+
if key = keys.shift
|
7
|
+
m = method_name(name, key)
|
8
|
+
if object.respond_to?(m)
|
9
|
+
m
|
10
|
+
else
|
11
|
+
find_defined(object, name, *keys)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def define_class_method(klass, name, val=nil, &block)
|
17
|
+
name = method_name(*name) if name.is_a?(Array)
|
18
|
+
(class << klass; self end).send(:define_method, name, &block || -> { val } )
|
19
|
+
end
|
20
|
+
|
21
|
+
def define_instance_method(klass, name, val=nil, &block)
|
22
|
+
name = method_name(*name) if name.is_a?(Array)
|
23
|
+
klass.send(:define_method, name, &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
def method_name(*keys)
|
27
|
+
:"__#{keys.map(&:to_s).join('__')}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def call(object, keys)
|
31
|
+
object.send(method_name(*keys))
|
32
|
+
rescue NoMethodError
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
# def set(klass, keys, val=nil, &block)
|
37
|
+
# m = method_name(*keys)
|
38
|
+
# (class << klass; self end).class_eval do
|
39
|
+
# klass.instance_eval do
|
40
|
+
# (class << klass; self end).instance_eval do
|
41
|
+
# define_method m do |*args|
|
42
|
+
# val
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
# define_class_method(klass, method_name(*keys)) { val }
|
46
|
+
# end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
module MethodUtils
|
51
|
+
extend MethodUtilsClassMethods
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module StateMethods
|
2
|
+
class Partition
|
3
|
+
def ==(p)
|
4
|
+
index == p.index
|
5
|
+
end
|
6
|
+
|
7
|
+
def initialize(partition = {}, ancestor = nil)
|
8
|
+
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
|
+
@index = process(partition, ancestor ? ancestor.index.dup : {}, ancestor ? nil : [])
|
12
|
+
end
|
13
|
+
|
14
|
+
def ancestors(s)
|
15
|
+
s = s.to_sym
|
16
|
+
# unsafe to prepend s but it catches undeclared state
|
17
|
+
index[s] || (index[:*] && [s, *(index[:*])]) || [s, :all]
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def index
|
23
|
+
@index
|
24
|
+
end
|
25
|
+
|
26
|
+
def process(partition, index, prefix)
|
27
|
+
partition.each do |k, v|
|
28
|
+
k = k.to_sym
|
29
|
+
orig_prefix = index[k]
|
30
|
+
new_prefix = nil
|
31
|
+
if prefix
|
32
|
+
new_prefix = [k, *prefix]
|
33
|
+
if orig_prefix
|
34
|
+
raise ArgumentError, "duplicate state or partition '#{k}'" unless new_prefix == orig_prefix
|
35
|
+
else
|
36
|
+
index[k] = new_prefix
|
37
|
+
end
|
38
|
+
else
|
39
|
+
new_prefix = orig_prefix || [k, :all]
|
40
|
+
end
|
41
|
+
case v
|
42
|
+
when Hash then process(v, index, new_prefix) unless v.empty?
|
43
|
+
when Symbol, String then process({ v => {} }, index, new_prefix)
|
44
|
+
when Array then v.each { |e| process({ e => {} }, index, new_prefix) }
|
45
|
+
else
|
46
|
+
raise ArgumentError, "invalid partition specification for '#{k}' => '#{v.inspect}'"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
index
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
9
|
+
config.run_all_when_everything_filtered = true
|
10
|
+
config.filter_run :focus
|
11
|
+
|
12
|
+
# Run specs in random order to surface order dependencies. If you find an
|
13
|
+
# order dependency and want to debug it, you can fix the order by providing
|
14
|
+
# the seed, which is printed after each run.
|
15
|
+
# --seed 1234
|
16
|
+
config.order = 'random'
|
17
|
+
end
|
@@ -0,0 +1,319 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'state_methods'
|
3
|
+
|
4
|
+
describe "state methods" do
|
5
|
+
|
6
|
+
class TestModel
|
7
|
+
include ::StateMethods
|
8
|
+
def state
|
9
|
+
@state
|
10
|
+
end
|
11
|
+
def state=(a)
|
12
|
+
@state = a
|
13
|
+
end
|
14
|
+
def state!(a)
|
15
|
+
self.state = a
|
16
|
+
self
|
17
|
+
end
|
18
|
+
def other_state
|
19
|
+
@other_state
|
20
|
+
end
|
21
|
+
def other_state=(a)
|
22
|
+
@other_state = a
|
23
|
+
end
|
24
|
+
def other_state!(a)
|
25
|
+
self.other_state = a
|
26
|
+
self
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def model_class
|
31
|
+
@model_class
|
32
|
+
end
|
33
|
+
|
34
|
+
def model_class!
|
35
|
+
@model_class = Class.new(TestModel)
|
36
|
+
end
|
37
|
+
|
38
|
+
def model_subclass
|
39
|
+
@model_subclass
|
40
|
+
end
|
41
|
+
|
42
|
+
def model_subclass!
|
43
|
+
@model_subclass = Class.new(model_class)
|
44
|
+
end
|
45
|
+
|
46
|
+
def model!
|
47
|
+
@model = (model_subclass || model_class).new
|
48
|
+
end
|
49
|
+
|
50
|
+
def model
|
51
|
+
@model
|
52
|
+
end
|
53
|
+
|
54
|
+
before(:each) do
|
55
|
+
model_class!
|
56
|
+
end
|
57
|
+
|
58
|
+
describe ::StateMethods::Partition do
|
59
|
+
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
|
65
|
+
end
|
66
|
+
|
67
|
+
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
|
72
|
+
end
|
73
|
+
|
74
|
+
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'")
|
76
|
+
end
|
77
|
+
|
78
|
+
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'")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "state partition declarations are allowed" do
|
84
|
+
|
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)
|
88
|
+
end
|
89
|
+
|
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])
|
94
|
+
end
|
95
|
+
|
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)
|
99
|
+
end
|
100
|
+
|
101
|
+
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)
|
105
|
+
end
|
106
|
+
|
107
|
+
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)
|
113
|
+
end
|
114
|
+
|
115
|
+
it "and define state_is_a? instance method" do
|
116
|
+
model_class.set_state_partition :state, :partition, :a => :b
|
117
|
+
model!.state!(:b)
|
118
|
+
model.state_is_a?(:b).should be_true
|
119
|
+
model.state_is_a?(:a).should be_true
|
120
|
+
model.state_is_a?(:all).should be_true
|
121
|
+
model.state_is_a?(:c).should be_false
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
describe "state method declarations" do
|
127
|
+
|
128
|
+
before(:each) do
|
129
|
+
model_class.set_state_partition :state, :partition, :default
|
130
|
+
end
|
131
|
+
|
132
|
+
it "take state method, partition as arguments" do
|
133
|
+
model_class.state_method :test, :state, :partition
|
134
|
+
end
|
135
|
+
|
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
|
160
|
+
end
|
161
|
+
|
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
|
166
|
+
end
|
167
|
+
|
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
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
shared_examples_for 'same-class specification' do
|
176
|
+
|
177
|
+
before(:each) do
|
178
|
+
model!.state!(:none)
|
179
|
+
end
|
180
|
+
|
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
|
+
end
|
204
|
+
|
205
|
+
context "with same-class specification" do
|
206
|
+
|
207
|
+
include_examples 'same-class specification'
|
208
|
+
|
209
|
+
end
|
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)
|
219
|
+
end
|
220
|
+
|
221
|
+
include_examples 'same-class specification'
|
222
|
+
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
context "with specification across superclass and subclass" do
|
227
|
+
|
228
|
+
before(:each) do
|
229
|
+
model_subclass!
|
230
|
+
model!.state!(:none)
|
231
|
+
end
|
232
|
+
|
233
|
+
include_examples 'singular specification'
|
234
|
+
|
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
|
+
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
|
+
end
|
284
|
+
|
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'
|
314
|
+
|
315
|
+
end
|
316
|
+
|
317
|
+
end
|
318
|
+
|
319
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'state_methods/version'
|
6
|
+
DESC = %q{declarative state aware method definitions}
|
7
|
+
|
8
|
+
Gem::Specification.new do |gem|
|
9
|
+
gem.name = "state_methods"
|
10
|
+
gem.version = StateMethods::VERSION
|
11
|
+
gem.authors = ["zelig"]
|
12
|
+
gem.email = ["viktor.tron@gmail.com"]
|
13
|
+
gem.description = DESC
|
14
|
+
gem.summary = DESC
|
15
|
+
gem.homepage = "https://github.com/zelig/state_methods.git"
|
16
|
+
|
17
|
+
gem.files = `git ls-files`.split($/)
|
18
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
19
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
20
|
+
gem.require_paths = ["lib"]
|
21
|
+
|
22
|
+
gem.add_development_dependency 'debugger'
|
23
|
+
gem.add_development_dependency 'rspec'
|
24
|
+
gem.add_development_dependency 'rake'
|
25
|
+
gem.add_development_dependency 'activemodel'
|
26
|
+
gem.add_development_dependency 'state_machine'
|
27
|
+
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: state_methods
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- zelig
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-21 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: debugger
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rake
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: activemodel
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: state_machine
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
description: declarative state aware method definitions
|
95
|
+
email:
|
96
|
+
- viktor.tron@gmail.com
|
97
|
+
executables: []
|
98
|
+
extensions: []
|
99
|
+
extra_rdoc_files: []
|
100
|
+
files:
|
101
|
+
- .gitignore
|
102
|
+
- .rvmrc
|
103
|
+
- Gemfile
|
104
|
+
- LICENSE.txt
|
105
|
+
- README.md
|
106
|
+
- Rakefile
|
107
|
+
- lib/state_methods.rb
|
108
|
+
- lib/state_methods/base.rb
|
109
|
+
- lib/state_methods/method_utils.rb
|
110
|
+
- lib/state_methods/partition.rb
|
111
|
+
- lib/state_methods/version.rb
|
112
|
+
- spec/spec_helper.rb
|
113
|
+
- spec/state_methods_spec.rb
|
114
|
+
- state_methods.gemspec
|
115
|
+
homepage: https://github.com/zelig/state_methods.git
|
116
|
+
licenses: []
|
117
|
+
post_install_message:
|
118
|
+
rdoc_options: []
|
119
|
+
require_paths:
|
120
|
+
- lib
|
121
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
122
|
+
none: false
|
123
|
+
requirements:
|
124
|
+
- - ! '>='
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
segments:
|
128
|
+
- 0
|
129
|
+
hash: 3006866352214158636
|
130
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
131
|
+
none: false
|
132
|
+
requirements:
|
133
|
+
- - ! '>='
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
segments:
|
137
|
+
- 0
|
138
|
+
hash: 3006866352214158636
|
139
|
+
requirements: []
|
140
|
+
rubyforge_project:
|
141
|
+
rubygems_version: 1.8.24
|
142
|
+
signing_key:
|
143
|
+
specification_version: 3
|
144
|
+
summary: declarative state aware method definitions
|
145
|
+
test_files:
|
146
|
+
- spec/spec_helper.rb
|
147
|
+
- spec/state_methods_spec.rb
|