<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 :record
          delegate    :name, :id, :to => :record
          
          def initialize(record, options) #:nodoc:
            options.symbolize_keys!.assert_valid_keys(
              :before_enter,
              :after_enter,
              :before_exit,
              :after_exit,
              :deadline_passed_event
            )
            options.reverse_merge!(
              :deadline_passed_event => "#{record.name}_deadline_passed"
            )
            
            @record, @options = record, options
          end
          
          # Gets the name of the event that should be invoked when the state's
          # deadline has passed
          def deadline_passed_event
            "#{@options[:deadline_passed_event]}!"
          end
          
          # Indicates that the state is being entered
          def entering(record, args)
            # 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
            
            run_actions(record, args, :before_enter)
          end
          
          # Indicates that the state has been entered
          def entered(record, args)
            run_actions(record, args, :after_enter)
          end
          
          # Indicates the the state is being exited
          def exiting(record, args)
            run_actions(record, args, :before_exit)
          end
          
          # Indicates the the state has been exited
          def exited(record, args)
            run_actions(record, args, :after_exit)
          end
          
          private
          def run_actions(record, args, action_type) #:nodoc:
            if actions = @options[action_type]
              Array(actions).each do |action|
                record.send(:run_transition_action, action, args)
              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, @guards = from_name.to_s, to_name.to_s, options[:guard]
            @guards = Array(@guards) if @guards
          end
          
          # Ensures that the transition can occur by checking the guards
          # associated with it
          def guard(record, args)
            @guards.nil? || @guards.all? {|guard| record.send(:run_transition_action, guard, args)}
          end
          
          # Runs the actual transition and any actions associated with entering
          # and exiting the states
          def perform(record, args)
            return false unless guard(record, args)
            
            loopback = record.state_name == to_name
            
            next_state = record.class.states[to_name]
            last_state = record.class.states[record.state_name]
            
            # Start leaving the last state
            last_state.exiting(record, args) unless loopback
            
            # Start entering the next state
            next_state.entering(record, args) unless loopback
            
            record.state = next_state.record
            
            # Leave the last state
            last_state.exited(record, args) unless loopback
            
            # Enter the next state
            next_state.entered(record, args) unless loopback
            
            true
          end
          
          def ==(obj) #:nodoc:
            @from_name == obj.from_name && @to_name == obj.to_name
          end
        end
        
        # Represents an event that can occur in the machine
        class Event
          attr_writer :valid_state_names
          attr_reader :record,
                      :transitions,
                      :options
          
          delegate    :name, :id, :to => :record
          
          #
          def initialize(record, options, transitions, valid_state_names, &block)
            @record = record
            @options = options.symbolize_keys!
            @transitions = transitions[name] = []
            @valid_state_names = valid_state_names
            
            instance_eval(&block) if block
          end
          
          # Gets all of the possible next states for the record
          def next_states(record)
            @transitions.select {|transition| transition.from_name == record.state_name}
          end
          
          #
          def fire(record, args)
            success = false
            
            original_state_name = record.state_name
            next_states(record).each do |transition|
              if success = transition.perform(record, args)
                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}!", *args)
                
                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
          
          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',
                        :dependent => :destroy
          has_many    :state_deadlines,
                        :class_name => "#{model_name}::StateDeadline",
                        :foreign_key => 'stateful_id',
                        :dependent => :destroy if use_deadlines
          
          extend PluginAWeek::Acts::StateMachine::ClassMethods
          include PluginAWeek::Acts::StateMachine::InstanceMethods
        end
      end
      
      module InstanceMethods
        def self.included(base) #:nodoc:
          base.class_eval do
            alias_method_chain :state, :initial_check
          end
        end
        
        #
        def initial_state_name
          name = self.class.read_inheritable_attribute(:initial_state_name)
          name = name.call(self) if name.is_a?(Proc)
          
          name
        end
        
        #
        def initial_state
          self.class.states[initial_state_name.to_s].record
        end
        
        #
        def state_with_initial_check
          state_without_initial_check || (new_record? ? initial_state : nil)
        end
        
        #
        #
        def state_id
          read_attribute(:state_id) || (new_record? ? state.id : nil)
        end
        
        # The current state the object is in
        def state_name
          state.name
        end
        
        # Returns what the next state for a given event would be, as a Ruby symbol.
        def next_state_for_event(event_name)
          next_states = next_states_for_event(event_name)
          next_states.empty? ? nil : next_states.first.to
        end
        
        # Returns all of the next possible states for a given event, as Ruby symbols.
        def next_states_for_event(event_name)
          self.class.transitions[event_name.to_s].select do |transition|
            transition.from == state_name
          end
        end
        
        #
        def record_transition(event_name, from_state_name, to_state_name)
          from_record = self.class.states[from_state_name].record if from_state_name
          to_record = self.class.states[to_state_name].record
          
          state_attrs = {
            :to_state_id => to_record.id,
            :occurred_at => Time.now
          }
          state_attrs[:event_id] = self.class.events[event_name].id if event_name
          state_attrs[:from_state_id] = from_record.id if from_record
          
          state_changes.create(state_attrs)
          
          if self.class.use_state_deadlines && send("#{to_state_name}_deadline")
            send("clear_#{to_state_name}_deadline")
          end
        end
        
        #
        def after_find
          check_deadlines
        end
        
        #
        def check_deadlines(options = {})
          transitioned = false
          
          if self.class.use_state_deadlines
            current_deadline = send("#{state_name}_deadline")
            
            if current_deadline && current_deadline <= Time.now
              state = self.class.states[state_name]
              transitioned = send(state.deadline_passed_event, options)
            end
          end
          
          transitioned
        end
        
        private
        #
        def set_initial_state_id
          self.state_id = state.id if read_attribute(:state_id).nil?
        end
        
        #
        def run_initial_state_actions
          if state_changes.empty?
            transaction(self) do
              state = self.class.states[initial_state_name.to_s]
              state.entering(self)
              state.entered(self)
              
              record_transition(nil, nil, state.name)
            end
          end
        end
        
        #
        def run_transition_action(action, args)
          Symbol === action ? send(action, *args) : action.call(*args.unshift(self))
        end
      end
      
      module ClassMethods
        # Returns an array of all known states.
        def state_names
          states.keys
        end
        
        # Define a state of the system. +state+ can take an optional Proc object
        # which will be executed every time the system transitions into that
        # state.  The proc will be passed the current object.
        #
        # Example:
        #
        # class Order < ActiveRecord::Base
        #   acts_as_state_machine :initial => :open
        #
        #   state :open
        #   state :closed, Proc.new { |o| Mailer.send_notice(o) }
        # end
        def state(name, options = {})
          name = name.to_s
          record = self::State.find_by_name(name)
          raise InvalidState, "#{name} is not a valid state for #{self.name}" unless record
          
          states[name] = SupportingClasses::State.new(record, options)
          
          class_eval <<-end_eval
            def #{name}?
              state_id == #{record.id}
            end
            
            def #{name}_at
              state_change = state_changes.find_by_to_state_id(#{record.id}, :order => 'occurred_at DESC')
              state_change.occurred_at if !state_change.nil?
            end
          end_eval
          
          # Add support for checking deadlines
          if use_state_deadlines
            class_eval <<-end_eval
              def #{name}_deadline
                state_deadline = state_deadlines.find_by_state_id(#{record.id})
                state_deadline.deadline if state_deadline
              end
              
              def #{name}_deadline=(value)
                state_deadlines.create(:state_id => #{record.id}, :deadline => value)
              end
              
              def clear_#{name}_deadline
                state_deadlines.find_by_state_id(#{record.id}).destroy
              end
            end_eval
          end
          
          self::StateExtension.module_eval <<-end_eval
            def #{name}(*args)
              find_all_by_state_id(#{record.id}, *args)
            end
          end_eval
        end
        
        # Returns an array of all known states.
        def event_names
          events.keys
        end
        
        # Define an event.  This takes a block which describes all valid transitions
        # for this event.
        #
        # Example:
        #<commit_msg>Fixed find_all_by_state_id within the StateExtension causing problems when multiple tables are being joined that have a state_id field. Modified find_all_by_state_id within the StateExtension to have a default of :all if the number to select is not specified.<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 :record
          delegate    :name, :id, :to => :record
          
          def initialize(record, options) #:nodoc:
            options.symbolize_keys!.assert_valid_keys(
              :before_enter,
              :after_enter,
              :before_exit,
              :after_exit,
              :deadline_passed_event
            )
            options.reverse_merge!(
              :deadline_passed_event => "#{record.name}_deadline_passed"
            )
            
            @record, @options = record, options
          end
          
          # Gets the name of the event that should be invoked when the state's
          # deadline has passed
          def deadline_passed_event
            "#{@options[:deadline_passed_event]}!"
          end
          
          # Indicates that the state is being entered
          def entering(record, args)
            # 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
            
            run_actions(record, args, :before_enter)
          end
          
          # Indicates that the state has been entered
          def entered(record, args)
            run_actions(record, args, :after_enter)
          end
          
          # Indicates the the state is being exited
          def exiting(record, args)
            run_actions(record, args, :before_exit)
          end
          
          # Indicates the the state has been exited
          def exited(record, args)
            run_actions(record, args, :after_exit)
          end
          
          private
          def run_actions(record, args, action_type) #:nodoc:
            if actions = @options[action_type]
              Array(actions).each do |action|
                record.send(:run_transition_action, action, args)
              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, @guards = from_name.to_s, to_name.to_s, options[:guard]
            @guards = Array(@guards) if @guards
          end
          
          # Ensures that the transition can occur by checking the guards
          # associated with it
          def guard(record, args)
            @guards.nil? || @guards.all? {|guard| record.send(:run_transition_action, guard, args)}
          end
          
          # Runs the actual transition and any actions associated with entering
          # and exiting the states
          def perform(record, args)
            return false unless guard(record, args)
            
            loopback = record.state_name == to_name
            
            next_state = record.class.states[to_name]
            last_state = record.class.states[record.state_name]
            
            # Start leaving the last state
            last_state.exiting(record, args) unless loopback
            
            # Start entering the next state
            next_state.entering(record, args) unless loopback
            
            record.state = next_state.record
            
            # Leave the last state
            last_state.exited(record, args) unless loopback
            
            # Enter the next state
            next_state.entered(record, args) unless loopback
            
            true
          end
          
          def ==(obj) #:nodoc:
            @from_name == obj.from_name && @to_name == obj.to_name
          end
        end
        
        # Represents an event that can occur in the machine
        class Event
          attr_writer :valid_state_names
          attr_reader :record,
                      :transitions,
                      :options
          
          delegate    :name, :id, :to => :record
          
          #
          def initialize(record, options, transitions, valid_state_names, &block)
            @record = record
            @options = options.symbolize_keys!
            @transitions = transitions[name] = []
            @valid_state_names = valid_state_names
            
            instance_eval(&block) if block
          end
          
          # Gets all of the possible next states for the record
          def next_states(record)
            @transitions.select {|transition| transition.from_name == record.state_name}
          end
          
          #
          def fire(record, args)
            success = false
            
            original_state_name = record.state_name
            next_states(record).each do |transition|
              if success = transition.perform(record, args)
                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}!", *args)
                
                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
          
          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',
                        :dependent => :destroy
          has_many    :state_deadlines,
                        :class_name => "#{model_name}::StateDeadline",
                        :foreign_key => 'stateful_id',
                        :dependent => :destroy if use_deadlines
          
          extend PluginAWeek::Acts::StateMachine::ClassMethods
          include PluginAWeek::Acts::StateMachine::InstanceMethods
        end
      end
      
      module InstanceMethods
        def self.included(base) #:nodoc:
          base.class_eval do
            alias_method_chain :state, :initial_check
          end
        end
        
        #
        def initial_state_name
          name = self.class.read_inheritable_attribute(:initial_state_name)
          name = name.call(self) if name.is_a?(Proc)
          
          name
        end
        
        #
        def initial_state
          self.class.states[initial_state_name.to_s].record
        end
        
        #
        def state_with_initial_check
          state_without_initial_check || (new_record? ? initial_state : nil)
        end
        
        #
        #
        def state_id
          read_attribute(:state_id) || (new_record? ? state.id : nil)
        end
        
        # The current state the object is in
        def state_name
          state.name
        end
        
        # Returns what the next state for a given event would be, as a Ruby symbol.
        def next_state_for_event(event_name)
          next_states = next_states_for_event(event_name)
          next_states.empty? ? nil : next_states.first.to
        end
        
        # Returns all of the next possible states for a given event, as Ruby symbols.
        def next_states_for_event(event_name)
          self.class.transitions[event_name.to_s].select do |transition|
            transition.from == state_name
          end
        end
        
        #
        def record_transition(event_name, from_state_name, to_state_name)
          from_record = self.class.states[from_state_name].record if from_state_name
          to_record = self.class.states[to_state_name].record
          
          state_attrs = {
            :to_state_id => to_record.id,
            :occurred_at => Time.now
          }
          state_attrs[:event_id] = self.class.events[event_name].id if event_name
          state_attrs[:from_state_id] = from_record.id if from_record
          
          state_changes.create(state_attrs)
          
          if self.class.use_state_deadlines && send("#{to_state_name}_deadline")
            send("clear_#{to_state_name}_deadline")
          end
        end
        
        #
        def after_find
          check_deadlines
        end
        
        #
        def check_deadlines(options = {})
          transitioned = false
          
          if self.class.use_state_deadlines
            current_deadline = send("#{state_name}_deadline")
            
            if current_deadline && current_deadline <= Time.now
              state = self.class.states[state_name]
              transitioned = send(state.deadline_passed_event, options)
            end
          end
          
          transitioned
        end
        
        private
        #
        def set_initial_state_id
          self.state_id = state.id if read_attribute(:state_id).nil?
        end
        
        #
        def run_initial_state_actions
          if state_changes.empty?
            transaction(self) do
              state = self.class.states[initial_state_name.to_s]
              state.entering(self)
              state.entered(self)
              
              record_transition(nil, nil, state.name)
            end
          end
        end
        
        #
        def run_transition_action(action, args)
          Symbol === action ? send(action, *args) : action.call(*args.unshift(self))
        end
      end
      
      module ClassMethods
        # Returns an array of all known states.
        def state_names
          states.keys
        end
        
        # Define a state of the system. +state+ can take an optional Proc object
        # which will be executed every time the system transitions into that
        # state.  The proc will be passed the current object.
        #
        # Example:
        #
        # class Order < ActiveRecord::Base
        #   acts_as_state_machine :initial => :open
        #
        #   state :open
        #   state :closed, Proc.new { |o| Mailer.send_notice(o) }
        # end
        def state(name, options = {})
          name = name.to_s
          record = self::State.find_by_name(name)
          raise InvalidState, "#{name} is not a valid state for #{self.name}" unless record
          
          states[name] = SupportingClasses::State.new(record, options)
          
          class_eval <<-end_eval
            def #{name}?
              state_id == #{record.id}
            end
            
            def #{name}_at
              state_change = state_changes.find_by_to_state_id(#{record.id}, :order => 'occurred_at DESC')
              state_change.occurred_at if !state_change.nil?
            end
          end_eval
          
          # Add support for checking deadlines
          if use_state_deadlines
            class_eval <<-end_eval
              def #{name}_deadline
                state_deadline = state_deadlines.find_by_state_id(#{record.id})
                state_deadline.deadline if state_deadline
              end
              
              def #{name}_deadline=(value)
                state_deadlines.create(:state_id => #{record.id}, :deadline => value)
              end
              
              def clear_#{name}_deadline
                state_deadlines.find_by_state_id(#{record.id}).destroy
              end
            end_eval
          end
          
          self::StateExtension.module_eval <<-end_eval
            def #{name}(*args)
              with_scope(:find => {:conditions => ["\#{aliased_table_name}.state_id = ?", #{record.id}]}) do
                find(args.first.is_a?(Symbol) ? args.shift : :all, *args)
              end
            end
          end_eval
        end
        
        # Returns an array of all known states.
        def event_names
          events.keys
        end
        
        # Define an event.  This takes a block which describes all valid transitions
        # for this event.
        #
        # Example:
        #
{'fraction_non_alphanumeric': 0.07115785762552516, 'fraction_numerical': 0.0, 'mean_word_length': 1.5135135135135136, 'pattern_counts': {'":': 2, '<': 37, '<?xml version=': 0, '>': 61, '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': '1180683', 'n_tokens_mistral': 8772, 'n_tokens_neox': 8645, 'n_words': 2116}