state-fu 0.12.3 → 0.13.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/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