state-fu 0.11.1 → 0.12.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/binding.rb +11 -7
- data/lib/interface.rb +12 -14
- data/lib/machine.rb +25 -17
- data/lib/method_factory.rb +21 -20
- data/lib/persistence.rb +9 -9
- data/lib/support/logger.rb +18 -27
- data/spec/features/machine_alias_spec.rb +46 -0
- data/spec/features/singleton_machine_spec.rb +17 -4
- data/spec/features/when_methods_are_defined_spec.rb +114 -0
- data/spec/state_fu_spec.rb +22 -5
- data/spec/units/binding_spec.rb +6 -8
- data/spec/units/machine_spec.rb +5 -8
- metadata +6 -2
data/lib/binding.rb
CHANGED
@@ -3,13 +3,12 @@ module StateFu
|
|
3
3
|
|
4
4
|
attr_reader :object, :machine, :method_name, :field_name, :persister, :transitions, :options, :target
|
5
5
|
|
6
|
-
|
7
6
|
# the constructor should not be called manually; a binding is
|
8
7
|
# returned when an instance of a class with a StateFu::Machine
|
9
8
|
# calls:
|
10
9
|
#
|
11
10
|
# instance.#state_fu (for the default machine which is called :state_fu),
|
12
|
-
# instance.#state_fu(
|
11
|
+
# instance.#state_fu(:<machine_name>) ,or
|
13
12
|
# instance.#<machine_name>
|
14
13
|
#
|
15
14
|
def initialize( machine, object, method_name, options={} )
|
@@ -18,13 +17,18 @@ module StateFu
|
|
18
17
|
@method_name = method_name
|
19
18
|
@transitions = []
|
20
19
|
@options = options.symbolize_keys!
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
if options[:singleton]
|
21
|
+
@target = object
|
22
|
+
else
|
23
|
+
@target = object.class
|
24
|
+
@options = @target.state_fu_options[@method_name].merge(options)
|
25
|
+
end
|
26
|
+
@field_name = @options.delete(:field_name) || raise("No field_name supplied")
|
27
|
+
@persister = Persistence.for self
|
24
28
|
|
25
29
|
# define event methods on this binding and its @object
|
26
|
-
MethodFactory.new(
|
27
|
-
@machine.helpers.inject_into
|
30
|
+
MethodFactory.new(self).install!
|
31
|
+
@machine.helpers.inject_into self
|
28
32
|
end
|
29
33
|
|
30
34
|
alias_method :o, :object
|
data/lib/interface.rb
CHANGED
@@ -2,21 +2,19 @@ module StateFu
|
|
2
2
|
module Interface
|
3
3
|
module SoftAlias
|
4
4
|
|
5
|
-
|
6
|
-
# so we can be liberal with them.
|
7
|
-
def soft_alias(x)
|
8
|
-
aliases = [ x.to_a[0] ].flatten
|
9
|
-
original = aliases.shift
|
5
|
+
def soft_alias(hash)
|
10
6
|
existing_method_names = (self.instance_methods | self.protected_instance_methods | self.private_instance_methods).map(&:to_sym)
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
7
|
+
hash.each do |original, aliases|
|
8
|
+
aliases.
|
9
|
+
reject { |a| existing_method_names.include?(a.to_sym) }.
|
10
|
+
each { |a| alias_method a, original}
|
11
|
+
end
|
12
|
+
end
|
16
13
|
end
|
17
14
|
|
18
15
|
module Aliases
|
19
|
-
|
16
|
+
# define aliases that won't clobber existing methods -
|
17
|
+
# so we can be liberal with them.
|
20
18
|
def self.extended(base)
|
21
19
|
base.extend SoftAlias
|
22
20
|
base.class_eval do
|
@@ -70,8 +68,8 @@ module StateFu
|
|
70
68
|
end
|
71
69
|
alias_method :machine, :state_fu_machine
|
72
70
|
|
73
|
-
def
|
74
|
-
@
|
71
|
+
def state_fu_options
|
72
|
+
@_state_fu_options ||= {}
|
75
73
|
end
|
76
74
|
|
77
75
|
def state_fu_machines
|
@@ -97,7 +95,7 @@ module StateFu
|
|
97
95
|
# can access a StateFu::Machine, the object's current state, the
|
98
96
|
# methods which trigger event transitions, etc.
|
99
97
|
|
100
|
-
def state_fu_binding(
|
98
|
+
def state_fu_binding(name = DEFAULT)
|
101
99
|
name = name.to_sym
|
102
100
|
if machine = self.class.state_fu_machines[name]
|
103
101
|
state_fu_bindings[name] ||= StateFu::Binding.new( machine, self, name )
|
data/lib/machine.rb
CHANGED
@@ -4,7 +4,7 @@ module StateFu
|
|
4
4
|
def self.BINDINGS
|
5
5
|
@@_bindings ||= {}
|
6
6
|
end
|
7
|
-
|
7
|
+
|
8
8
|
include Applicable
|
9
9
|
include HasOptions
|
10
10
|
|
@@ -17,10 +17,9 @@ module StateFu
|
|
17
17
|
def self.for_class(klass, name, options={}, &block)
|
18
18
|
options.symbolize_keys!
|
19
19
|
name = name.to_sym
|
20
|
-
|
21
20
|
unless machine = klass.state_fu_machines[ name ]
|
22
21
|
machine = new(options)
|
23
|
-
machine.bind! klass, name, options
|
22
|
+
machine.bind! klass, name, options
|
24
23
|
end
|
25
24
|
if block_given?
|
26
25
|
machine.apply! &block
|
@@ -30,26 +29,36 @@ module StateFu
|
|
30
29
|
|
31
30
|
# make it so that a class which has included StateFu has a binding to
|
32
31
|
# this machine
|
33
|
-
def self.bind!(
|
32
|
+
def self.bind!(machine, owner, name, options={})
|
34
33
|
name = name.to_sym
|
34
|
+
options[:define_methods] = (name == DEFAULT) unless options.symbolize_keys!.has_key?(:define_methods)
|
35
|
+
options[:field_name] ||= Persistence.default_field_name(name)
|
36
|
+
options[:singleton] = true unless owner.is_a?(Class)
|
35
37
|
# define an accessor method with the given name
|
36
|
-
if
|
37
|
-
owner
|
38
|
-
owner
|
38
|
+
if options[:singleton]
|
39
|
+
_binding = StateFu::Binding.new machine, owner, name, options
|
40
|
+
MethodFactory.define_singleton_method(owner, name) { _binding }
|
41
|
+
if alias_name = options[:alias] || options[:as]
|
42
|
+
MethodFactory.define_singleton_method(owner, alias_name) { _binding }
|
43
|
+
end
|
44
|
+
else
|
45
|
+
owner.state_fu_machines[name] = machine
|
46
|
+
owner.state_fu_options[name] = options
|
39
47
|
# method_missing to catch NoMethodError for event methods, etc
|
40
|
-
StateFu::MethodFactory.define_once_only_method_missing
|
41
|
-
unless owner.respond_to?
|
48
|
+
StateFu::MethodFactory.define_once_only_method_missing owner
|
49
|
+
unless owner.respond_to? name
|
42
50
|
owner.class_eval do
|
43
51
|
define_method name do
|
44
|
-
state_fu
|
52
|
+
state_fu name
|
53
|
+
end
|
54
|
+
# allow aliases to be set up, e.g. machine(:as => :status)
|
55
|
+
if alias_name = options[:alias] || options[:as]
|
56
|
+
alias_method alias_name, name
|
45
57
|
end
|
46
58
|
end
|
47
59
|
end
|
48
60
|
# prepare the persistence field
|
49
|
-
StateFu::Persistence.prepare_field owner, field_name
|
50
|
-
else
|
51
|
-
_binding = StateFu::Binding.new machine, owner, name, :field_name => field_name, :singleton => true
|
52
|
-
MethodFactory.define_singleton_method(owner, name) { _binding }
|
61
|
+
StateFu::Persistence.prepare_field owner, options[:field_name]
|
53
62
|
end
|
54
63
|
end
|
55
64
|
|
@@ -116,9 +125,8 @@ module StateFu
|
|
116
125
|
|
117
126
|
# make it so a class which has included StateFu has a binding to
|
118
127
|
# this machine
|
119
|
-
def bind!(
|
120
|
-
|
121
|
-
self.class.bind!(self, owner, name, field_name)
|
128
|
+
def bind!(owner, name=DEFAULT, options={})
|
129
|
+
self.class.bind!(self, owner, name, options)
|
122
130
|
end
|
123
131
|
|
124
132
|
def empty?
|
data/lib/method_factory.rb
CHANGED
@@ -7,14 +7,15 @@ module StateFu
|
|
7
7
|
|
8
8
|
class MethodFactory
|
9
9
|
attr_accessor :method_definitions
|
10
|
-
attr_reader :binding
|
11
|
-
|
10
|
+
attr_reader :binding, :machine
|
11
|
+
|
12
12
|
# An instance of MethodFactory is created to define methods on a specific StateFu::Binding, and
|
13
13
|
# on the object it is bound to.
|
14
14
|
|
15
|
-
def initialize(
|
16
|
-
@binding = _binding
|
17
|
-
|
15
|
+
def initialize(_binding)
|
16
|
+
@binding = _binding
|
17
|
+
@machine = binding.machine
|
18
|
+
simple_events, complex_events = machine.events.partition &:simple?
|
18
19
|
@method_definitions = {}
|
19
20
|
|
20
21
|
# simple event methods
|
@@ -95,7 +96,7 @@ module StateFu
|
|
95
96
|
end unless event.targets.nil?
|
96
97
|
end
|
97
98
|
|
98
|
-
|
99
|
+
machine.states.each do |state|
|
99
100
|
method_definitions["#{state.name}?"] = lambda do
|
100
101
|
_binding.current_state == state
|
101
102
|
end
|
@@ -114,9 +115,9 @@ module StateFu
|
|
114
115
|
# Note this happens when a machine is first bound to the class,
|
115
116
|
# not when StateFu is included.
|
116
117
|
|
117
|
-
def self.prepare_class(
|
118
|
+
def self.prepare_class(klass)
|
118
119
|
raise caller.inspect
|
119
|
-
self.define_once_only_method_missing(
|
120
|
+
self.define_once_only_method_missing(klass)
|
120
121
|
end # prepare_class
|
121
122
|
|
122
123
|
# When triggered, method_missing will first call state_fu!,
|
@@ -137,7 +138,7 @@ module StateFu
|
|
137
138
|
# or thoroughly understand what's happening in
|
138
139
|
# MethodFactory#define_once_only_method_missing.
|
139
140
|
|
140
|
-
def self.define_once_only_method_missing(
|
141
|
+
def self.define_once_only_method_missing(klass)
|
141
142
|
raise ArgumentError.new(klass.to_s) unless klass.is_a?(Class)
|
142
143
|
|
143
144
|
klass.class_eval do
|
@@ -160,14 +161,14 @@ module StateFu
|
|
160
161
|
end
|
161
162
|
|
162
163
|
# call the newly defined method, or the original method_missing
|
163
|
-
if respond_to?
|
164
|
+
if respond_to? method_name, true
|
164
165
|
# it was defined by calling state_fu!, which instantiated bindings
|
165
166
|
# for its state machines, which defined singleton methods for its
|
166
167
|
# states & events when it was constructed.
|
167
|
-
__send__
|
168
|
+
__send__ method_name, *args, &block
|
168
169
|
else
|
169
170
|
# call the original method_missing (method_missing_before_state_fu)
|
170
|
-
method_missing
|
171
|
+
method_missing method_name, *args, &block
|
171
172
|
end
|
172
173
|
end # method_missing
|
173
174
|
end # class_eval
|
@@ -177,8 +178,8 @@ module StateFu
|
|
177
178
|
# object. Any existing methods will not be tampered with, but a
|
178
179
|
# warning will be issued in the logs if any methods cannot be defined.
|
179
180
|
def install!
|
180
|
-
define_event_methods_on
|
181
|
-
define_event_methods_on
|
181
|
+
define_event_methods_on @binding
|
182
|
+
define_event_methods_on @binding.object if @binding.options[:define_methods]
|
182
183
|
end
|
183
184
|
|
184
185
|
#
|
@@ -202,13 +203,13 @@ module StateFu
|
|
202
203
|
# itself. The remaining arguments are passed into the transition,
|
203
204
|
# as with simple event methods.
|
204
205
|
#
|
205
|
-
def define_event_methods_on(
|
206
|
+
def define_event_methods_on(obj)
|
206
207
|
method_definitions.each do |method_name, method_body|
|
207
|
-
define_singleton_method
|
208
|
+
define_singleton_method obj, method_name, &method_body
|
208
209
|
end
|
209
210
|
end # define_event_methods_on
|
210
211
|
|
211
|
-
def define_singleton_method(
|
212
|
+
def define_singleton_method(object, method_name, &block)
|
212
213
|
MethodFactory.define_singleton_method object, method_name, &block
|
213
214
|
end
|
214
215
|
|
@@ -221,8 +222,8 @@ module StateFu
|
|
221
222
|
#
|
222
223
|
# existing methods will never be overwritten.
|
223
224
|
|
224
|
-
def self.define_singleton_method(
|
225
|
-
if object.respond_to?
|
225
|
+
def self.define_singleton_method(object, method_name, options={}, &block)
|
226
|
+
if object.respond_to? method_name, true
|
226
227
|
msg = !options[:force]
|
227
228
|
Logger.info "Existing method #{method(method_name) rescue [method_name].inspect} "\
|
228
229
|
"for #{object.class} #{object} "\
|
@@ -231,7 +232,7 @@ module StateFu
|
|
231
232
|
else
|
232
233
|
metaclass = class << object; self; end
|
233
234
|
metaclass.class_eval do
|
234
|
-
define_method
|
235
|
+
define_method method_name, &block
|
235
236
|
end
|
236
237
|
end
|
237
238
|
end
|
data/lib/persistence.rb
CHANGED
@@ -39,7 +39,7 @@ module StateFu
|
|
39
39
|
end
|
40
40
|
|
41
41
|
# returns the appropriate persister class for the given class & field name.
|
42
|
-
def self.class_for(
|
42
|
+
def self.class_for(klass, field_name)
|
43
43
|
raise ArgumentError if [klass, field_name].any?(&:nil?)
|
44
44
|
@@class_for[klass] ||= {}
|
45
45
|
@@class_for[klass][field_name] ||=
|
@@ -52,7 +52,7 @@ module StateFu
|
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
|
-
def self.for_class(
|
55
|
+
def self.for_class(klass, binding, field_name)
|
56
56
|
persister_class = class_for klass, field_name
|
57
57
|
prepare_field( klass, field_name, persister_class)
|
58
58
|
returning persister_class.new( binding, field_name ) do |persister|
|
@@ -60,7 +60,7 @@ module StateFu
|
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
63
|
-
def self.for_instance(
|
63
|
+
def self.for_instance(binding, field_name)
|
64
64
|
metaclass = class << binding.object; self; end
|
65
65
|
for_class( metaclass, binding, field_name )
|
66
66
|
end
|
@@ -69,7 +69,7 @@ module StateFu
|
|
69
69
|
# also ensures the persister class method :prepare_field has been called
|
70
70
|
# once for the given class & field name so the field can be set up; eg an
|
71
71
|
# attr_accessor or a before_save hook defined
|
72
|
-
def self.for(
|
72
|
+
def self.for(binding)
|
73
73
|
field_name = binding.field_name.to_sym
|
74
74
|
if binding.singleton?
|
75
75
|
for_instance( binding, field_name )
|
@@ -95,21 +95,21 @@ module StateFu
|
|
95
95
|
# checks to see if the field_name for persistence is a
|
96
96
|
# RelaxDB attribute.
|
97
97
|
# Safe to use (skipped) if RelaxDB is not included.
|
98
|
-
def self.relaxdb_document_property?(
|
98
|
+
def self.relaxdb_document_property?(klass, field_name)
|
99
99
|
Object.const_defined?('RelaxDB') &&
|
100
100
|
klass.ancestors.include?( ::RelaxDB::Document ) &&
|
101
|
-
klass.properties.map(&:to_s).include?(
|
101
|
+
klass.properties.map(&:to_s).include?(field_name.to_s)
|
102
102
|
end
|
103
103
|
|
104
104
|
# checks to see if the field_name for persistence is an
|
105
105
|
# ActiveRecord column.
|
106
106
|
# Safe to use (skipped) if ActiveRecord is not included.
|
107
|
-
def self.active_record_column?(
|
107
|
+
def self.active_record_column?(klass, field_name)
|
108
108
|
Object.const_defined?("ActiveRecord") &&
|
109
109
|
::ActiveRecord.const_defined?("Base") &&
|
110
|
-
klass.ancestors.include?(
|
110
|
+
klass.ancestors.include?(::ActiveRecord::Base) &&
|
111
111
|
klass.table_exists? &&
|
112
|
-
klass.columns.map(&:name).include?(
|
112
|
+
klass.columns.map(&:name).include?(field_name.to_s)
|
113
113
|
end
|
114
114
|
|
115
115
|
end
|
data/lib/support/logger.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
require 'logger'
|
3
2
|
module StateFu
|
4
3
|
#
|
@@ -32,9 +31,9 @@ module StateFu
|
|
32
31
|
@@shared = false
|
33
32
|
@@log_level = nil
|
34
33
|
|
35
|
-
def self.new( logger
|
36
|
-
|
37
|
-
|
34
|
+
def self.new( logger=nil, options={} )
|
35
|
+
@@suppress = false
|
36
|
+
set_logger(logger, options)
|
38
37
|
self
|
39
38
|
end
|
40
39
|
|
@@ -55,19 +54,19 @@ module StateFu
|
|
55
54
|
end
|
56
55
|
|
57
56
|
def self.shared?
|
58
|
-
|
57
|
+
!!@@shared
|
59
58
|
end
|
60
59
|
|
61
60
|
def self.prefix
|
62
61
|
shared? ? @@prefix : nil
|
63
62
|
end
|
64
63
|
|
65
|
-
def self.logger=
|
66
|
-
set_logger
|
64
|
+
def self.logger= new_logger
|
65
|
+
set_logger new_logger
|
67
66
|
end
|
68
67
|
|
69
68
|
def self.instance
|
70
|
-
@@logger ||=
|
69
|
+
@@logger ||= default_logger
|
71
70
|
end
|
72
71
|
|
73
72
|
def self.suppress!
|
@@ -108,7 +107,7 @@ module StateFu
|
|
108
107
|
when Logger.activesupport_logger_available? && ActiveSupport::BufferedLogger
|
109
108
|
@@logger = logger
|
110
109
|
else
|
111
|
-
|
110
|
+
default_logger
|
112
111
|
end
|
113
112
|
self.shared = !!options.symbolize_keys![:shared]
|
114
113
|
if shared?
|
@@ -123,27 +122,19 @@ module StateFu
|
|
123
122
|
|
124
123
|
private
|
125
124
|
|
126
|
-
def self.get_logger( logr = $stdout )
|
127
|
-
if Object.const_defined?( "RAILS_DEFAULT_LOGGER" )
|
128
|
-
set_logger RAILS_DEFAULT_LOGGER, :shared => true
|
129
|
-
else
|
130
|
-
if Object.const_defined?( 'ActiveSupport' ) && ActiveSupport.const_defined?('BufferedLogger')
|
131
|
-
set_logger( ActiveSupport::BufferedLogger.new( logr ))
|
132
|
-
else
|
133
|
-
set_logger ::Logger.new( logr )
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
125
|
def self.activesupport_logger_available?
|
139
126
|
Object.const_defined?( 'ActiveSupport' ) && ActiveSupport.const_defined?('BufferedLogger')
|
140
127
|
end
|
141
128
|
|
142
|
-
def self.default_logger
|
143
|
-
if
|
144
|
-
RAILS_DEFAULT_LOGGER
|
145
|
-
|
146
|
-
|
129
|
+
def self.default_logger(target=$stdout)
|
130
|
+
if activesupport_logger_available?
|
131
|
+
if Object.const_defined?("RAILS_DEFAULT_LOGGER")
|
132
|
+
RAILS_DEFAULT_LOGGER
|
133
|
+
else
|
134
|
+
ActiveSupport::BufferedLogger.new(target)
|
135
|
+
end
|
136
|
+
else
|
137
|
+
::Logger.new(target)
|
147
138
|
end
|
148
139
|
end
|
149
140
|
|
@@ -156,7 +147,7 @@ module StateFu
|
|
156
147
|
when nil
|
157
148
|
level
|
158
149
|
else
|
159
|
-
raise ArgumentError
|
150
|
+
raise ArgumentError.new(input.inspect)
|
160
151
|
end
|
161
152
|
end
|
162
153
|
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../helper")
|
2
|
+
|
3
|
+
describe "defining an alias for the default machine" do
|
4
|
+
describe "with machine(:as => :status)" do
|
5
|
+
before do
|
6
|
+
make_pristine_class('Klass')
|
7
|
+
Klass.machine :as => :status do
|
8
|
+
state :active
|
9
|
+
end
|
10
|
+
@obj = Klass.new
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should not prevent normal access through #state_fu" do
|
14
|
+
@obj.state_fu.machine.should == Klass.machine
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should not affect the internal name of the machine" do
|
18
|
+
@obj.status.method_name.should == :default
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should let you access the machine through #status" do
|
22
|
+
@obj.status.should be_kind_of(StateFu::Binding)
|
23
|
+
@obj.status.machine.should == Klass.machine
|
24
|
+
@obj.status.current_state.should == :active
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "defining an alias for a default singleton machine" do
|
31
|
+
describe "with #bind! :default, :as => :alias" do
|
32
|
+
before do
|
33
|
+
make_pristine_class('Klass')
|
34
|
+
@machine = StateFu::Machine.new do
|
35
|
+
state :exemplary
|
36
|
+
end
|
37
|
+
@obj = Klass.new
|
38
|
+
@machine.bind!( @obj, :default, :as => :example)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should work too" do
|
42
|
+
@obj.example.machine.should == @machine
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
@@ -5,7 +5,7 @@ describe "singleton machines" do
|
|
5
5
|
make_pristine_class('Klass')
|
6
6
|
@m = StateFu::Machine.new do
|
7
7
|
state :a do
|
8
|
-
event :
|
8
|
+
event :bify, :transitions_to => :b
|
9
9
|
end
|
10
10
|
end
|
11
11
|
@m.events.length.should == 1
|
@@ -20,9 +20,22 @@ describe "singleton machines" do
|
|
20
20
|
@obj.my_binding.object.should == @obj
|
21
21
|
end
|
22
22
|
|
23
|
-
it "should have
|
24
|
-
%w/
|
23
|
+
it "should have methods defined on the binding by default" do
|
24
|
+
%w/bify can_bify? bify!/.each do |method_name|
|
25
25
|
@obj.my_binding.should respond_to(method_name)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should not have methods defined on the instance by default" do
|
30
|
+
%w/bify can_bify? bify!/.each do |method_name|
|
31
|
+
@obj.should_not respond_to(method_name)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
it "should have methods defined on the instance if bound with :define_methods => true" do
|
37
|
+
@m.bind!( @obj, :my_binding, :define_methods => true)
|
38
|
+
%w/bify can_bify? bify!/.each do |method_name|
|
26
39
|
@obj.should respond_to(method_name)
|
27
40
|
end
|
28
41
|
end
|
@@ -30,7 +43,7 @@ describe "singleton machines" do
|
|
30
43
|
it "should transition" do
|
31
44
|
@b = @obj.my_binding
|
32
45
|
@b.current_state.should == :a
|
33
|
-
t = @obj.
|
46
|
+
t = @obj.my_binding.bify!
|
34
47
|
t.should be_kind_of(StateFu::Transition)
|
35
48
|
t.should be_accepted
|
36
49
|
@b.current_state.should == :b
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../helper")
|
2
|
+
|
3
|
+
describe "instance methods defined for a class's machine," do
|
4
|
+
before do
|
5
|
+
make_pristine_class('Klass')
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "for the default machine" do
|
9
|
+
it "should define methods for states and events" do
|
10
|
+
Klass.machine do
|
11
|
+
event :go, :from => :a, :to => :b
|
12
|
+
end
|
13
|
+
instance = Klass.new
|
14
|
+
instance.state_fu!
|
15
|
+
instance.should respond_to(:a?)
|
16
|
+
instance.should respond_to(:can_go?)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should not define methods given :define_methods => false" do
|
20
|
+
Klass.machine :define_methods => false do
|
21
|
+
event :go, :from => :a, :to => :b
|
22
|
+
end
|
23
|
+
instance = Klass.new
|
24
|
+
instance.state_fu!
|
25
|
+
instance.should_not respond_to(:a?)
|
26
|
+
instance.should_not respond_to(:can_go?)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "for other machines" do
|
31
|
+
it "should not define methods" do
|
32
|
+
Klass.machine :other do
|
33
|
+
event :go, :from => :a, :to => :b
|
34
|
+
end
|
35
|
+
instance = Klass.new
|
36
|
+
instance.state_fu!
|
37
|
+
instance.should_not respond_to(:a?)
|
38
|
+
instance.should_not respond_to(:can_go?)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should define methods given :define_methods => true" do
|
42
|
+
Klass.machine :define_methods => true do
|
43
|
+
event :go, :from => :a, :to => :b
|
44
|
+
end
|
45
|
+
instance = Klass.new
|
46
|
+
instance.state_fu!
|
47
|
+
instance.should respond_to(:a?)
|
48
|
+
instance.should respond_to(:can_go?)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
describe "instance methods when you #bind! a machine" do
|
55
|
+
before do
|
56
|
+
make_pristine_class('Klass')
|
57
|
+
@machine = StateFu::Machine.new do
|
58
|
+
state :hot
|
59
|
+
state :cold
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "as the default machine," do
|
64
|
+
describe "the default behaviour" do
|
65
|
+
before do
|
66
|
+
@machine.bind! Klass, :default, :define_methods => true
|
67
|
+
end
|
68
|
+
|
69
|
+
it "defines methods" do
|
70
|
+
instance = Klass.new
|
71
|
+
instance.state_fu!
|
72
|
+
instance.should respond_to(:hot?)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "when it is bound with :define_methods => false" do
|
77
|
+
before do
|
78
|
+
@machine.bind! Klass, :default, :define_methods => false
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should not define methods" do
|
82
|
+
instance = Klass.new
|
83
|
+
instance.state_fu!
|
84
|
+
instance.should_not respond_to(:hot?)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "as another machine," do
|
90
|
+
describe "the default behaviour" do
|
91
|
+
before do
|
92
|
+
@machine.bind! Klass, :temperature
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should not define methods" do
|
96
|
+
instance = Klass.new
|
97
|
+
instance.state_fu!
|
98
|
+
instance.should_not respond_to(:hot?)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe "when it is bound with :define_methods => true" do
|
103
|
+
before do
|
104
|
+
@machine.bind! Klass, :temperature, :define_methods => true
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should define methods" do
|
108
|
+
instance = Klass.new
|
109
|
+
instance.state_fu!
|
110
|
+
instance.should respond_to(:hot?)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/spec/state_fu_spec.rb
CHANGED
@@ -885,7 +885,23 @@ describe "Chameleon" do
|
|
885
885
|
event :go_inside, :from => {:outside => :inside}
|
886
886
|
end
|
887
887
|
|
888
|
-
|
888
|
+
# With :define_methods => true, we can create methods for the :skin
|
889
|
+
# machine directly on our Chameleon, so we can type e.g.
|
890
|
+
#
|
891
|
+
# @chameleon.comoflage! :bark
|
892
|
+
# instead of:
|
893
|
+
# @chameleon.skin.camoflage! :bark
|
894
|
+
#
|
895
|
+
# This is the usual behaviour for the default machine, but not for any
|
896
|
+
# machine given an explicit name. Otherwise, it would cause confusion
|
897
|
+
# when (like the PokerMachine example) multiple machines would compete
|
898
|
+
# for the same methods.
|
899
|
+
#
|
900
|
+
# Hint for the masochistic: state / event methods will never overwrite a
|
901
|
+
# pre-existing method, so in the event of overlap, the first machine
|
902
|
+
# defined will take precedence.
|
903
|
+
|
904
|
+
machine :skin, :define_methods => true do
|
889
905
|
initial_state :green
|
890
906
|
|
891
907
|
states :plaid, :paisley, :tartan, :location => :indoors
|
@@ -897,9 +913,9 @@ describe "Chameleon" do
|
|
897
913
|
else
|
898
914
|
case transition.target[:location]
|
899
915
|
when :indoors
|
900
|
-
inside?
|
916
|
+
location.inside?
|
901
917
|
when :outdoors
|
902
|
-
outside?
|
918
|
+
location.outside?
|
903
919
|
else
|
904
920
|
true
|
905
921
|
end
|
@@ -923,13 +939,13 @@ describe "Chameleon" do
|
|
923
939
|
@chameleon.current_state(:location).should == :outside
|
924
940
|
@chameleon.current_state(:skin).should == :green
|
925
941
|
|
926
|
-
@chameleon.outside?.should == true
|
942
|
+
@chameleon.location.outside?.should == true
|
927
943
|
|
928
944
|
@chameleon.skin.valid_transitions.targets.names.should == [:bark, :pebbles, :foliage]
|
929
945
|
@chameleon.camoflage!(:bark)
|
930
946
|
@chameleon.skin.should == :bark
|
931
947
|
|
932
|
-
@chameleon.go_inside!
|
948
|
+
@chameleon.location.go_inside!
|
933
949
|
@chameleon.skin.valid_transitions.targets.names.should == [:green, :plaid, :paisley, :tartan]
|
934
950
|
|
935
951
|
@chameleon.camoflage!(:tartan)
|
@@ -943,6 +959,7 @@ describe "Chameleon" do
|
|
943
959
|
end
|
944
960
|
|
945
961
|
end
|
962
|
+
|
946
963
|
end
|
947
964
|
|
948
965
|
|
data/spec/units/binding_spec.rb
CHANGED
@@ -14,12 +14,6 @@ describe StateFu::Binding do
|
|
14
14
|
end
|
15
15
|
|
16
16
|
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
|
21
|
-
|
22
|
-
|
23
17
|
#
|
24
18
|
#
|
25
19
|
#
|
@@ -33,8 +27,10 @@ describe StateFu::Binding do
|
|
33
27
|
|
34
28
|
describe "constructor" do
|
35
29
|
before do
|
36
|
-
mock(Klass).
|
37
|
-
{
|
30
|
+
mock(Klass).state_fu_options.at_most(2) do
|
31
|
+
{
|
32
|
+
:example => {:field_name => :example_field}
|
33
|
+
}
|
38
34
|
end
|
39
35
|
end
|
40
36
|
|
@@ -63,10 +59,12 @@ describe StateFu::Binding do
|
|
63
59
|
end
|
64
60
|
|
65
61
|
describe "when StateFu::Persistence.active_record_column? is true" do
|
62
|
+
|
66
63
|
before do
|
67
64
|
mock( StateFu::Persistence ).active_record_column?(Klass, :example_field).times(1) { true }
|
68
65
|
mock( Klass ).before_create( :state_fu!) { }
|
69
66
|
end
|
67
|
+
|
70
68
|
it "should get an ActiveRecord persister" do
|
71
69
|
mock( StateFu::Persistence::ActiveRecord ).new( anything, :example_field ) { @p }
|
72
70
|
b = StateFu::Binding.new( Klass.state_fu_machine, @obj, :example )
|
data/spec/units/machine_spec.rb
CHANGED
@@ -20,22 +20,19 @@ describe StateFu::Machine do
|
|
20
20
|
before do
|
21
21
|
reset!
|
22
22
|
make_pristine_class 'Klass'
|
23
|
-
# mock( Klass ).machines() { {} }
|
24
23
|
end
|
25
24
|
|
26
25
|
it "should create a new machine and bind! it" do
|
27
26
|
@machine = Object.new
|
28
|
-
mock(
|
29
|
-
mock(
|
30
|
-
StateFu::Machine.for_class
|
27
|
+
mock(@machine).bind!(Klass, :moose, {})
|
28
|
+
mock(StateFu::Machine).new({}) { @machine }
|
29
|
+
StateFu::Machine.for_class Klass, :moose
|
31
30
|
end
|
32
31
|
|
33
32
|
it "should apply the block (via lathe) if one is given" do
|
34
|
-
@m = StateFu::Machine.for_class
|
33
|
+
@m = StateFu::Machine.for_class Klass, :snoo do
|
35
34
|
state :porpoise
|
36
35
|
end
|
37
|
-
# mock( Klass ).machines() { {} }
|
38
|
-
# @m.states.map(&:name).should == [:porpoise]
|
39
36
|
end
|
40
37
|
end
|
41
38
|
|
@@ -85,7 +82,7 @@ describe StateFu::Machine do
|
|
85
82
|
name = :StinkJuice
|
86
83
|
field_name = 'stink_juice_field'
|
87
84
|
@m.bind!( Klass, name )
|
88
|
-
Klass.
|
85
|
+
Klass.state_fu_options[name][:field_name].should == 'stink_juice_field'
|
89
86
|
end
|
90
87
|
end
|
91
88
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: state-fu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.12.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Lee
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-09-26 00:00:00 +10:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -73,6 +73,7 @@ files:
|
|
73
73
|
- lib/transition_query.rb
|
74
74
|
- spec/custom_formatter.rb
|
75
75
|
- spec/features/binding_and_transition_helper_mixin_spec.rb
|
76
|
+
- spec/features/machine_alias_spec.rb
|
76
77
|
- spec/features/method_missing_only_once_spec.rb
|
77
78
|
- spec/features/not_requirements_spec.rb
|
78
79
|
- spec/features/plotter_spec.rb
|
@@ -80,6 +81,7 @@ files:
|
|
80
81
|
- spec/features/singleton_machine_spec.rb
|
81
82
|
- spec/features/state_and_array_options_accessor_spec.rb
|
82
83
|
- spec/features/transition_boolean_comparison_spec.rb
|
84
|
+
- spec/features/when_methods_are_defined_spec.rb
|
83
85
|
- spec/helper.rb
|
84
86
|
- spec/integration/active_record_persistence_spec.rb
|
85
87
|
- spec/integration/binding_extension_spec.rb
|
@@ -138,6 +140,7 @@ summary: A rich library for state-oriented programming with state machines / wor
|
|
138
140
|
test_files:
|
139
141
|
- spec/custom_formatter.rb
|
140
142
|
- spec/features/binding_and_transition_helper_mixin_spec.rb
|
143
|
+
- spec/features/machine_alias_spec.rb
|
141
144
|
- spec/features/method_missing_only_once_spec.rb
|
142
145
|
- spec/features/not_requirements_spec.rb
|
143
146
|
- spec/features/plotter_spec.rb
|
@@ -145,6 +148,7 @@ test_files:
|
|
145
148
|
- spec/features/singleton_machine_spec.rb
|
146
149
|
- spec/features/state_and_array_options_accessor_spec.rb
|
147
150
|
- spec/features/transition_boolean_comparison_spec.rb
|
151
|
+
- spec/features/when_methods_are_defined_spec.rb
|
148
152
|
- spec/helper.rb
|
149
153
|
- spec/integration/active_record_persistence_spec.rb
|
150
154
|
- spec/integration/binding_extension_spec.rb
|