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.
142 143 144 145 146 |
# File 'lib/signoff/model.rb', line 142 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
118 119 120 121 |
# File 'lib/signoff/model.rb', line 118 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.
211 212 213 214 215 216 217 218 219 |
# File 'lib/signoff/model.rb', line 211 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 —————————————
173 174 175 176 177 178 179 180 181 |
# File 'lib/signoff/model.rb', line 173 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
183 184 185 186 187 188 189 190 191 192 |
# File 'lib/signoff/model.rb', line 183 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.
111 112 113 114 115 116 |
# File 'lib/signoff/model.rb', line 111 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
202 203 204 |
# File 'lib/signoff/model.rb', line 202 def last_approval _latest_event(conditions: { action: "approve" }) { |e| e.action == "approve" } end |
#last_rejection ⇒ Object
206 207 208 |
# File 'lib/signoff/model.rb', line 206 def last_rejection _latest_event(conditions: { action: "reject" }) { |e| e.action == "reject" } end |
#pending? ⇒ Boolean
127 128 129 |
# File 'lib/signoff/model.rb', line 127 def pending? !approved? && !rejected? 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.
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/signoff/model.rb', line 150 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
123 124 125 |
# File 'lib/signoff/model.rb', line 123 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.
135 136 137 138 139 |
# File 'lib/signoff/model.rb', line 135 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).
198 199 200 |
# File 'lib/signoff/model.rb', line 198 def workflow_history signoff_events end |