Module: Sequel::Plugins::StateMachine::InstanceMethods

Defined in:
lib/sequel/plugins/state_machine.rb

Instance Method Summary collapse

Instance Method Details

#audit(message, reason: nil, machine: nil) ⇒ Object



120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/sequel/plugins/state_machine.rb', line 120

def audit(message, reason: nil, machine: nil)
  audlog = self.current_audit_log(machine: machine)
  if audlog.class.state_machine_messages_supports_array
    audlog.messages ||= []
    audlog.messages << message
  else
    audlog.messages ||= ""
    audlog.messages += (audlog.messages.empty? ? message : (message + "\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.



152
153
154
155
# File 'lib/sequel/plugins/state_machine.rb', line 152

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



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/sequel/plugins/state_machine.rb', line 133

def audit_one_off(event, messages, reason: nil, machine: nil)
  messages = [messages] unless messages.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.state_machine_messages_supports_array ? messages : messages.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



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/sequel/plugins/state_machine.rb', line 86

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_saved = self.audit_logs.find do |a|
    a.sequel_state_machine_get(:event) == transition.event.to_s &&
      a.sequel_state_machine_get(:from_state) == transition.from &&
      a.sequel_state_machine_get(:to_state) == transition.to
  end
  if last_saved
    StateMachines::Sequel.log(self, :debug, "updating_audit_log", {audit_log_id: last_saved.id})
    last_saved.update(**last_saved.sequel_state_machine_map_columns(
      at: Time.now,
      actor: StateMachines::Sequel.current_actor,
      messages: current.messages,
      reason: current.reason,
    ))
  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.



170
171
172
173
174
# File 'lib/sequel/plugins/state_machine.rb', line 170

def must_process(event, *args)
  success = self.process(event, *args)
  raise StateMachines::Sequel::FailedTransition.new(self, event) unless success
  return self
end

#new_audit_logObject



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.



160
161
162
163
164
165
166
167
# File 'lib/sequel/plugins/state_machine.rb', line 160

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.



180
181
182
183
184
185
186
# File 'lib/sequel/plugins/state_machine.rb', line 180

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.



189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/sequel/plugins/state_machine.rb', line 189

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



204
205
206
207
208
209
210
211
# File 'lib/sequel/plugins/state_machine.rb', line 204

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