state-fu 0.12.3 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.textile CHANGED
@@ -165,7 +165,7 @@ It is also delightfully elegant and easy to use for simple things:
165
165
  # this wants to be last, as it iterates over each state which is
166
166
  # already defined.
167
167
  states do
168
- accepted { object.save! }
168
+ accepted { save! }
169
169
  end
170
170
  end
171
171
  end
data/lib/binding.rb CHANGED
@@ -19,16 +19,16 @@ module StateFu
19
19
  @options = options.symbolize_keys!
20
20
  if options[:singleton]
21
21
  @target = object
22
- else
22
+ else
23
23
  @target = object.class
24
24
  @options = @target.state_fu_options[@method_name].merge(options)
25
- end
26
- @field_name = @options.delete(:field_name) || raise("No field_name supplied")
25
+ end
26
+ @field_name = @options.delete(:field_name) || raise("No field_name supplied")
27
27
  @persister = Persistence.for self
28
28
 
29
29
  # define event methods on this binding and its @object
30
30
  MethodFactory.new(self).install!
31
- @machine.helpers.inject_into self
31
+ @machine.helpers.inject_into self
32
32
  end
33
33
 
34
34
  alias_method :o, :object
@@ -143,7 +143,6 @@ module StateFu
143
143
  def transition( event_or_array, *args, &block )
144
144
  return transitions.with(*args, &block).find(event_or_array)
145
145
  end
146
-
147
146
  #
148
147
  # next_transition and friends: when there's exactly one valid move
149
148
  #
@@ -292,5 +291,11 @@ module StateFu
292
291
  s
293
292
  end
294
293
 
294
+ # little kludge - allows the binding to reuse the same method definitions as 'object'
295
+ # in MethodFactory#method_definitions_for
296
+ def state_fu(name=nil)
297
+ self
298
+ end
299
+
295
300
  end
296
301
  end
data/lib/executioner.rb CHANGED
@@ -1,65 +1,40 @@
1
1
  module StateFu
2
2
  #
3
- # delegator class for evaluation methods / procs in the context of
4
- # your object.
3
+ # class that handles executing stuff in the context of your 'object'
5
4
  #
6
5
 
7
6
  class Executioner
8
7
 
9
- # give us a blank slate
10
- # instance_methods.each { |m| undef_method m unless m =~ /(^__|^self|^nil\?$|^send$|proxy_|^object_id|^respond_to\?|^instance_exec|^instance_eval|^method$)/ }
8
+ attr_reader :transition, :object
11
9
 
12
10
  def initialize transition, &block
13
- @transition = transition
14
- @__target__ = transition.object
15
- @__self___ = self
16
- yield self if block_given?
17
- # forces method_missing to snap back to its pre-state-fu condition:
18
- # @__target__.initialize_state_fu!
11
+ @transition = transition
12
+ @object = transition.object
19
13
  self
20
14
  end
21
15
 
22
- delegate :origin, :to => :transition, :prefix => true # transition_origin
23
- delegate :target, :to => :transition, :prefix => true # transition_target
24
- delegate :event, :to => :transition, :prefix => true # transition_event
25
-
26
- delegate :halt!, :to => :transition
27
- delegate :args, :to => :transition
28
- delegate :options, :to => :transition
29
-
30
- def binding
31
- transition.binding
32
- end
33
-
34
- attr_reader :transition, :__target__, :__self__
35
-
36
- alias_method :t, :transition
37
- alias_method :current_transition, :transition
38
- alias_method :context, :transition
39
- alias_method :ctx, :transition
40
-
41
- alias_method :arguments, :args
42
- alias_method :transition_arguments, :args
43
-
44
- def machine
45
- binding.machine
16
+ def evaluate method_name_or_proc
17
+ args = [transition, transition.arguments]
18
+ evaluate_with_arguments(method_name_or_proc, *args)
46
19
  end
47
20
 
48
- def states
49
- machine.states
50
- end
51
- # delegate :machine, :to => :transition
52
-
53
- def evaluate_with_arguments method_name_or_proc, *arguments
54
- if method_name_or_proc.is_a?(Proc) && meth = method_name_or_proc
55
- elsif meth = transition.machine.named_procs[method_name_or_proc]
56
- elsif respond_to?( method_name_or_proc) && meth = method(method_name_or_proc)
21
+ private
22
+
23
+ def evaluate_with_arguments method_name_or_proc, *arguments
24
+ if method_name_or_proc.is_a?(Proc) &&
25
+ meth = method_name_or_proc
26
+ # got a proc
27
+ elsif meth = transition.machine.named_procs[method_name_or_proc]
28
+ # got a named proc belonging to the machine
29
+ elsif object.__send__(:respond_to?, method_name_or_proc, true) &&
30
+ meth = object.__send__(:method, method_name_or_proc)
31
+ # got the name of a method on 'object'
57
32
  elsif method_name_or_proc.to_s =~ /^not?_(.*)$/
58
- # special case: prefix a method with no_ or not_ and get the
59
- # boolean opposite of its evaluation result
60
- return !( evaluate_with_arguments $1, *args )
33
+ # special case: given a method name prefixed with no_ or not_
34
+ # return the boolean opposite of its evaluation result
35
+ return !( evaluate_with_arguments $1.to_sym, *arguments )
61
36
  else
62
- raise NoMethodError.new( "undefined method_name `#{method_name_or_proc.to_s}' for \"#{__target__}\":#{__target__.class.to_s}" )
37
+ raise NoMethodError.new( "undefined method_name `#{method_name_or_proc.to_s}' for \"#{object}\":#{object.class.to_s}" )
63
38
  end
64
39
 
65
40
  if arguments.length < meth.arity.abs && meth.arity != -1
@@ -71,50 +46,8 @@ module StateFu
71
46
  end
72
47
 
73
48
  # execute it!
74
- __target__.with_methods_on(self) do
75
- self.instance_exec *arguments, &meth
76
- end
77
- end
78
-
79
- def evaluate method_name_or_proc
80
- arguments = [transition, args, __target__]
81
- evaluate_with_arguments(method_name_or_proc, *arguments)
82
- end
83
-
84
- alias_method :executioner_respond_to?, :respond_to?
85
-
86
- def respond_to? method_name, include_private = false
87
- executioner_respond_to?(method_name, include_private) ||
88
- __target__.__send__( :respond_to?, method_name, include_private )
89
- end
90
-
91
- alias_method :executioner_method, :method
92
- def method method_name
93
- begin
94
- executioner_method(method_name)
95
- rescue NameError
96
- __target__.__send__ :method, method_name
97
- end
98
- end
99
-
100
- private
101
-
102
- # Forwards any missing method call to the \target.
103
- # TODO / NOTE: we don't (can't ?) handle block arguments ...
104
- def method_missing(method_name, *args)
105
- if __target__.respond_to?(method_name, true)
106
- begin
107
- meth = __target__.__send__ :method, method_name
108
- rescue NameError
109
- super
110
- end
111
- __target__.instance_exec( *args, &meth)
112
- else # let's hope it's a named proc
113
- evaluate_with_arguments(method_name, *args)
114
- end
115
-
49
+ object.__send__(:instance_exec, *arguments, &meth)
116
50
  end
117
51
 
118
- # NOTE: const_missing is not handled.
119
52
  end
120
53
  end
data/lib/lathe.rb CHANGED
@@ -442,7 +442,7 @@ module StateFu
442
442
 
443
443
  # allow requirements and messages to be added as options
444
444
  if k = [:requires, :guard, :must, :must_be, :needs].detect {|k| options.has_key?(k) }
445
- # Logger.debug("removing option #{k} - will use as requirement ..")
445
+ # Logging.debug("removing option #{k} - will use as requirement ..")
446
446
  req = options.delete(k)
447
447
  msg = options.delete(:message) || options.delete(:msg)
448
448
  raise ArgumentError unless msg.nil? || req.is_a?(Symbol)
data/lib/machine.rb CHANGED
@@ -4,10 +4,10 @@ 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
+
11
11
  attr_reader :hooks
12
12
 
13
13
  #
@@ -19,17 +19,17 @@ module StateFu
19
19
  name = name.to_sym
20
20
  unless machine = klass.state_fu_machines[ name ]
21
21
  machine = new(options)
22
- machine.bind! klass, name, options
23
22
  end
24
23
  if block_given?
25
- machine.apply! &block
24
+ machine.apply! &block
26
25
  end
26
+ machine.bind! klass, name, options
27
27
  machine
28
28
  end
29
29
 
30
30
  # make it so that a class which has included StateFu has a binding to
31
31
  # this machine
32
- def self.bind!(machine, owner, name, options={})
32
+ def self.bind!(machine, owner, name, options={})
33
33
  name = name.to_sym
34
34
  options[:define_methods] = (name == DEFAULT) unless options.symbolize_keys!.has_key?(:define_methods)
35
35
  options[:field_name] ||= Persistence.default_field_name(name)
@@ -40,14 +40,17 @@ module StateFu
40
40
  MethodFactory.define_singleton_method(owner, name) { _binding }
41
41
  if alias_name = options[:alias] || options[:as]
42
42
  MethodFactory.define_singleton_method(owner, alias_name) { _binding }
43
- end
43
+ end
44
44
  else
45
- owner.state_fu_machines[name] = machine
46
- owner.state_fu_options[name] = options
47
- # method_missing to catch NoMethodError for event methods, etc
48
- StateFu::MethodFactory.define_once_only_method_missing owner
49
- unless owner.respond_to? name
50
- owner.class_eval do
45
+ klass = owner
46
+ klass.state_fu_machines[name] = machine
47
+ klass.state_fu_options[name] = options
48
+
49
+ # prepare the state machine accessor method
50
+ if owner.respond_to? name
51
+ raise "FIXME " + name
52
+ else
53
+ klass.class_eval do
51
54
  define_method name do
52
55
  state_fu name
53
56
  end
@@ -57,6 +60,10 @@ module StateFu
57
60
  end
58
61
  end
59
62
  end
63
+
64
+ # prepare event / state class methods
65
+ StateFu::MethodFactory.prepare_class_machine klass, machine, name, options
66
+
60
67
  # prepare the persistence field
61
68
  StateFu::Persistence.prepare_field owner, options[:field_name]
62
69
  end
@@ -99,10 +106,6 @@ module StateFu
99
106
  tools.inject_into( obj )
100
107
  end
101
108
 
102
- def inject_methods_into( obj )
103
- #puts 'inject_methods_into'
104
- end
105
-
106
109
  # the modules listed here will be mixed into Binding and
107
110
  # Transition objects for this machine. use this to define methods,
108
111
  # references or data useful to you during transitions, event
@@ -4,106 +4,110 @@ module StateFu
4
4
  # TODO: all events, simple or complex, should get the same method signature
5
5
  # simple events will be called as: event_name! nil, *args
6
6
  # complex events will be called as: event_name! :state, *args
7
-
7
+
8
8
  class MethodFactory
9
9
  attr_accessor :method_definitions
10
10
  attr_reader :binding, :machine
11
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
15
  def initialize(_binding)
16
- @binding = _binding
17
- @machine = binding.machine
18
- simple_events, complex_events = machine.events.partition &:simple?
19
- @method_definitions = {}
20
-
21
- # simple event methods
22
- # all arguments are passed into the transition / transition query
23
-
24
- simple_events.each do |event|
25
- method_definitions["#{event.name}"] = lambda do |*args|
26
- _binding.find_transition(event, event.target, *args)
27
- end
28
-
29
- method_definitions["can_#{event.name}?"] = lambda do |*args|
30
- _binding.can_transition?(event, event.target, *args)
31
- end
16
+ @binding = _binding
17
+ @machine = binding.machine
18
+ @method_definitions = MethodFactory.method_definitions_for(@machine, @binding.method_name)
19
+ self
20
+ end
32
21
 
33
- method_definitions["#{event.name}!"] = lambda do |*args|
34
- _binding.fire_transition!(event, event.target, *args)
35
- end
36
- end
22
+ def self.method_definitions_for(machine, name)
23
+ returning({}) do |method_definitions|
24
+ simple_events, complex_events = machine.events.partition &:simple?
37
25
 
38
- # complex event methods
39
- # the first argument is the target state
40
- # any remaining arguments are passed into the transition / transition query
41
-
42
- # object.event_name [:target], *arguments
43
- #
44
- # returns a new transition. Will raise an IllegalTransition if
45
- # it is not given arguments which result in a valid combination
46
- # of event and target state being deducted.
47
- #
48
- # object.event_name [nil] suffices if the event has only one valid
49
- # target (ie only one transition which would not raise a
50
- # RequirementError if fired)
51
-
52
- # object.event_name! [:target], *arguments
53
- #
54
- # as per the method above, except that it also fires the event
55
-
56
- # object.can_event_name? [:target], *arguments
57
- #
58
- # tests that calling event_name or event_name! would not raise an error
59
- # ie, the transition is legal and is valid with the arguments supplied
60
-
61
- complex_events.each do |event|
62
- method_definitions["#{event.name}"] = lambda do |target, *args|
63
- _binding.find_transition(event, target, *args)
64
- end
65
-
66
- method_definitions["can_#{event.name}?"] = lambda do |target, *args|
67
- begin
68
- t = _binding.find_transition(event, target, *args)
69
- t.valid?
70
- rescue IllegalTransition
71
- false
26
+ # simple event methods
27
+ # all arguments are passed into the transition / transition query
28
+
29
+ simple_events.each do |event|
30
+ method_definitions["#{event.name}"] = lambda do |*args|
31
+ state_fu(name).find_transition(event, event.target, *args)
72
32
  end
73
- end
74
33
 
75
- method_definitions["#{event.name}!"] = lambda do |target, *args|
76
- _binding.fire_transition!(event, target, *args)
34
+ method_definitions["can_#{event.name}?"] = lambda do |*args|
35
+ state_fu(name).can_transition?(event, event.target, *args)
36
+ end
37
+
38
+ method_definitions["#{event.name}!"] = lambda do |*args|
39
+ state_fu(name).fire_transition!(event, event.target, *args)
40
+ end
77
41
  end
78
- end
79
-
80
- # methods dedicated to a combination of event and target
81
- # all arguments are passed into the transition / transition query
82
-
83
- (simple_events + complex_events).each do |event|
84
- event.targets.each do |target|
85
- method_definitions["#{event.name}_to_#{target.name}"] = lambda do |*args|
86
- _binding.find_transition(event, target, *args)
42
+
43
+ # complex event methods
44
+ # the first argument is the target state
45
+ # any remaining arguments are passed into the transition / transition query
46
+
47
+ # object.event_name [:target], *arguments
48
+ #
49
+ # returns a new transition. Will raise an IllegalTransition if
50
+ # it is not given arguments which result in a valid combination
51
+ # of event and target state being deducted.
52
+ #
53
+ # object.event_name [nil] suffices if the event has only one valid
54
+ # target (ie only one transition which would not raise a
55
+ # RequirementError if fired)
56
+
57
+ # object.event_name! [:target], *arguments
58
+ #
59
+ # as per the method above, except that it also fires the event
60
+
61
+ # object.can_event_name? [:target], *arguments
62
+ #
63
+ # tests that calling event_name or event_name! would not raise an error
64
+ # ie, the transition is legal and is valid with the arguments supplied
65
+
66
+ complex_events.each do |event|
67
+ method_definitions["#{event.name}"] = lambda do |target, *args|
68
+ state_fu(name).find_transition(event, target, *args)
87
69
  end
88
70
 
89
- method_definitions["can_#{event.name}_to_#{target.name}?"] = lambda do |*args|
90
- _binding.can_transition?(event, target, *args)
71
+ method_definitions["can_#{event.name}?"] = lambda do |target, *args|
72
+ begin
73
+ t = state_fu(name).find_transition(event, target, *args)
74
+ t.valid?
75
+ rescue IllegalTransition
76
+ false
77
+ end
91
78
  end
92
79
 
93
- method_definitions["#{event.name}_to_#{target.name}!"] = lambda do |*args|
94
- _binding.fire_transition!(event, target, *args)
95
- end
96
- end unless event.targets.nil?
97
- end
98
-
99
- machine.states.each do |state|
100
- method_definitions["#{state.name}?"] = lambda do
101
- _binding.current_state == state
80
+ method_definitions["#{event.name}!"] = lambda do |target, *args|
81
+ state_fu(name).fire_transition!(event, target, *args)
82
+ end
83
+ end
84
+
85
+ # methods dedicated to a combination of event and target
86
+ # all arguments are passed into the transition / transition query
87
+
88
+ (simple_events + complex_events).each do |event|
89
+ event.targets.each do |target|
90
+ method_definitions["#{event.name}_to_#{target.name}"] = lambda do |*args|
91
+ state_fu(name).find_transition(event, target, *args)
92
+ end
93
+
94
+ method_definitions["can_#{event.name}_to_#{target.name}?"] = lambda do |*args|
95
+ state_fu(name).can_transition?(event, target, *args)
96
+ end
97
+
98
+ method_definitions["#{event.name}_to_#{target.name}!"] = lambda do |*args|
99
+ state_fu(name).fire_transition!(event, target, *args)
100
+ end
101
+ end unless event.targets.nil?
102
+ end
103
+
104
+ machine.states.each do |state|
105
+ method_definitions["#{state.name}?"] = lambda do
106
+ state_fu(name).current_state == state
107
+ end
102
108
  end
103
109
  end
104
-
105
- end
106
-
110
+ end
107
111
 
108
112
  #
109
113
  # Class Methods
@@ -115,71 +119,25 @@ module StateFu
115
119
  # Note this happens when a machine is first bound to the class,
116
120
  # not when StateFu is included.
117
121
 
118
- def self.prepare_class(klass)
119
- raise caller.inspect
120
- self.define_once_only_method_missing(klass)
121
- end # prepare_class
122
+ def self.prepare_class_machine(klass, machine, name, options)
123
+ return unless options[:define_methods]
122
124
 
123
- # When triggered, method_missing will first call state_fu!,
124
- # instantating all bindings & installing their attendant
125
- # MethodFactories, then check if the object now responds to the
126
- # missing method name; otherwise it will call the original
127
- # method_missing.
128
- #
129
- # method_missing will then revert to its original implementation.
130
- #
131
- # The purpose of all this is to allow dynamically created methods
132
- # to be called, without worrying about whether they have been
133
- # defined yet, and without incurring the expense of loading all
134
- # the object's StateFu::Bindings before they're likely to be needed.
135
- #
136
- # Note that if you redefine method_missing on your StateFul
137
- # classes, it's best to either do it before you include StateFu,
138
- # or thoroughly understand what's happening in
139
- # MethodFactory#define_once_only_method_missing.
140
-
141
- def self.define_once_only_method_missing(klass)
142
- raise ArgumentError.new(klass.to_s) unless klass.is_a?(Class)
143
-
144
- klass.class_eval do
145
- return false if @_state_fu_prepared
146
- @_state_fu_prepared = true
147
-
148
- alias_method(:method_missing_before_state_fu, :method_missing) # if defined?(:method_missing, true)
149
-
150
- def method_missing(method_name, *args, &block)
151
- # invoke state_fu! to ensure event, etc methods are defined
152
- begin
153
- state_fu! unless defined? initialize_state_fu!
154
- rescue NoMethodError => e
155
- raise e
156
- end
157
-
158
- # reset method_missing for this instance
159
- class << self; self; end.class_eval do
160
- alias_method :method_missing, :method_missing_before_state_fu
125
+ method_definitions_for(machine, name).each do |method_name, block|
126
+ unless klass.respond_to? method_name, true
127
+ klass.class_eval do
128
+ define_method method_name, &block
161
129
  end
162
-
163
- # call the newly defined method, or the original method_missing
164
- if respond_to? method_name, true
165
- # it was defined by calling state_fu!, which instantiated bindings
166
- # for its state machines, which defined singleton methods for its
167
- # states & events when it was constructed.
168
- __send__ method_name, *args, &block
169
- else
170
- # call the original method_missing (method_missing_before_state_fu)
171
- method_missing method_name, *args, &block
172
- end
173
- end # method_missing
174
- end # class_eval
175
- end # define_once_only_method_missing
130
+ end
131
+ end
132
+
133
+ end # prepare_class
176
134
 
177
135
  # Define the same helper methods on the StateFu::Binding and its
178
136
  # object. Any existing methods will not be tampered with, but a
179
137
  # warning will be issued in the logs if any methods cannot be defined.
180
138
  def install!
181
- define_event_methods_on @binding
182
- define_event_methods_on @binding.object if @binding.options[:define_methods]
139
+ define_event_methods_on @binding
140
+ define_event_methods_on @binding.object if @binding.options[:define_methods] && @binding.options[:singleton]
183
141
  end
184
142
 
185
143
  #
@@ -225,7 +183,7 @@ module StateFu
225
183
  def self.define_singleton_method(object, method_name, options={}, &block)
226
184
  if object.respond_to? method_name, true
227
185
  msg = !options[:force]
228
- Logger.info "Existing method #{method(method_name) rescue [method_name].inspect} "\
186
+ Logging.info "Existing method #{method(method_name) rescue [method_name].inspect} "\
229
187
  "for #{object.class} #{object} "\
230
188
  "#{options[:force] ? 'WILL' : 'won\'t'} "\
231
189
  "be overwritten."
@@ -237,7 +195,7 @@ module StateFu
237
195
  end
238
196
  end
239
197
  alias_method :define_singleton_method, :define_singleton_method
240
-
198
+
241
199
  end # class MethodFactory
242
200
  end # module StateFu
243
201