Module: Sequel::Plugins::StateMachine::InstanceMethods
- Defined in:
- lib/sequel/plugins/state_machine.rb
Instance Method Summary collapse
- #audit(message, reason: nil, machine: nil) ⇒ Object
-
#audit_logs_for(machine) ⇒ Object
Return audit logs for the given state machine name.
- #audit_one_off(event, messages, reason: nil, machine: nil) ⇒ Object
-
#commit_audit_log(transition) ⇒ Object
Commit pending changes to the audit log.
- #current_audit_log(machine: nil) ⇒ Object
-
#must_process(event, *args) ⇒ Object
Same as process, but raises an error if the transition fails.
- #new_audit_log ⇒ Object
-
#process(event, *args) ⇒ Object
Send event with arguments inside of a transaction, save the changes to the receiver, and return the transition result.
-
#process_if(event, *args) ⇒ Object
Same as must_process, but takes a lock, and calls the given block, only doing actual processing if the block returns true.
- #sequel_state_machine_status(machine = nil) ⇒ Object
-
#valid_state_path_through?(event, machine: nil) ⇒ Boolean
Return true if the given event can be transitioned into by the current state.
- #validates_state_machine(machine: nil) ⇒ Object
Instance Method Details
#audit(message, reason: nil, machine: nil) ⇒ Object
131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/sequel/plugins/state_machine.rb', line 131 def audit(, reason: nil, machine: nil) audlog = self.current_audit_log(machine: machine) if audlog.class. audlog. ||= [] audlog. << else audlog. ||= "" audlog. += (audlog..empty? ? : ("\n" + )) end audlog.reason = reason if reason return audlog end |
#audit_logs_for(machine) ⇒ Object
Return audit logs for the given state machine name. Only useful for multi-state-machine models.
163 164 165 166 |
# File 'lib/sequel/plugins/state_machine.rb', line 163 def audit_logs_for(machine) lines = self.send(self.class.instance_variable_get(:@sequel_state_machine_audit_logs_association)) return lines.select { |ln| ln.sequel_state_machine_get(:machine_name) == machine.to_s } end |
#audit_one_off(event, messages, reason: nil, machine: nil) ⇒ Object
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/sequel/plugins/state_machine.rb', line 144 def audit_one_off(event, , reason: nil, machine: nil) = [] unless .respond_to?(:to_ary) audlog = self.new_audit_log mapped_values = audlog.sequel_state_machine_map_columns( at: Time.now, event: event, from_state: self.sequel_state_machine_status(machine), to_state: self.sequel_state_machine_status(machine), messages: audlog.class. ? : .join("\n"), reason: reason || "", actor: StateMachines::Sequel.current_actor, machine_name: machine, ) audlog.set(mapped_values) return self.add_audit_log(audlog) end |
#commit_audit_log(transition) ⇒ Object
Commit pending changes to the audit log. This involves either:
-
Updating the last audit log step, if it matches our current criteria (event, from state, to state),
-
or creating a new audit log entry.
This ensures that we have the following behavior:
-
Failed transitions - ie where from and to state are the same - do not add multiple audit log steps. Only the latest failed transition is recorded.
-
Successful transitions are always recorded. If we transition, a->b->c, and then reset the state machine to ‘a’ and transition a->b->c again, we’d end up with 5 transitions (a->b->c->a->b->c).
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/sequel/plugins/state_machine.rb', line 96 def commit_audit_log(transition) machine = self.class.state_machines.length > 1 ? transition.machine.name : nil StateMachines::Sequel.log( self, :debug, "committing_audit_log", {transition: transition, state_machine: machine}, ) current = self.current_audit_log(machine: machine) last_log = self.audit_logs.last can_update_last = last_log && last_log.sequel_state_machine_get(:event) == transition.event.to_s && last_log.sequel_state_machine_get(:from_state) == transition.from && last_log.sequel_state_machine_get(:to_state) == transition.to if can_update_last StateMachines::Sequel.log(self, :debug, "updating_audit_log", {audit_log_id: last_log.id}) mapped_values = last_log.sequel_state_machine_map_columns( at: Time.now, actor: StateMachines::Sequel.current_actor, messages: current. || (current.class. ? [] : ""), reason: current.reason, ) last_log.update(**mapped_values) else StateMachines::Sequel.log(self, :debug, "creating_audit_log", {}) current.set(**current.sequel_state_machine_map_columns( at: Time.now, actor: StateMachines::Sequel.current_actor, event: transition.event.to_s, from_state: transition.from, to_state: transition.to, )) self.add_audit_log(current) end @current_audit_logs[machine] = nil end |
#current_audit_log(machine: nil) ⇒ Object
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/sequel/plugins/state_machine.rb', line 66 def current_audit_log(machine: nil) @current_audit_logs ||= {} alog = @current_audit_logs[machine] if alog.nil? StateMachines::Sequel.log(self, :debug, "preparing_audit_log", {}) alog = self.new_audit_log if machine machine_name_col = alog.class.state_machine_column_mappings[:machine_name] unless alog.respond_to?(machine_name_col) msg = "Audit logs must have a :machine_name field for multi-machine models or if specifying :machine." raise InvalidConfiguration, msg end alog.sequel_state_machine_set(:machine_name, machine) end @current_audit_logs[machine] = alog alog.sequel_state_machine_set(:reason, "") end return alog end |
#must_process(event, *args) ⇒ Object
Same as process, but raises an error if the transition fails.
181 182 183 184 185 |
# File 'lib/sequel/plugins/state_machine.rb', line 181 def must_process(event, *args) success = self.process(event, *args) raise StateMachines::Sequel::FailedTransition.new(self, event) unless success return self end |
#new_audit_log ⇒ Object
54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/sequel/plugins/state_machine.rb', line 54 def new_audit_log assoc_name = self.class.instance_variable_get(:@sequel_state_machine_audit_logs_association) unless (audit_log_assoc = self.class.association_reflections[assoc_name]) msg = "Association for audit logs '#{assoc_name}' does not exist. " \ "Your model must have 'one_to_many :audit_logs' for its audit log lines, " \ "or pass the :audit_logs_association parameter to the plugin to define its association name." raise InvalidConfiguration, msg end audit_log_cls = audit_log_assoc[:class] || Kernel.const_get(audit_log_assoc[:class_name]) return audit_log_cls.new end |
#process(event, *args) ⇒ Object
Send event with arguments inside of a transaction, save the changes to the receiver, and return the transition result. Used to ensure the event processing happens in a transaction and the receiver is saved.
171 172 173 174 175 176 177 178 |
# File 'lib/sequel/plugins/state_machine.rb', line 171 def process(event, *args) self.db.transaction do self.lock! result = self.send(event, *args) self.save_changes return result end end |
#process_if(event, *args) ⇒ Object
Same as must_process, but takes a lock, and calls the given block, only doing actual processing if the block returns true. If the block returns false, it acts as a success. Used to avoid issues concurrently processing the same object through the same state.
191 192 193 194 195 196 197 |
# File 'lib/sequel/plugins/state_machine.rb', line 191 def process_if(event, *args) self.db.transaction do self.lock! return self unless yield(self) return self.must_process(event, *args) end end |
#sequel_state_machine_status(machine = nil) ⇒ Object
50 51 52 |
# File 'lib/sequel/plugins/state_machine.rb', line 50 def sequel_state_machine_status(machine=nil) return self.send(self.state_machine_status_column(machine)) end |
#valid_state_path_through?(event, machine: nil) ⇒ Boolean
Return true if the given event can be transitioned into by the current state.
200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
# File 'lib/sequel/plugins/state_machine.rb', line 200 def valid_state_path_through?(event, machine: nil) current_state_str = self.sequel_state_machine_status(machine).to_s current_state_sym = current_state_str.to_sym sm = find_state_machine(machine) event_obj = sm.events[event] or raise ArgumentError, "Invalid event #{event} (available #{sm.name} events: #{sm.events.keys.join(', ')})" event_obj.branches.each do |branch| branch.state_requirements.each do |state_req| next unless (from = state_req[:from]) return true if from.matches?(current_state_str) || from.matches?(current_state_sym) end end return false end |
#validates_state_machine(machine: nil) ⇒ Object
215 216 217 218 219 220 221 222 |
# File 'lib/sequel/plugins/state_machine.rb', line 215 def validates_state_machine(machine: nil) state_machine = find_state_machine(machine) states = state_machine.states.map(&:value) state = self.sequel_state_machine_status(state_machine.attribute) return if states.include?(state) self.errors.add(self.state_machine_status_column(machine), "state '#{state}' must be one of (#{states.sort.join(', ')})",) end |