Module: Collins::State::Mixin

Included in:
PersistentState
Defined in:
lib/collins/state/mixin.rb,
lib/collins/state/mixin_class_methods.rb

Defined Under Namespace

Modules: ClassMethods

Class Method Summary collapse

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args, &block) ⇒ Object

Allow registered events to be executed. Allow predicate calls to respond true if the asset is currently in the specified state or false otherwise



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/collins/state/mixin.rb', line 197

def method_missing method, *args, &block
  if args.length == 0 then
    return super
  end
  asset = args[0]
  options = args[1]
  if not (asset.is_a?(Collins::Asset) || asset.is_a?(String)) then
    return super
  end
  if not options.nil? and not options.is_a?(Hash) then
    return super
  elsif options.nil? then
    options = {}
  end
  question_only = method.to_s.end_with?('?')
  if question_only then
    meth = method.to_s[0..-2].to_sym # drop ? at end
    if event?(meth) then
      state_name(asset) == meth
    else
      false
    end
  elsif event?(method) then
    run_event(asset, method, options)
  else
    super
  end
end

Class Method Details

.included(base) ⇒ Object

Classes that include Mixin will also be extended by ClassMethods



9
10
11
# File 'lib/collins/state/mixin.rb', line 9

def included(base)
  base.extend Collins::State::Mixin::ClassMethods
end

Instance Method Details

#attribute_nameString

Note:

we append _json to the managed_state_name since it will serialize this way

The attribute name used for storing the sate value

Returns:

  • (String)

    the key to use for storing the state value on an asset



31
32
33
# File 'lib/collins/state/mixin.rb', line 31

def attribute_name
  self.class.managed_state_name.to_s + "_json"
end

#collins_clientCollins::Client

This method is abstract.

Classes mixing this in must supply a collins client

Returns collins client.

Returns:

  • (Collins::Client)

    collins client

Raises:

  • (NotImplementedError)

    if not specified



17
18
19
# File 'lib/collins/state/mixin.rb', line 17

def collins_client
  raise NotImplementedError.new("no collins client available")
end

#expired?(asset) ⇒ Boolean

Note:

This method will return true if no specification is associated with the asset

Has the specified asset state expired?

Read the state from the specified asset, and determine whether the state is expired yet or not. The timestamp + expiration is compared to now.

Parameters:

  • asset (Collins::Asset)

    The asset to look at

Returns:

  • (Boolean)

    True if the specification has expired, false otherwise



43
44
45
# File 'lib/collins/state/mixin.rb', line 43

def expired? asset
  specification_expired?(state_specification(asset))
end

#finished?(asset) ⇒ Boolean

Whether we are done processing or not

Parameters:

  • asset (Collins::Asset)

Returns:

  • (Boolean)

    whether asset is in done state or not



50
51
52
53
54
# File 'lib/collins/state/mixin.rb', line 50

def finished? asset
  state_specification(asset).to_option.map do |spec|
    specification_expired?(spec) && event(spec.name)[:terminus]
  end.get_or_else(false)
end

#loggerLogger

This method is abstract.

Classes mixing this in must supply a logger

Returns logger instance.

Returns:

  • (Logger)

    logger instance

Raises:

  • (NotImplementedError)

    if not specified



24
25
26
# File 'lib/collins/state/mixin.rb', line 24

def logger
  raise NotImplementedError.new("no logger available")
end

#plan(asset) ⇒ Array<Array<Symbol,String>>

The things that would be done if the asset was transitioned

Parameters:

  • asset (Collins::Asset)

Returns:

  • (Array<Array<Symbol,String>>)

    array of arrays. Each sub array has two elements



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/collins/state/mixin.rb', line 59

def plan asset
  plans = []
  state_specification(asset).to_option.map { |specification|
    event(specification.name).to_option.map { |ev|
      if not specification_expired?(specification) then
        plans << [:noop, "not yet expired"]
      else
        if ev[:transition] then
          event(ev[:transition]).to_option.map { |ev2|
            plans << [:event, ev2.name]
          }.get_or_else {
            plans << [:exception, "invalid event name #{ev[:transition]}"]
          }
        elsif not ev[:on_transition] then
          plans << [:noop, "no transition specified, and no on_transition action specified"]
        end
        if ev[:on_transition] then
          action(ev[:on_transition]).to_option.map { |ae|
            plans << [:action, ae.name]
          }.get_or_else {
            plans << [:exception, "invalid action specified #{ev[:on_transition]}"]
          }
        end
      end
    }.get_or_else {
      plans << [:exception, "invalid event name #{e.name}"]
    }
  }.get_or_else {
    Collins::Option(initial).map { |init|
      event(init).to_option.map { |ev|
        if ev[:before_transition] then
          action(ev[:before_transition]).to_option.map do |act|
            plans << [:action, act.name]
          end.get_or_else {
            plans << [:exception, "action #{ev[:before_transition]} not defined"]
          }
        end
        plans << [:event, init]
      }.get_or_else {
        plans << [:exception, "initial state #{init} is undefined"]
      }
    }.get_or_else {
      plans << [:exception, "no initial state defined"]
    }
  }
  plans
end

#reset!(asset) ⇒ Boolean

Reset (delete) the attribute once the process is complete

Parameters:

  • asset (Collins::Asset)

    The asset on which to delete the attribute

Returns:

  • (Boolean)

    True if the value was successfully deleted



111
112
113
# File 'lib/collins/state/mixin.rb', line 111

def reset! asset
  collins_client.delete_attribute! asset, attribute_name
end

#respond_to?(method) ⇒ Boolean

method_missing

Returns:

  • (Boolean)


226
227
228
229
230
231
232
233
234
235
236
# File 'lib/collins/state/mixin.rb', line 226

def respond_to? method
  question_only = method.to_s.end_with?('?')
  if question_only then
    method = method.to_s[0..-2] # drop? at end
  end
  if not event?(method) then
    super
  else
    true
  end
end

#state_name(asset) ⇒ Symbol

Return the name of the current sate. Will be :None if not initialized

Parameters:

  • asset (Collins::Asset)

Returns:

  • (Symbol)

    state name



118
119
120
# File 'lib/collins/state/mixin.rb', line 118

def state_name asset
  state_specification(asset).name
end

#state_specification(asset) ⇒ Collins::State::Specification

Get the state specification associated with the asset

Parameters:

  • asset (Collins::Asset)

    The asset to retrieve

Returns:



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/collins/state/mixin.rb', line 126

def state_specification asset
  updated = asset_from_cache asset
  result = updated.send(attribute_name.to_sym)
  if result then
    res = JSON.parse(result, :create_additions => false) rescue nil
    if res.is_a?(Hash) and res.key?('data') then
      res = ::Collins::State::Specification.json_create(res)
    end

    if res.is_a?(::Collins::State::Specification) then
      res
    else
      logger.warn("Could not deserialize #{result} to a State Specification")
      ::Collins::State::Specification.empty
    end
  else
    ::Collins::State::Specification.empty
  end
end

#transition(asset, options = {}) ⇒ Collins::State::Specification

Transition the asset to the next appropriate state

This method will either initialize the state on the specified asset (if needed), or process the current state. If processing the current state, the expiration time will be checked, followed by running any specified transition event, followed by any ‘on_transition` action. The transition event is run before the `on_transition` action, because the `on_transition` action should only be called if we have successfully transitioned to a new state. In the event that the transition event has a `before_transition` defined that fails, we don’t want to execute the ‘on_transition` code since we have not yet successfully transitioned.

Parameters:

  • asset (Collins::Asset)

    The asset to transition

  • options (Hash) (defaults to: {})

    Transition options

Options Hash (options):

  • :quiet (Boolean)

    Don’t throw an exception if a transition fails

Returns:

Raises:

  • (CollinsError)

    if state needs to be initialized and no ‘:initial` key is found in the manage_state options hash

  • (CollinsError)

    if a specification is found on the asset, but the named state isn’t found as a registered event

  • (CollinsError)

    if an action is specified as a ‘:before_transition` or `:on_transition` value but not registered



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/collins/state/mixin.rb', line 163

def transition asset, options = {}
  state_specification(asset).to_option.map { |specification|
    event(specification.name).to_option.or_else {
      raise CollinsError.new("no event defined with name #{specification.name}")
    }.filter { |e| specification_expired?(specification) }.map { |e|
      if e[:transition] then
        spec = run_event(asset, e[:transition], options)
        run_action(asset, e[:on_transition]) if e[:on_transition]
        # If we transitioned and no expiration is set, rerun
        if specification_expired?(spec) and spec.name != e.name then
          transition(asset, options)
        else
          spec
        end
      else
        logger.debug("No transition event specified for #{e.name}")
        run_action(asset, e[:on_transition], :log => true) if e[:on_transition]
        specification
      end
    }.get_or_else {
      logger.trace("Specification #{specification.name} not yet expired")
      specification
    }
  }.get_or_else {
    init = Collins::Option(initial).get_or_else {
      raise Collins::CollinsError.new("no initial state defined for transition")
    }
    options = Collins::Option(self.class.managed_state_options).get_or_else({})
    run_event(asset, init, options)
  }
end