<commit_before>require 'dry_transaction_rollbacks'

module PluginAWeek #:nodoc:
  module Acts #:nodoc:
    module StateMachine
      
      # An unknown state was specified
      #
      class InvalidState < Exception #:nodoc:
      end
      
      # An unknown event was specified
      #
      class InvalidEvent < Exception #:nodoc:
      end
      
      # No initial state was specified for the machine
      #
      class NoInitialState < Exception #:nodoc:
      end
      
      def self.included(base) #:nodoc:
        base.extend(MacroMethods)
      end
      
      module SupportingClasses #:nodoc:
        # Represents a state in the machine
        #
        class State
          attr_reader :model
          delegate    :name, :id, :to => :model
          
          def initialize(model, options)
            options.symbolize_keys!.assert_valid_keys(
              :enter,
              :after,
              :exit,
              :deadline_passed_event
            )
            options.reverse_merge!(
              :deadline_passed_event => "#{model.name}_deadline_passed"
            )
            
            @model, @options = model, options
          end
          
          #
          #
          def deadline_passed_event
            "#{@options[:deadline_passed_event]}!"
          end
          
          # Indicates that the state is being entered
          #
          def entering(record)
            # If the class supports deadlines, then see if we can set it now
            if record.class.use_state_deadlines && record.respond_to?("set_#{name}_deadline")
              record.send("set_#{name}_deadline")
            end
            
            # Execute the actions for entering
            if enter_actions = @options[:enter]
              Array(enter_actions).each do |action|
                record.send(:run_transition_action, action)
              end
            end
          end
          
          # Indicates that the state has been entered
          #
          def entered(record)
            # Execute the actions after entering the state
            if after_actions = @options[:after]
              Array(after_actions).each do |action|
                record.send(:run_transition_action, action)
              end
            end
          end
          
          # Indicates the the state has been exited
          #
          def exited(record)
            # Execute the actions for exiting
            if exit_actions = @options[:exit]
              Array(exit_actions).each do |action|
                record.send(:run_transition_action, action)
              end
            end
          end
        end
        
        # Represents a transition in the machine.  A transition consists of:
        #   -The starting state
        #   -The ending state
        #   -A guard to check if the transition is allowed
        #
        class StateTransition
          attr_reader :from_name, :to_name, :options
          
          def initialize(from_name, to_name, options) #:nodoc:
            options.symbolize_keys!.assert_valid_keys(:guard)
            
            @from_name, @to_name, @guard = from_name.to_s, to_name.to_s, options[:guard]
          end
          
          # Ensures that the transition can occur by checking the guard associated
          # with it
          #
          def guard(record)
            @guard ? record.send(:run_transition_action, @guard) : true
          end
          
          # Runs the actual transition and any actions associated with entering
          # and exiting the states
          #
          def perform(record)
            return false unless guard(record)
            
            loopback = record.state_name == to_name
            
            next_state = record.class.states[to_name]
            old_state = record.class.states[record.state_name]
            
            # Start entering the next state
            next_state.entering(record) unless loopback
            
            # Update that we've entered the state
            record.state = next_state.model
            
            # Enter the next state
            next_state.entered(record) unless loopback
            
            # Leave the last state
            old_state.exited(record) unless loopback
            
            true
          end
          
          def ==(obj) #:nodoc:
            @from_name == obj.from_name && @to_name == obj.to_name
          end
        end
        
        #
        #
        class Event
          attr_reader :model,
                      :transitions,
                      :options
          
          delegate    :name, :id, :to => :model
          
          #
          #
          def initialize(model, options, transitions, valid_state_names, &block)
            @model = model
            @options = options.symbolize_keys!
            @transitions = transitions[name] = []
            @valid_state_names = valid_state_names
            
            instance_eval(&block) if block
          end
          
          #
          #
          def next_states(record)
            @transitions.select {|transition| transition.from_name == record.state_name}
          end
          
          #
          #
          def fire(record, options)
            options = options.merge(:use_transaction => false)
            success = false
            
            original_state_name = record.state_name
            next_states(record).each do |transition|
              if success = transition.perform(record)
                record.record_transition(name, original_state_name, record.state_name)
                break
              end
            end
            
            # Execute the event on all other state machines running in parallel
            if parallel_state_machines = options[:parallel]
              parallel_state_machines = [parallel_state_machines].flatten.inject({}) do |machine_events, machine|
                if machine.is_a?(Hash)
                  machine_events.merge(machine)
                else
                  machine_events[machine] = name
                end
                machine_events
              end
              
              parallel_state_machines.each do |machine, event|
                machine = Symbol === machine ? record.send(machine) : machine.call(self)
                success = machine.send("#{event}!", options)
                
                break if !success
              end
            end
            
            success
          end
          
          #
          # 
          def transition_to(to_name, options = {})
            raise InvalidState, "#{to_name} is not a valid state for #{self.name}" unless @valid_state_names.include?(to_name.to_s)
            
            options.symbolize_keys!
            
            Array(options.delete(:from)).each do |from_name|
              raise InvalidState, "#{from_name} is not a valid state for #{self.name}" unless @valid_state_names.include?(from_name.to_s)
              
              @transitions << SupportingClasses::StateTransition.new(from_name, to_name, options)
            end
          end
        end
      end
      
      module MacroMethods
        # Configuration options:
        # * <tt>initial</tt> - Specifies an initial state for newly created objects (required)
        # * <tt>use_deadlines</tt> - 
        # 
        def acts_as_state_machine(options)
          options.assert_valid_keys(
            :initial,
            :use_deadlines
          )
          raise NoInitialState unless options[:initial]
          
          options.reverse_merge!(:use_deadlines => false)
          
          model_name = "::#{self.name}"
          model_assoc_name = model_name.demodulize.underscore
          
          # Create the State model
          const_set('State', Class.new(::State)).class_eval do
            def self.reloadable?
              false
            end
          end
          
          # Create a model for recording each change in state
          const_set('Event', Class.new(::Event)).class_eval do
            def self.reloadable?
              false
            end
          end
          
          # Create a model for recording each change in state
          const_set('StateChange', Class.new(::StateChange)).class_eval do
            belongs_to  :stateful,
                          :class_name => model_name,
                          :foreign_key => 'stateful_id',
                          :dependent => :destroy
            
            alias_method    model_assoc_name, :stateful
            alias_attribute "#{model_assoc_name}_id", :stateful_id
            
            def self.reloadable?
              false
            end
          end
          
          # Create a model for tracking a deadline for each state
          use_deadlines = options[:use_deadlines]
          if use_deadlines
            const_set('StateDeadline', Class.new(::StateDeadline)).class_eval do
              belongs_to  :stateful,
                            :class_name => model_name,
                            :foreign_key => 'stateful_id',
                            :dependent => :destroy
              
              alias_method    model_assoc_name, :stateful
              alias_attribute "#{model_assoc_name}_id", :stateful_id
              
              def self.reloadable?
                false
              end
            end
          end
          
          write_inheritable_attribute :states, {}
          write_inheritable_attribute :initial_state_name, options[:initial]
          write_inheritable_attribute :transitions, {}
          write_inheritable_attribute :events, {}
          write_inheritable_attribute :use_state_deadlines, use_deadlines
          
          class_inheritable_reader    :states
          class_inheritable_reader    :transitions
          class_inheritable_reader    :events
          class_inheritable_reader    :use_state_deadlines
          
          before_create               :set_initial_state_id
          after_create                :run_initial_state_actions
          
          module_eval <<-end_eval
            module StateExtension
              def find_in_states(number, state_names, *args)
                @reflection.klass.with_state_scope(state_names) do
                  find(number, *args)
                end
              end
            end
          end_eval
          
          belongs_to  :state,
                        :class_name => "#{model_name}::State",
                        :foreign_key => 'state_id'
          has_many    :state_changes,
                        :class_name => "#{model_name}::StateChange",
                        :foreign_key => 'stateful_id',<commit_msg>Removed override of reloadable? since it is no longer used in Edge.<commit_after>require 'dry_transaction_rollbacks'

module PluginAWeek #:nodoc:
  module Acts #:nodoc:
    module StateMachine
      
      # An unknown state was specified
      #
      class InvalidState < Exception #:nodoc:
      end
      
      # An unknown event was specified
      #
      class InvalidEvent < Exception #:nodoc:
      end
      
      # No initial state was specified for the machine
      #
      class NoInitialState < Exception #:nodoc:
      end
      
      def self.included(base) #:nodoc:
        base.extend(MacroMethods)
      end
      
      module SupportingClasses #:nodoc:
        # Represents a state in the machine
        #
        class State
          attr_reader :model
          delegate    :name, :id, :to => :model
          
          def initialize(model, options)
            options.symbolize_keys!.assert_valid_keys(
              :enter,
              :after,
              :exit,
              :deadline_passed_event
            )
            options.reverse_merge!(
              :deadline_passed_event => "#{model.name}_deadline_passed"
            )
            
            @model, @options = model, options
          end
          
          #
          #
          def deadline_passed_event
            "#{@options[:deadline_passed_event]}!"
          end
          
          # Indicates that the state is being entered
          #
          def entering(record)
            # If the class supports deadlines, then see if we can set it now
            if record.class.use_state_deadlines && record.respond_to?("set_#{name}_deadline")
              record.send("set_#{name}_deadline")
            end
            
            # Execute the actions for entering
            if enter_actions = @options[:enter]
              Array(enter_actions).each do |action|
                record.send(:run_transition_action, action)
              end
            end
          end
          
          # Indicates that the state has been entered
          #
          def entered(record)
            # Execute the actions after entering the state
            if after_actions = @options[:after]
              Array(after_actions).each do |action|
                record.send(:run_transition_action, action)
              end
            end
          end
          
          # Indicates the the state has been exited
          #
          def exited(record)
            # Execute the actions for exiting
            if exit_actions = @options[:exit]
              Array(exit_actions).each do |action|
                record.send(:run_transition_action, action)
              end
            end
          end
        end
        
        # Represents a transition in the machine.  A transition consists of:
        #   -The starting state
        #   -The ending state
        #   -A guard to check if the transition is allowed
        #
        class StateTransition
          attr_reader :from_name, :to_name, :options
          
          def initialize(from_name, to_name, options) #:nodoc:
            options.symbolize_keys!.assert_valid_keys(:guard)
            
            @from_name, @to_name, @guard = from_name.to_s, to_name.to_s, options[:guard]
          end
          
          # Ensures that the transition can occur by checking the guard associated
          # with it
          #
          def guard(record)
            @guard ? record.send(:run_transition_action, @guard) : true
          end
          
          # Runs the actual transition and any actions associated with entering
          # and exiting the states
          #
          def perform(record)
            return false unless guard(record)
            
            loopback = record.state_name == to_name
            
            next_state = record.class.states[to_name]
            old_state = record.class.states[record.state_name]
            
            # Start entering the next state
            next_state.entering(record) unless loopback
            
            # Update that we've entered the state
            record.state = next_state.model
            
            # Enter the next state
            next_state.entered(record) unless loopback
            
            # Leave the last state
            old_state.exited(record) unless loopback
            
            true
          end
          
          def ==(obj) #:nodoc:
            @from_name == obj.from_name && @to_name == obj.to_name
          end
        end
        
        #
        #
        class Event
          attr_reader :model,
                      :transitions,
                      :options
          
          delegate    :name, :id, :to => :model
          
          #
          #
          def initialize(model, options, transitions, valid_state_names, &block)
            @model = model
            @options = options.symbolize_keys!
            @transitions = transitions[name] = []
            @valid_state_names = valid_state_names
            
            instance_eval(&block) if block
          end
          
          #
          #
          def next_states(record)
            @transitions.select {|transition| transition.from_name == record.state_name}
          end
          
          #
          #
          def fire(record, options)
            options = options.merge(:use_transaction => false)
            success = false
            
            original_state_name = record.state_name
            next_states(record).each do |transition|
              if success = transition.perform(record)
                record.record_transition(name, original_state_name, record.state_name)
                break
              end
            end
            
            # Execute the event on all other state machines running in parallel
            if parallel_state_machines = options[:parallel]
              parallel_state_machines = [parallel_state_machines].flatten.inject({}) do |machine_events, machine|
                if machine.is_a?(Hash)
                  machine_events.merge(machine)
                else
                  machine_events[machine] = name
                end
                machine_events
              end
              
              parallel_state_machines.each do |machine, event|
                machine = Symbol === machine ? record.send(machine) : machine.call(self)
                success = machine.send("#{event}!", options)
                
                break if !success
              end
            end
            
            success
          end
          
          #
          # 
          def transition_to(to_name, options = {})
            raise InvalidState, "#{to_name} is not a valid state for #{self.name}" unless @valid_state_names.include?(to_name.to_s)
            
            options.symbolize_keys!
            
            Array(options.delete(:from)).each do |from_name|
              raise InvalidState, "#{from_name} is not a valid state for #{self.name}" unless @valid_state_names.include?(from_name.to_s)
              
              @transitions << SupportingClasses::StateTransition.new(from_name, to_name, options)
            end
          end
        end
      end
      
      module MacroMethods
        # Configuration options:
        # * <tt>initial</tt> - Specifies an initial state for newly created objects (required)
        # * <tt>use_deadlines</tt> - 
        # 
        def acts_as_state_machine(options)
          options.assert_valid_keys(
            :initial,
            :use_deadlines
          )
          raise NoInitialState unless options[:initial]
          
          options.reverse_merge!(:use_deadlines => false)
          
          model_name = "::#{self.name}"
          model_assoc_name = model_name.demodulize.underscore
          
          # Create the State model
          const_set('State', Class.new(::State))
          
          # Create a model for recording each change in state
          const_set('Event', Class.new(::Event))
          
          # Create a model for recording each change in state
          const_set('StateChange', Class.new(::StateChange)).class_eval do
            belongs_to  :stateful,
                          :class_name => model_name,
                          :foreign_key => 'stateful_id',
                          :dependent => :destroy
            
            alias_method    model_assoc_name, :stateful
            alias_attribute "#{model_assoc_name}_id", :stateful_id
          end
          
          # Create a model for tracking a deadline for each state
          use_deadlines = options[:use_deadlines]
          if use_deadlines
            const_set('StateDeadline', Class.new(::StateDeadline)).class_eval do
              belongs_to  :stateful,
                            :class_name => model_name,
                            :foreign_key => 'stateful_id',
                            :dependent => :destroy
              
              alias_method    model_assoc_name, :stateful
              alias_attribute "#{model_assoc_name}_id", :stateful_id
            end
          end
          
          write_inheritable_attribute :states, {}
          write_inheritable_attribute :initial_state_name, options[:initial]
          write_inheritable_attribute :transitions, {}
          write_inheritable_attribute :events, {}
          write_inheritable_attribute :use_state_deadlines, use_deadlines
          
          class_inheritable_reader    :states
          class_inheritable_reader    :transitions
          class_inheritable_reader    :events
          class_inheritable_reader    :use_state_deadlines
          
          before_create               :set_initial_state_id
          after_create                :run_initial_state_actions
          
          const_set('StateExtension', Module.new).class_eval do
            def find_in_states(number, state_names, *args)
              @reflection.klass.with_state_scope(state_names) do
                find(number, *args)
              end
            end
          end
#          module_eval <<-end_eval
#            module StateExtension
#              def find_in_states(number, state_names, *args)
#                @reflection.klass.with_state_scope(state_names) do
#                  find(number, *args)
#                end
#              end
#            end
#          end_eval
          
          belongs_to  :state,
                        :class_name => "#{model_name}::State",
                        :foreign_key => 'state_id'
          has_many    :state_changes,
                        :class_name => "#{model_name}::StateChange",
                        :foreign_key => 'stateful_id',
{'fraction_non_alphanumeric': 0.0691610504299326, 'fraction_numerical': 0.0, 'mean_word_length': 1.391463821273758, 'pattern_counts': {'":': 2, '<': 25, '<?xml version=': 0, '>': 41, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3684801', 'n_tokens_mistral': 5357, 'n_tokens_neox': 5264, 'n_words': 1278}