Module: Signoff::Model
Overview
The behaviour mixed into ActiveRecord models via include Signoff. It provides the signoff class macro, the transition methods (submit!, approve!, reject!), state predicates, query scopes and the audit-trail helpers.
Instance Method Summary collapse
-
#approve!(user: nil, comment: nil, metadata: {}, to: nil, ip_address: nil, user_agent: nil) ⇒ Object
Advance the record forward and record an “approve” event.
- #approved? ⇒ Boolean
-
#approved_by ⇒ Object
The user that moved the record into a successful terminal state.
-
#can_approve?(user = nil) ⇒ Boolean
— authorization predicates —————————————.
- #can_reject?(user = nil) ⇒ Boolean
-
#current_state ⇒ Object
The current workflow state as a Symbol.
- #last_approval ⇒ Object
- #last_rejection ⇒ Object
- #pending? ⇒ Boolean
-
#reject!(user: nil, comment: nil, metadata: {}, ip_address: nil, user_agent: nil) ⇒ Object
Move the record to the configured reject state and record a “reject” event.
- #rejected? ⇒ Boolean
-
#submit!(user: nil, comment: nil, metadata: {}, to: nil, ip_address: nil, user_agent: nil) ⇒ Object
Advance the record forward and record a “submit” event.
-
#workflow_history ⇒ Object
The full, chronologically ordered event history (preloadable via includes(:signoff_events) to avoid N+1 queries).
Instance Method Details
#approve!(user: nil, comment: nil, metadata: {}, to: nil, ip_address: nil, user_agent: nil) ⇒ Object
Advance the record forward and record an “approve” event.
163 164 165 166 167 |
# File 'lib/signoff/model.rb', line 163 def approve!(user: nil, comment: nil, metadata: {}, to: nil, ip_address: nil, user_agent: nil) _advance!(action: "approve", to: to, user: user, comment: comment, metadata: , ip_address: ip_address, user_agent: user_agent) end |
#approved? ⇒ Boolean
139 140 141 142 |
# File 'lib/signoff/model.rb', line 139 def approved? self.class.signoff_definition! .approval_terminal_states.include?(current_state) end |
#approved_by ⇒ Object
The user that moved the record into a successful terminal state.
232 233 234 235 236 237 238 239 240 |
# File 'lib/signoff/model.rb', line 232 def approved_by states = self.class.signoff_definition! .approval_terminal_states.map(&:to_s) return nil if states.empty? _latest_event(conditions: { to_state: states }) do |e| states.include?(e.to_state) end&.user end |
#can_approve?(user = nil) ⇒ Boolean
— authorization predicates —————————————
194 195 196 197 198 199 200 201 202 |
# File 'lib/signoff/model.rb', line 194 def can_approve?(user = nil) definition = self.class.signoff_definition! from = current_state # A no-argument approve! only proceeds when exactly one forward # transition exists (zero => terminal, many => ambiguous). return false unless definition.forward_targets(from).size == 1 _guard_satisfied?(from, user || Signoff::Current.user) end |
#can_reject?(user = nil) ⇒ Boolean
204 205 206 207 208 209 210 211 212 213 |
# File 'lib/signoff/model.rb', line 204 def can_reject?(user = nil) definition = self.class.signoff_definition! return false unless definition.reject_state from = current_state return false if from == definition.reject_state || definition.approval_terminal_states.include?(from) _guard_satisfied?(from, user || Signoff::Current.user) end |
#current_state ⇒ Object
The current workflow state as a Symbol.
132 133 134 135 136 137 |
# File 'lib/signoff/model.rb', line 132 def current_state definition = self.class.signoff_definition! _ensure_state_column!(definition) value = self[definition.state_column] (value.presence || definition.initial_state).to_sym end |
#last_approval ⇒ Object
223 224 225 |
# File 'lib/signoff/model.rb', line 223 def last_approval _latest_event(conditions: { action: "approve" }) { |e| e.action == "approve" } end |
#last_rejection ⇒ Object
227 228 229 |
# File 'lib/signoff/model.rb', line 227 def last_rejection _latest_event(conditions: { action: "reject" }) { |e| e.action == "reject" } end |
#pending? ⇒ Boolean
148 149 150 |
# File 'lib/signoff/model.rb', line 148 def pending? !self.class.signoff_definition!.finalized_states.include?(current_state) end |
#reject!(user: nil, comment: nil, metadata: {}, ip_address: nil, user_agent: nil) ⇒ Object
Move the record to the configured reject state and record a “reject” event.
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
# File 'lib/signoff/model.rb', line 171 def reject!(user: nil, comment: nil, metadata: {}, ip_address: nil, user_agent: nil) definition = self.class.signoff_definition! unless definition.reject_state raise InvalidTransitionError, "no reject state configured for #{self.class.name}" end from = current_state raise InvalidTransitionError, "record is already rejected" if from == definition.reject_state if definition.approval_terminal_states.include?(from) raise InvalidTransitionError, "cannot reject a finalized record (state: #{from})" end _perform_transition!(action: "reject", from: from, to: definition.reject_state, user: user, comment: comment, metadata: , ip_address: ip_address, user_agent: user_agent) end |
#rejected? ⇒ Boolean
144 145 146 |
# File 'lib/signoff/model.rb', line 144 def rejected? current_state == self.class.signoff_definition!.reject_state end |
#submit!(user: nil, comment: nil, metadata: {}, to: nil, ip_address: nil, user_agent: nil) ⇒ Object
Advance the record forward and record a “submit” event. Conventionally used for the initial submission out of the draft state.
156 157 158 159 160 |
# File 'lib/signoff/model.rb', line 156 def submit!(user: nil, comment: nil, metadata: {}, to: nil, ip_address: nil, user_agent: nil) _advance!(action: "submit", to: to, user: user, comment: comment, metadata: , ip_address: ip_address, user_agent: user_agent) end |
#workflow_history ⇒ Object
The full, chronologically ordered event history (preloadable via includes(:signoff_events) to avoid N+1 queries).
219 220 221 |
# File 'lib/signoff/model.rb', line 219 def workflow_history signoff_events end |