Module: Tasker::Concerns::IdempotentStateTransitions
- Extended by:
- ActiveSupport::Concern
- Included in:
- Events::Publisher, Orchestration::Coordinator, Orchestration::StepExecutor, Orchestration::TaskFinalizer, Orchestration::TaskInitializer, Orchestration::TaskReenqueuer
- Defined in:
- lib/tasker/concerns/idempotent_state_transitions.rb
Overview
IdempotentStateTransitions provides helper methods for safely transitioning state machine objects without throwing errors on same-state transitions.
This concern extracts the common pattern of checking current state before attempting transitions to handle Statesman’s restriction on same-state transitions.
Instance Method Summary collapse
-
#conditional_transition_to(state_machine_object, target_state, allowed_from_states, metadata = {}) ⇒ Symbol
Safely transition to target state only if current state matches one of the allowed from states.
-
#in_any_state?(state_machine_object, states) ⇒ Boolean
Check if an object is in any of the specified states.
-
#safe_current_state(state_machine_object) ⇒ String?
Get current state safely, handling cases where state machine might not exist.
-
#safe_transition_to(state_machine_object, target_state, metadata = {}) ⇒ Boolean
Safely transition a state machine object to a target state.
Instance Method Details
#conditional_transition_to(state_machine_object, target_state, allowed_from_states, metadata = {}) ⇒ Symbol
Safely transition to target state only if current state matches one of the allowed from states
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 |
# File 'lib/tasker/concerns/idempotent_state_transitions.rb', line 67 def conditional_transition_to(state_machine_object, target_state, allowed_from_states, = {}) current_state = state_machine_object.state_machine.current_state # Already in target state - idempotent return :already_target if current_state == target_state # Check if current state allows this transition unless allowed_from_states.include?(current_state) allowed_states_list = allowed_from_states.join(', ') Rails.logger.debug do "#{self.class.name}: Cannot transition #{state_machine_object.class.name} #{state_machine_object.id} " \ "from #{current_state} to #{target_state}. Allowed from states: #{allowed_states_list}" end return :invalid_from_state end # Perform the transition state_machine_object.state_machine.transition_to!(target_state, ) Rails.logger.debug do "#{self.class.name}: Successfully transitioned" \ "#{state_machine_object.class.name} #{state_machine_object.id} " \ "from #{current_state} to #{target_state}" end :transitioned rescue StandardError => e Rails.logger.error do "#{self.class.name}: Failed to transition #{state_machine_object.class.name} #{state_machine_object.id} " \ "to #{target_state}: #{e.}" end raise end |
#in_any_state?(state_machine_object, states) ⇒ Boolean
Check if an object is in any of the specified states
120 121 122 123 124 125 |
# File 'lib/tasker/concerns/idempotent_state_transitions.rb', line 120 def in_any_state?(state_machine_object, states) current_state = safe_current_state(state_machine_object) return false if current_state.nil? states.include?(current_state) end |
#safe_current_state(state_machine_object) ⇒ String?
Get current state safely, handling cases where state machine might not exist
103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/tasker/concerns/idempotent_state_transitions.rb', line 103 def safe_current_state(state_machine_object) return nil unless state_machine_object.respond_to?(:state_machine) state_machine_object.state_machine.current_state rescue StandardError => e Rails.logger.warn do "#{self.class.name}: Could not get current state for #{state_machine_object.class.name} " \ "#{state_machine_object.id}: #{e.}" end nil end |
#safe_transition_to(state_machine_object, target_state, metadata = {}) ⇒ Boolean
Safely transition a state machine object to a target state
Checks the current state first and only attempts transition if different. This prevents Statesman’s “Cannot transition from X to X” errors.
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/tasker/concerns/idempotent_state_transitions.rb', line 22 def safe_transition_to(state_machine_object, target_state, = {}) current_state = state_machine_object.state_machine.current_state if current_state == target_state Rails.logger.debug do "#{self.class.name}: #{state_machine_object.class.name} #{state_machine_object.id} " \ "already in #{target_state}, skipping transition" end return false end Rails.logger.debug do "#{self.class.name}: Transitioning #{state_machine_object.class.name} #{state_machine_object.id} " \ "from #{current_state} to #{target_state}" end state_machine_object.state_machine.transition_to!(target_state, ) true rescue Statesman::GuardFailedError => e Rails.logger.debug do "#{self.class.name}: Guard clause prevented transition of #{state_machine_object.class.name} " \ "#{state_machine_object.id} from '#{current_state}' to '#{target_state}': #{e.}" end false rescue Statesman::TransitionFailedError => e Rails.logger.warn do "#{self.class.name}: Invalid transition for #{state_machine_object.class.name} " \ "#{state_machine_object.id} from '#{current_state}' to '#{target_state}': #{e.}" end false rescue StandardError => e Rails.logger.error do "#{self.class.name}: Failed to transition #{state_machine_object.class.name} #{state_machine_object.id} " \ "to #{target_state}: #{e.}" end raise end |