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
-
.included(base) ⇒ Object
Classes that include Mixin will also be extended by ClassMethods.
Instance Method Summary collapse
-
#attribute_name ⇒ String
The attribute name used for storing the sate value.
-
#collins_client ⇒ Collins::Client
abstract
Collins client.
-
#expired?(asset) ⇒ Boolean
Has the specified asset state expired?.
-
#finished?(asset) ⇒ Boolean
Whether we are done processing or not.
-
#logger ⇒ Logger
abstract
Logger instance.
-
#method_missing(method, *args, &block) ⇒ Object
Allow registered events to be executed.
-
#plan(asset) ⇒ Array<Array<Symbol,String>>
The things that would be done if the asset was transitioned.
-
#reset!(asset) ⇒ Boolean
Reset (delete) the attribute once the process is complete.
-
#respond_to?(method) ⇒ Boolean
method_missing.
-
#state_name(asset) ⇒ Symbol
Return the name of the current sate.
-
#state_specification(asset) ⇒ Collins::State::Specification
Get the state specification associated with the asset.
-
#transition(asset, options = {}) ⇒ Collins::State::Specification
Transition the asset to the next appropriate state.
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] = args[1] if not (asset.is_a?(Collins::Asset) || asset.is_a?(String)) then return super end if not .nil? and not .is_a?(Hash) then return super elsif .nil? then = {} 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, ) 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_name ⇒ String
we append _json to the managed_state_name since it will serialize this way
The attribute name used for storing the sate value
31 32 33 |
# File 'lib/collins/state/mixin.rb', line 31 def attribute_name self.class.managed_state_name.to_s + "_json" end |
#collins_client ⇒ Collins::Client
Classes mixing this in must supply a collins client
Returns collins client.
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
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.
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
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 |
#logger ⇒ Logger
Classes mixing this in must supply a logger
Returns logger instance.
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
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
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
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
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
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.
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, = {} 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], ) 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, ) 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") } = Collins::Option(self.class.).get_or_else({}) run_event(asset, init, ) } end |