Class: WebFlow::FlowStep

Inherits:
Object
  • Object
show all
Defined in:
lib/webflow/flow_step.rb

Overview

The FlowStep is a superclass from which every step type inherits. It’s methods are therefore available to any step type present in the WebFlow framework.

Mapping instructions

The mapping instructions common to every step type are the following.

on

The on instruction is used to map a returned event name to a subsequent step. Here’s a simple example of it’s usage.

action_step :example_step do

  (...)

  on :success => :next_step_name

  on :back => :previous_step_name

end

In the previous example, the example step will route the flow to the step named next_step_name if the step definition returns an event which is named success. If the step definition returns a back event, the flow will then call the previous_step_name step.

upon

The upon instruction is used to map a raised error class to a subsequent step. Here’s a simple example of it’s usage.

action_step :example_step do

  (...)

  upon :StandardError => :next_step_name

end

In the previous example, the example step will route the flow to the step named next_step_name if the step definition raises an error which is a kind of StandardError. If the upon instruction is used more than once, the first declaration has priority. Also note that the kind_of? method is used to validate the correspondence, so subclasses of mapped error classes will be included as well.

method

The method instruction is meant to override the default step definition name. By default, the definition of a given step has to be declared with the same name as the step name in the mapping. If a step is named my_jolly_step, the controller has to implement the step logic with a def block which is named my_jolly_step.

The method instruction will tell the WebFlow framework to look for a definition of the given value instead of looking for the same name. This allows to reuse business logic and decouple the mapping from the step implementation. One could then do something like :

class MyController < WebFlow::Base

  def initialize

    (...)

    action_step :step_1 do
      method :shared_implementation
      (...)
    end

    action_step :step_2 do
      method :shared_implementation
      (...)
    end
  end

  def shared_implementation
    (...)
  end

end

Developer infos

This class defines a basic skeleton for different step types used by the WebFlow framework. All new step types must inherit of this superclass to be usable in the WebFlow controller.

It is also the responsibility of any subclass to add it’s declaration method in the WebFlow::Base class. See view_step.rb for an example.

Also, subclasses can change the @definition_required instance variable value to tell the WebFlow framework that it can handle the execution without the user controller defining explicitly a step implementation. See view_step.rb(initialize) for an example.

Event outcomes are defined in the @outcomes instance variable while the error handlers are defined in the @handlers instance variable. Those variables are protected, so any subclasses of FlowStep can access them and hack the mechanism if required.

Direct Known Subclasses

ActionStep, ViewStep

Instance Method Summary collapse

Instance Method Details

#definition_required?Boolean

Used to know if the step needs an explicitly declared step definition or if it can manage the task with a default behavior.

Returns:

  • (Boolean)


150
151
152
153
154
155
156
157
158
159
# File 'lib/webflow/flow_step.rb', line 150

def definition_required?
  # We have to distinguish the false value from the non
  # existence of the variable. Therefore the ||= operator
  # can't help us.
  if @definition_required.class.kind_of?(NilClass)
   @definition_required = true
  else
    @definition_required
  end
end

#execute(*args) ⇒ Object

Method signature to override in implementing classes. Does the ‘dirty job’ once it is required.

Each implementing step HAS TO VERIFY THE VALUE OF THE lookup_method_to_call method. Or else, the ‘method’ instruction won’t be respected if the user has used the ‘method’ instruction.

Raises:



141
142
143
144
145
# File 'lib/webflow/flow_step.rb', line 141

def execute(*args)
  
  raise(WebFlowError.new, "You can't directly use FlowStep as a step. As a matter of fact, I don't even know how you were able in the first place anyways...")
  
end

#handler(error_class) ⇒ Object

Returns the appropriate step name to execute upon the given error class



275
276
277
278
# File 'lib/webflow/flow_step.rb', line 275

def handler(error_class)
  error_class.kind_of?(String) ? key = error_class : key = error_class.to_s
  handlers.has_key?(key) ? handlers.fetch(key) : raise(WebFlowError.new, "There's no handler defined for the error class '#{key}'.")
end

#handles?(error_class) ⇒ Boolean

Tells if the error class passed as an argument is handled

Returns:

  • (Boolean)


267
268
269
# File 'lib/webflow/flow_step.rb', line 267

def handles?(error_class)
  handlers.has_key?(error_class)
end

#has_an_outcome_for?(event_name) ⇒ Boolean

Tells if the given event name is a possibe outcome of this step

Returns:

  • (Boolean)


282
283
284
# File 'lib/webflow/flow_step.rb', line 282

def has_an_outcome_for?(event_name)
  outcomes.has_key? event_name.to_s
end

#method(method_name) ⇒ Object

Maps a method name to execute instead of searching the controller for a method name who is the same as the step name.

Use it as :

action_step :my_step do
  method :call_this_instead
end


259
260
261
262
263
# File 'lib/webflow/flow_step.rb', line 259

def method(method_name)

  @_method = method_name.to_s
  
end

#on(options) ⇒ Object

Maps an event to a method call. Use in a mapping like this :

implemented_step_type :id_of_step do

on :event => :method

end

This is also possible :

implemented_step_type :id_of_step do

on { :event => :method,
     :event2 => :method2 }

end

Raises:



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/webflow/flow_step.rb', line 176

def on(options)

  # Make sure we received a hash
  raise(WebFlowError.new, "The 'on' method takes a Hash object as a parameter.") unless options.kind_of? Hash

  # Make sure the key is not used twice in a definition
  # to enforce coherence of the mapping.
  options.each_key do |key|
    raise(WebFlowError.new, "An event in this step scope already uses the name '#{key}'.") if outcomes.has_key?(key.to_s)
    raise(WebFlowError.new, "#{key} is a reserved event keyword.") if WebFlow::Base.reserved_event?(key.to_s)
  end

  # Store the values in the possible outcomes hash
  options.each { |key,value| outcomes[key.to_s] = value.to_s }

end

#outcome(event_name) ⇒ Object

Returns the step name associated to the given event.



288
289
290
# File 'lib/webflow/flow_step.rb', line 288

def outcome(event_name)
  outcomes.has_key?(event_name.to_s) ? outcomes.fetch(event_name.to_s) : raise(WebFlowError.new, "There's no outcome defined for the event name '#{event_name}'.")
end

#upon(hash) ⇒ Object

Maps an error class to a step name to execute. Use in a mapping like this :

implemented_step_type :id_of_step do
  upon :WebFlowError => :step_name
end

This is also possible :

implemented_step_type :id_of_step do
  upon { :WebFlowError => :step_name,
         :WhateverError => :step_name }
end

Only subclasses of StandardError will be rescued. This means that RuntimeError cannot be handled.

Raises:



232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/webflow/flow_step.rb', line 232

def upon(hash)

  # Make sure we received a hash
  raise(WebFlowError.new, "The 'upon' method takes a Hash object as a parameter. Go back to the API documents since you've obviously didn't read them well enough...") unless hash.kind_of? Hash

  # Make sure the key is not used twice in a definition
  # to enforce coherence of the mapping.
  hash.each_key do |key|
    raise(WebFlowError.new, "An error of the class '#{key}' is already mapped. They must be unique within a step scope.") if handlers.has_key?(key.to_s)
  end

  # Store the values in the handlers hash
  hash.each { |key,value| handlers.store key.to_s, value.to_s }

end