statesman 7.0.1 → 7.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/.circleci/config.yml +5 -0
 - data/CHANGELOG.md +27 -0
 - data/Gemfile +1 -1
 - data/README.md +1 -1
 - data/lib/statesman.rb +12 -1
 - data/lib/statesman/adapters/active_record.rb +176 -32
 - data/lib/statesman/adapters/memory.rb +4 -0
 - data/lib/statesman/config.rb +26 -0
 - data/lib/statesman/exceptions.rb +10 -5
 - data/lib/statesman/machine.rb +4 -0
 - data/lib/statesman/version.rb +1 -1
 - data/spec/statesman/adapters/active_record_queries_spec.rb +3 -1
 - data/spec/statesman/adapters/active_record_spec.rb +24 -1
 - data/spec/statesman/exceptions_spec.rb +88 -0
 - data/spec/support/active_record.rb +5 -0
 - data/statesman.gemspec +13 -3
 - metadata +24 -11
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 9dc585aeecc837bf65850c562c9f41524959bd07c5f7f889284f534c260911c3
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 807ae19092dcce330131916b825964e5f62ff13f8c4bde2e2140dee9d1f9983d
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 2688beb079f3b1993bd19b4460834a18a4ca1d5eac9733003c7c5f08c1fd2deb19aeb46103a8f799e89c346ac0704368119ff180ed907928c74587b6a6623ea0
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: c58db63a374f2474ccc73f0064d9dc089d8e52967b047ef77333fdae9b57eb1798cf428a0e6ec162788eaec99e5758d1bd26118c43944f5edda90261dbcb6845
         
     | 
    
        data/.circleci/config.yml
    CHANGED
    
    | 
         @@ -56,6 +56,7 @@ jobs: 
     | 
|
| 
       56 
56 
     | 
    
         
             
                    environment:
         
     | 
| 
       57 
57 
     | 
    
         
             
                      - POSTGRES_USER=postgres
         
     | 
| 
       58 
58 
     | 
    
         
             
                      - POSTGRES_DB=statesman_test
         
     | 
| 
      
 59 
     | 
    
         
            +
                      - POSTGRES_PASSWORD=statesman
         
     | 
| 
       59 
60 
     | 
    
         
             
                steps: *steps
         
     | 
| 
       60 
61 
     | 
    
         | 
| 
       61 
62 
     | 
    
         
             
              build-ruby265-rails-602-mysql:
         
     | 
| 
         @@ -83,6 +84,7 @@ jobs: 
     | 
|
| 
       83 
84 
     | 
    
         
             
                    environment:
         
     | 
| 
       84 
85 
     | 
    
         
             
                      - POSTGRES_USER=postgres
         
     | 
| 
       85 
86 
     | 
    
         
             
                      - POSTGRES_DB=statesman_test
         
     | 
| 
      
 87 
     | 
    
         
            +
                      - POSTGRES_PASSWORD=statesman
         
     | 
| 
       86 
88 
     | 
    
         
             
                steps: *steps
         
     | 
| 
       87 
89 
     | 
    
         
             
              build-ruby265-rails-master-mysql:
         
     | 
| 
       88 
90 
     | 
    
         
             
                docker:
         
     | 
| 
         @@ -110,6 +112,7 @@ jobs: 
     | 
|
| 
       110 
112 
     | 
    
         
             
                    environment:
         
     | 
| 
       111 
113 
     | 
    
         
             
                      - POSTGRES_USER=postgres
         
     | 
| 
       112 
114 
     | 
    
         
             
                      - POSTGRES_DB=statesman_test
         
     | 
| 
      
 115 
     | 
    
         
            +
                      - POSTGRES_PASSWORD=statesman
         
     | 
| 
       113 
116 
     | 
    
         
             
                steps: *steps
         
     | 
| 
       114 
117 
     | 
    
         | 
| 
       115 
118 
     | 
    
         
             
              build-ruby270-rails-602-mysql:
         
     | 
| 
         @@ -137,6 +140,7 @@ jobs: 
     | 
|
| 
       137 
140 
     | 
    
         
             
                    environment:
         
     | 
| 
       138 
141 
     | 
    
         
             
                      - POSTGRES_USER=postgres
         
     | 
| 
       139 
142 
     | 
    
         
             
                      - POSTGRES_DB=statesman_test
         
     | 
| 
      
 143 
     | 
    
         
            +
                      - POSTGRES_PASSWORD=statesman
         
     | 
| 
       140 
144 
     | 
    
         
             
                steps: *steps
         
     | 
| 
       141 
145 
     | 
    
         
             
              build-ruby270-rails-master-mysql:
         
     | 
| 
       142 
146 
     | 
    
         
             
                docker:
         
     | 
| 
         @@ -164,6 +168,7 @@ jobs: 
     | 
|
| 
       164 
168 
     | 
    
         
             
                    environment:
         
     | 
| 
       165 
169 
     | 
    
         
             
                      - POSTGRES_USER=postgres
         
     | 
| 
       166 
170 
     | 
    
         
             
                      - POSTGRES_DB=statesman_test
         
     | 
| 
      
 171 
     | 
    
         
            +
                      - POSTGRES_PASSWORD=statesman
         
     | 
| 
       167 
172 
     | 
    
         
             
                steps: *steps
         
     | 
| 
       168 
173 
     | 
    
         | 
| 
       169 
174 
     | 
    
         
             
            workflows:
         
     | 
    
        data/CHANGELOG.md
    CHANGED
    
    | 
         @@ -1,3 +1,30 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ## v7.4.0 26th August 2020
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            ### Added
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            - [Gem Metadata](https://guides.rubygems.org/specification-reference/#metadata)
         
     | 
| 
      
 6 
     | 
    
         
            +
              to make finding changes between releases even easier.
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            ## v7.3.0, 24th August 2020
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            ### Changed
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            - Use correct Arel for null [#409](https://github.com/gocardless/statesman/pull/#409)
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            ## v7.2.0, 19th May 2020
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            ### Changed
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
            - Set non-empty password for postgres tests [#398](https://github.com/gocardless/statesman/pull/#398)
         
     | 
| 
      
 19 
     | 
    
         
            +
            - Handle transitions differently for MySQL [#399](https://github.com/gocardless/statesman/pull/#399)
         
     | 
| 
      
 20 
     | 
    
         
            +
            - pg requirement from >= 0.18, <= 1.1 to >= 0.18, <= 1.3 [#400](https://github.com/gocardless/statesman/pull/#400)
         
     | 
| 
      
 21 
     | 
    
         
            +
            - Lazily enable mysql gaplock protection [#402](https://github.com/gocardless/statesman/pull/#402)
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
            ## v7.1.0, 10th Feb 2020
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            - Fix `to_s` on `TransitionFailedError` & `GuardFailedError`. `.message` and
         
     | 
| 
      
 26 
     | 
    
         
            +
                `.to_s` diverged when `from` and `to` accessors where added in v4.1.3
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
       1 
28 
     | 
    
         
             
            ## v7.0.1, 8th Jan 2020
         
     | 
| 
       2 
29 
     | 
    
         | 
| 
       3 
30 
     | 
    
         
             
            - Fix deprecation warning with Ruby 2.7 [#386](https://github.com/gocardless/statesman/pull/386)
         
     | 
    
        data/Gemfile
    CHANGED
    
    | 
         @@ -14,5 +14,5 @@ end 
     | 
|
| 
       14 
14 
     | 
    
         | 
| 
       15 
15 
     | 
    
         
             
            group :development do
         
     | 
| 
       16 
16 
     | 
    
         
             
              # test/unit is no longer bundled with Ruby 2.2, but required by Rails
         
     | 
| 
       17 
     | 
    
         
            -
              gem "test-unit", "~> 3. 
     | 
| 
      
 17 
     | 
    
         
            +
              gem "test-unit", "~> 3.3" if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.2.0")
         
     | 
| 
       18 
18 
     | 
    
         
             
            end
         
     | 
    
        data/README.md
    CHANGED
    
    
    
        data/lib/statesman.rb
    CHANGED
    
    | 
         @@ -20,14 +20,25 @@ module Statesman 
     | 
|
| 
       20 
20 
     | 
    
         
             
              # Example:
         
     | 
| 
       21 
21 
     | 
    
         
             
              #   Statesman.configure do
         
     | 
| 
       22 
22 
     | 
    
         
             
              #     storage_adapter Statesman::ActiveRecordAdapter
         
     | 
| 
      
 23 
     | 
    
         
            +
              #     enable_mysql_gaplock_protection
         
     | 
| 
       23 
24 
     | 
    
         
             
              #   end
         
     | 
| 
       24 
25 
     | 
    
         
             
              #
         
     | 
| 
       25 
26 
     | 
    
         
             
              def self.configure(&block)
         
     | 
| 
       26 
     | 
    
         
            -
                config = Config.new(block)
         
     | 
| 
      
 27 
     | 
    
         
            +
                @config = Config.new(block)
         
     | 
| 
       27 
28 
     | 
    
         
             
                @storage_adapter = config.adapter_class
         
     | 
| 
       28 
29 
     | 
    
         
             
              end
         
     | 
| 
       29 
30 
     | 
    
         | 
| 
       30 
31 
     | 
    
         
             
              def self.storage_adapter
         
     | 
| 
       31 
32 
     | 
    
         
             
                @storage_adapter || Adapters::Memory
         
     | 
| 
       32 
33 
     | 
    
         
             
              end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
              def self.mysql_gaplock_protection?
         
     | 
| 
      
 36 
     | 
    
         
            +
                return @mysql_gaplock_protection unless @mysql_gaplock_protection.nil?
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                @mysql_gaplock_protection = config.mysql_gaplock_protection?
         
     | 
| 
      
 39 
     | 
    
         
            +
              end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
              def self.config
         
     | 
| 
      
 42 
     | 
    
         
            +
                @config ||= Config.new
         
     | 
| 
      
 43 
     | 
    
         
            +
              end
         
     | 
| 
       33 
44 
     | 
    
         
             
            end
         
     | 
| 
         @@ -5,9 +5,6 @@ require_relative "../exceptions" 
     | 
|
| 
       5 
5 
     | 
    
         
             
            module Statesman
         
     | 
| 
       6 
6 
     | 
    
         
             
              module Adapters
         
     | 
| 
       7 
7 
     | 
    
         
             
                class ActiveRecord
         
     | 
| 
       8 
     | 
    
         
            -
                  attr_reader :transition_class
         
     | 
| 
       9 
     | 
    
         
            -
                  attr_reader :parent_model
         
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
8 
     | 
    
         
             
                  JSON_COLUMN_TYPES = %w[json jsonb].freeze
         
     | 
| 
       12 
9 
     | 
    
         | 
| 
       13 
10 
     | 
    
         
             
                  def self.database_supports_partial_indexes?
         
     | 
| 
         @@ -19,6 +16,10 @@ module Statesman 
     | 
|
| 
       19 
16 
     | 
    
         
             
                    end
         
     | 
| 
       20 
17 
     | 
    
         
             
                  end
         
     | 
| 
       21 
18 
     | 
    
         | 
| 
      
 19 
     | 
    
         
            +
                  def self.adapter_name
         
     | 
| 
      
 20 
     | 
    
         
            +
                    ::ActiveRecord::Base.connection.adapter_name.downcase
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
       22 
23 
     | 
    
         
             
                  def initialize(transition_class, parent_model, observer, options = {})
         
     | 
| 
       23 
24 
     | 
    
         
             
                    serialized = serialized?(transition_class)
         
     | 
| 
       24 
25 
     | 
    
         
             
                    column_type = transition_class.columns_hash["metadata"].sql_type
         
     | 
| 
         @@ -29,12 +30,15 @@ module Statesman 
     | 
|
| 
       29 
30 
     | 
    
         
             
                    end
         
     | 
| 
       30 
31 
     | 
    
         | 
| 
       31 
32 
     | 
    
         
             
                    @transition_class = transition_class
         
     | 
| 
      
 33 
     | 
    
         
            +
                    @transition_table = transition_class.arel_table
         
     | 
| 
       32 
34 
     | 
    
         
             
                    @parent_model = parent_model
         
     | 
| 
       33 
35 
     | 
    
         
             
                    @observer = observer
         
     | 
| 
       34 
36 
     | 
    
         
             
                    @association_name =
         
     | 
| 
       35 
37 
     | 
    
         
             
                      options[:association_name] || @transition_class.table_name
         
     | 
| 
       36 
38 
     | 
    
         
             
                  end
         
     | 
| 
       37 
39 
     | 
    
         | 
| 
      
 40 
     | 
    
         
            +
                  attr_reader :transition_class, :transition_table, :parent_model
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
       38 
42 
     | 
    
         
             
                  def create(from, to, metadata = {})
         
     | 
| 
       39 
43 
     | 
    
         
             
                    create_transition(from.to_s, to.to_s, metadata)
         
     | 
| 
       40 
44 
     | 
    
         
             
                  rescue ::ActiveRecord::RecordNotUnique => e
         
     | 
| 
         @@ -63,21 +67,42 @@ module Statesman 
     | 
|
| 
       63 
67 
     | 
    
         
             
                    end
         
     | 
| 
       64 
68 
     | 
    
         
             
                  end
         
     | 
| 
       65 
69 
     | 
    
         | 
| 
      
 70 
     | 
    
         
            +
                  def reset
         
     | 
| 
      
 71 
     | 
    
         
            +
                    @last_transition = nil
         
     | 
| 
      
 72 
     | 
    
         
            +
                  end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
       66 
74 
     | 
    
         
             
                  private
         
     | 
| 
       67 
75 
     | 
    
         | 
| 
      
 76 
     | 
    
         
            +
                  # rubocop:disable Metrics/MethodLength
         
     | 
| 
       68 
77 
     | 
    
         
             
                  def create_transition(from, to, metadata)
         
     | 
| 
       69 
     | 
    
         
            -
                     
     | 
| 
       70 
     | 
    
         
            -
             
     | 
| 
       71 
     | 
    
         
            -
             
     | 
| 
       72 
     | 
    
         
            -
             
     | 
| 
       73 
     | 
    
         
            -
                    transition_attributes[:most_recent] = true
         
     | 
| 
       74 
     | 
    
         
            -
             
     | 
| 
       75 
     | 
    
         
            -
                    transition = transitions_for_parent.build(transition_attributes)
         
     | 
| 
      
 78 
     | 
    
         
            +
                    transition = transitions_for_parent.build(
         
     | 
| 
      
 79 
     | 
    
         
            +
                      default_transition_attributes(to, metadata),
         
     | 
| 
      
 80 
     | 
    
         
            +
                    )
         
     | 
| 
       76 
81 
     | 
    
         | 
| 
       77 
82 
     | 
    
         
             
                    ::ActiveRecord::Base.transaction(requires_new: true) do
         
     | 
| 
       78 
83 
     | 
    
         
             
                      @observer.execute(:before, from, to, transition)
         
     | 
| 
       79 
     | 
    
         
            -
             
     | 
| 
       80 
     | 
    
         
            -
                       
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                      if mysql_gaplock_protection?
         
     | 
| 
      
 86 
     | 
    
         
            +
                        # We save the transition first with most_recent falsy, then mark most_recent
         
     | 
| 
      
 87 
     | 
    
         
            +
                        # true after to avoid letting MySQL acquire a next-key lock which can cause
         
     | 
| 
      
 88 
     | 
    
         
            +
                        # deadlocks.
         
     | 
| 
      
 89 
     | 
    
         
            +
                        #
         
     | 
| 
      
 90 
     | 
    
         
            +
                        # To avoid an additional query, we manually adjust the most_recent attribute
         
     | 
| 
      
 91 
     | 
    
         
            +
                        # on our transition assuming that update_most_recents will have set it to true
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
                        transition.save!
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                        unless update_most_recents(transition.id).positive?
         
     | 
| 
      
 96 
     | 
    
         
            +
                          raise ActiveRecord::Rollback, "failed to update most_recent"
         
     | 
| 
      
 97 
     | 
    
         
            +
                        end
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
                        transition.assign_attributes(most_recent: true)
         
     | 
| 
      
 100 
     | 
    
         
            +
                      else
         
     | 
| 
      
 101 
     | 
    
         
            +
                        update_most_recents
         
     | 
| 
      
 102 
     | 
    
         
            +
                        transition.assign_attributes(most_recent: true)
         
     | 
| 
      
 103 
     | 
    
         
            +
                        transition.save!
         
     | 
| 
      
 104 
     | 
    
         
            +
                      end
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
       81 
106 
     | 
    
         
             
                      @last_transition = transition
         
     | 
| 
       82 
107 
     | 
    
         
             
                      @observer.execute(:after, from, to, transition)
         
     | 
| 
       83 
108 
     | 
    
         
             
                      add_after_commit_callback(from, to, transition)
         
     | 
| 
         @@ -85,6 +110,16 @@ module Statesman 
     | 
|
| 
       85 
110 
     | 
    
         | 
| 
       86 
111 
     | 
    
         
             
                    transition
         
     | 
| 
       87 
112 
     | 
    
         
             
                  end
         
     | 
| 
      
 113 
     | 
    
         
            +
                  # rubocop:enable Metrics/MethodLength
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
                  def default_transition_attributes(to, metadata)
         
     | 
| 
      
 116 
     | 
    
         
            +
                    {
         
     | 
| 
      
 117 
     | 
    
         
            +
                      to_state: to,
         
     | 
| 
      
 118 
     | 
    
         
            +
                      sort_key: next_sort_key,
         
     | 
| 
      
 119 
     | 
    
         
            +
                      metadata: metadata,
         
     | 
| 
      
 120 
     | 
    
         
            +
                      most_recent: not_most_recent_value(db_cast: false),
         
     | 
| 
      
 121 
     | 
    
         
            +
                    }
         
     | 
| 
      
 122 
     | 
    
         
            +
                  end
         
     | 
| 
       88 
123 
     | 
    
         | 
| 
       89 
124 
     | 
    
         
             
                  def add_after_commit_callback(from, to, transition)
         
     | 
| 
       90 
125 
     | 
    
         
             
                    ::ActiveRecord::Base.connection.add_transaction_record(
         
     | 
| 
         @@ -98,20 +133,92 @@ module Statesman 
     | 
|
| 
       98 
133 
     | 
    
         
             
                    parent_model.send(@association_name)
         
     | 
| 
       99 
134 
     | 
    
         
             
                  end
         
     | 
| 
       100 
135 
     | 
    
         | 
| 
       101 
     | 
    
         
            -
                   
     | 
| 
       102 
     | 
    
         
            -
             
     | 
| 
      
 136 
     | 
    
         
            +
                  # Sets the given transition most_recent = t while unsetting the most_recent of any
         
     | 
| 
      
 137 
     | 
    
         
            +
                  # previous transitions.
         
     | 
| 
      
 138 
     | 
    
         
            +
                  def update_most_recents(most_recent_id = nil)
         
     | 
| 
      
 139 
     | 
    
         
            +
                    update = build_arel_manager(::Arel::UpdateManager)
         
     | 
| 
      
 140 
     | 
    
         
            +
                    update.table(transition_table)
         
     | 
| 
      
 141 
     | 
    
         
            +
                    update.where(most_recent_transitions(most_recent_id))
         
     | 
| 
      
 142 
     | 
    
         
            +
                    update.set(build_most_recents_update_all_values(most_recent_id))
         
     | 
| 
       103 
143 
     | 
    
         | 
| 
       104 
     | 
    
         
            -
                    #  
     | 
| 
       105 
     | 
    
         
            -
                    #  
     | 
| 
       106 
     | 
    
         
            -
                    #
         
     | 
| 
       107 
     | 
    
         
            -
                     
     | 
| 
       108 
     | 
    
         
            -
             
     | 
| 
       109 
     | 
    
         
            -
                     
     | 
| 
       110 
     | 
    
         
            -
             
     | 
| 
       111 
     | 
    
         
            -
             
     | 
| 
       112 
     | 
    
         
            -
             
     | 
| 
      
 144 
     | 
    
         
            +
                    # MySQL will validate index constraints across the intermediate result of an
         
     | 
| 
      
 145 
     | 
    
         
            +
                    # update. This means we must order our update to deactivate the previous
         
     | 
| 
      
 146 
     | 
    
         
            +
                    # most_recent before setting the new row to be true.
         
     | 
| 
      
 147 
     | 
    
         
            +
                    update.order(transition_table[:most_recent].desc) if mysql_gaplock_protection?
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
                    ::ActiveRecord::Base.connection.update(update.to_sql)
         
     | 
| 
      
 150 
     | 
    
         
            +
                  end
         
     | 
| 
      
 151 
     | 
    
         
            +
             
     | 
| 
      
 152 
     | 
    
         
            +
                  def most_recent_transitions(most_recent_id = nil)
         
     | 
| 
      
 153 
     | 
    
         
            +
                    if most_recent_id
         
     | 
| 
      
 154 
     | 
    
         
            +
                      transitions_of_parent.and(
         
     | 
| 
      
 155 
     | 
    
         
            +
                        transition_table[:id].eq(most_recent_id).or(
         
     | 
| 
      
 156 
     | 
    
         
            +
                          transition_table[:most_recent].eq(true),
         
     | 
| 
      
 157 
     | 
    
         
            +
                        ),
         
     | 
| 
      
 158 
     | 
    
         
            +
                      )
         
     | 
| 
       113 
159 
     | 
    
         
             
                    else
         
     | 
| 
       114 
     | 
    
         
            -
                      most_recent. 
     | 
| 
      
 160 
     | 
    
         
            +
                      transitions_of_parent.and(transition_table[:most_recent].eq(true))
         
     | 
| 
      
 161 
     | 
    
         
            +
                    end
         
     | 
| 
      
 162 
     | 
    
         
            +
                  end
         
     | 
| 
      
 163 
     | 
    
         
            +
             
     | 
| 
      
 164 
     | 
    
         
            +
                  def transitions_of_parent
         
     | 
| 
      
 165 
     | 
    
         
            +
                    transition_table[parent_join_foreign_key.to_sym].eq(parent_model.id)
         
     | 
| 
      
 166 
     | 
    
         
            +
                  end
         
     | 
| 
      
 167 
     | 
    
         
            +
             
     | 
| 
      
 168 
     | 
    
         
            +
                  # Generates update_all Arel values that will touch the updated timestamp (if valid
         
     | 
| 
      
 169 
     | 
    
         
            +
                  # for this model) and set most_recent to true only for the transition with a
         
     | 
| 
      
 170 
     | 
    
         
            +
                  # matching most_recent ID.
         
     | 
| 
      
 171 
     | 
    
         
            +
                  #
         
     | 
| 
      
 172 
     | 
    
         
            +
                  # This is quite nasty, but combines two updates (set all most_recent = f, set
         
     | 
| 
      
 173 
     | 
    
         
            +
                  # current most_recent = t) into one, which helps improve transition performance
         
     | 
| 
      
 174 
     | 
    
         
            +
                  # especially when database latency is significant.
         
     | 
| 
      
 175 
     | 
    
         
            +
                  #
         
     | 
| 
      
 176 
     | 
    
         
            +
                  # The SQL this can help produce looks like:
         
     | 
| 
      
 177 
     | 
    
         
            +
                  #
         
     | 
| 
      
 178 
     | 
    
         
            +
                  #   update transitions
         
     | 
| 
      
 179 
     | 
    
         
            +
                  #      set most_recent = (case when id = 'PA123' then TRUE else FALSE end)
         
     | 
| 
      
 180 
     | 
    
         
            +
                  #        , updated_at = '...'
         
     | 
| 
      
 181 
     | 
    
         
            +
                  #      ...
         
     | 
| 
      
 182 
     | 
    
         
            +
                  #
         
     | 
| 
      
 183 
     | 
    
         
            +
                  def build_most_recents_update_all_values(most_recent_id = nil)
         
     | 
| 
      
 184 
     | 
    
         
            +
                    [
         
     | 
| 
      
 185 
     | 
    
         
            +
                      [
         
     | 
| 
      
 186 
     | 
    
         
            +
                        transition_table[:most_recent],
         
     | 
| 
      
 187 
     | 
    
         
            +
                        Arel::Nodes::SqlLiteral.new(most_recent_value(most_recent_id)),
         
     | 
| 
      
 188 
     | 
    
         
            +
                      ],
         
     | 
| 
      
 189 
     | 
    
         
            +
                    ].tap do |values|
         
     | 
| 
      
 190 
     | 
    
         
            +
                      # Only if we support the updated at timestamps should we add this column to the
         
     | 
| 
      
 191 
     | 
    
         
            +
                      # update
         
     | 
| 
      
 192 
     | 
    
         
            +
                      updated_column, updated_at = updated_column_and_timestamp
         
     | 
| 
      
 193 
     | 
    
         
            +
             
     | 
| 
      
 194 
     | 
    
         
            +
                      if updated_column
         
     | 
| 
      
 195 
     | 
    
         
            +
                        values << [
         
     | 
| 
      
 196 
     | 
    
         
            +
                          transition_table[updated_column.to_sym],
         
     | 
| 
      
 197 
     | 
    
         
            +
                          updated_at,
         
     | 
| 
      
 198 
     | 
    
         
            +
                        ]
         
     | 
| 
      
 199 
     | 
    
         
            +
                      end
         
     | 
| 
      
 200 
     | 
    
         
            +
                    end
         
     | 
| 
      
 201 
     | 
    
         
            +
                  end
         
     | 
| 
      
 202 
     | 
    
         
            +
             
     | 
| 
      
 203 
     | 
    
         
            +
                  def most_recent_value(most_recent_id)
         
     | 
| 
      
 204 
     | 
    
         
            +
                    if most_recent_id
         
     | 
| 
      
 205 
     | 
    
         
            +
                      Arel::Nodes::Case.new.
         
     | 
| 
      
 206 
     | 
    
         
            +
                        when(transition_table[:id].eq(most_recent_id)).then(db_true).
         
     | 
| 
      
 207 
     | 
    
         
            +
                        else(not_most_recent_value).to_sql
         
     | 
| 
      
 208 
     | 
    
         
            +
                    else
         
     | 
| 
      
 209 
     | 
    
         
            +
                      Arel::Nodes::SqlLiteral.new(not_most_recent_value)
         
     | 
| 
      
 210 
     | 
    
         
            +
                    end
         
     | 
| 
      
 211 
     | 
    
         
            +
                  end
         
     | 
| 
      
 212 
     | 
    
         
            +
             
     | 
| 
      
 213 
     | 
    
         
            +
                  # Provide a wrapper for constructing an update manager which handles a breaking API
         
     | 
| 
      
 214 
     | 
    
         
            +
                  # change in Arel as we move into Rails >6.0.
         
     | 
| 
      
 215 
     | 
    
         
            +
                  #
         
     | 
| 
      
 216 
     | 
    
         
            +
                  # https://github.com/rails/rails/commit/7508284800f67b4611c767bff9eae7045674b66f
         
     | 
| 
      
 217 
     | 
    
         
            +
                  def build_arel_manager(manager)
         
     | 
| 
      
 218 
     | 
    
         
            +
                    if manager.instance_method(:initialize).arity.zero?
         
     | 
| 
      
 219 
     | 
    
         
            +
                      manager.new
         
     | 
| 
      
 220 
     | 
    
         
            +
                    else
         
     | 
| 
      
 221 
     | 
    
         
            +
                      manager.new(::ActiveRecord::Base)
         
     | 
| 
       115 
222 
     | 
    
         
             
                    end
         
     | 
| 
       116 
223 
     | 
    
         
             
                  end
         
     | 
| 
       117 
224 
     | 
    
         | 
| 
         @@ -170,7 +277,8 @@ module Statesman 
     | 
|
| 
       170 
277 
     | 
    
         
             
                    end
         
     | 
| 
       171 
278 
     | 
    
         
             
                  end
         
     | 
| 
       172 
279 
     | 
    
         | 
| 
       173 
     | 
    
         
            -
                   
     | 
| 
      
 280 
     | 
    
         
            +
                  # updated_column_and_timestamp should return [column_name, value]
         
     | 
| 
      
 281 
     | 
    
         
            +
                  def updated_column_and_timestamp
         
     | 
| 
       174 
282 
     | 
    
         
             
                    # TODO: Once we've set expectations that transition classes should conform to
         
     | 
| 
       175 
283 
     | 
    
         
             
                    # the interface of Adapters::ActiveRecordTransition as a breaking change in the
         
     | 
| 
       176 
284 
     | 
    
         
             
                    # next major version, we can stop calling `#respond_to?` first and instead
         
     | 
| 
         @@ -184,15 +292,51 @@ module Statesman 
     | 
|
| 
       184 
292 
     | 
    
         
             
                               ActiveRecordTransition::DEFAULT_UPDATED_TIMESTAMP_COLUMN
         
     | 
| 
       185 
293 
     | 
    
         
             
                             end
         
     | 
| 
       186 
294 
     | 
    
         | 
| 
       187 
     | 
    
         
            -
                     
     | 
| 
      
 295 
     | 
    
         
            +
                    # No updated timestamp column, don't return anything
         
     | 
| 
      
 296 
     | 
    
         
            +
                    return nil if column.nil?
         
     | 
| 
       188 
297 
     | 
    
         | 
| 
       189 
     | 
    
         
            -
                     
     | 
| 
       190 
     | 
    
         
            -
             
     | 
| 
       191 
     | 
    
         
            -
             
     | 
| 
       192 
     | 
    
         
            -
             
     | 
| 
       193 
     | 
    
         
            -
             
     | 
| 
      
 298 
     | 
    
         
            +
                    [
         
     | 
| 
      
 299 
     | 
    
         
            +
                      column, ::ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
         
     | 
| 
      
 300 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 301 
     | 
    
         
            +
                  end
         
     | 
| 
      
 302 
     | 
    
         
            +
             
     | 
| 
      
 303 
     | 
    
         
            +
                  def mysql_gaplock_protection?
         
     | 
| 
      
 304 
     | 
    
         
            +
                    Statesman.mysql_gaplock_protection?
         
     | 
| 
      
 305 
     | 
    
         
            +
                  end
         
     | 
| 
      
 306 
     | 
    
         
            +
             
     | 
| 
      
 307 
     | 
    
         
            +
                  def db_true
         
     | 
| 
      
 308 
     | 
    
         
            +
                    value = ::ActiveRecord::Base.connection.type_cast(
         
     | 
| 
      
 309 
     | 
    
         
            +
                      true,
         
     | 
| 
      
 310 
     | 
    
         
            +
                      transition_class.columns_hash["most_recent"],
         
     | 
| 
      
 311 
     | 
    
         
            +
                    )
         
     | 
| 
      
 312 
     | 
    
         
            +
                    ::ActiveRecord::Base.connection.quote(value)
         
     | 
| 
      
 313 
     | 
    
         
            +
                  end
         
     | 
| 
      
 314 
     | 
    
         
            +
             
     | 
| 
      
 315 
     | 
    
         
            +
                  def db_false
         
     | 
| 
      
 316 
     | 
    
         
            +
                    value = ::ActiveRecord::Base.connection.type_cast(
         
     | 
| 
      
 317 
     | 
    
         
            +
                      false,
         
     | 
| 
      
 318 
     | 
    
         
            +
                      transition_class.columns_hash["most_recent"],
         
     | 
| 
      
 319 
     | 
    
         
            +
                    )
         
     | 
| 
      
 320 
     | 
    
         
            +
                    ::ActiveRecord::Base.connection.quote(value)
         
     | 
| 
      
 321 
     | 
    
         
            +
                  end
         
     | 
| 
      
 322 
     | 
    
         
            +
             
     | 
| 
      
 323 
     | 
    
         
            +
                  def db_null
         
     | 
| 
      
 324 
     | 
    
         
            +
                    Arel::Nodes::SqlLiteral.new("NULL")
         
     | 
| 
      
 325 
     | 
    
         
            +
                  end
         
     | 
| 
      
 326 
     | 
    
         
            +
             
     | 
| 
      
 327 
     | 
    
         
            +
                  # Check whether the `most_recent` column allows null values. If it doesn't, set old
         
     | 
| 
      
 328 
     | 
    
         
            +
                  # records to `false`, otherwise, set them to `NULL`.
         
     | 
| 
      
 329 
     | 
    
         
            +
                  #
         
     | 
| 
      
 330 
     | 
    
         
            +
                  # Some conditioning here is required to support databases that don't support partial
         
     | 
| 
      
 331 
     | 
    
         
            +
                  # indexes. By doing the conditioning on the column, rather than Rails' opinion of
         
     | 
| 
      
 332 
     | 
    
         
            +
                  # whether the database supports partial indexes, we're robust to DBs later adding
         
     | 
| 
      
 333 
     | 
    
         
            +
                  # support for partial indexes.
         
     | 
| 
      
 334 
     | 
    
         
            +
                  def not_most_recent_value(db_cast: true)
         
     | 
| 
      
 335 
     | 
    
         
            +
                    if transition_class.columns_hash["most_recent"].null == false
         
     | 
| 
      
 336 
     | 
    
         
            +
                      return db_cast ? db_false : false
         
     | 
| 
      
 337 
     | 
    
         
            +
                    end
         
     | 
| 
       194 
338 
     | 
    
         | 
| 
       195 
     | 
    
         
            -
                     
     | 
| 
      
 339 
     | 
    
         
            +
                    db_cast ? db_null : nil
         
     | 
| 
       196 
340 
     | 
    
         
             
                  end
         
     | 
| 
       197 
341 
     | 
    
         
             
                end
         
     | 
| 
       198 
342 
     | 
    
         | 
    
        data/lib/statesman/config.rb
    CHANGED
    
    | 
         @@ -14,5 +14,31 @@ module Statesman 
     | 
|
| 
       14 
14 
     | 
    
         
             
                def storage_adapter(adapter_class)
         
     | 
| 
       15 
15 
     | 
    
         
             
                  @adapter_class = adapter_class
         
     | 
| 
       16 
16 
     | 
    
         
             
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                def mysql_gaplock_protection?
         
     | 
| 
      
 19 
     | 
    
         
            +
                  return @mysql_gaplock_protection unless @mysql_gaplock_protection.nil?
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  # If our adapter class suggests we're using mysql, enable gaplock protection by
         
     | 
| 
      
 22 
     | 
    
         
            +
                  # default.
         
     | 
| 
      
 23 
     | 
    
         
            +
                  enable_mysql_gaplock_protection if mysql_adapter?(adapter_class)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  @mysql_gaplock_protection
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                def enable_mysql_gaplock_protection
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @mysql_gaplock_protection = true
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                private
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                def mysql_adapter?(adapter_class)
         
     | 
| 
      
 34 
     | 
    
         
            +
                  adapter_name = adapter_name(adapter_class)
         
     | 
| 
      
 35 
     | 
    
         
            +
                  return false unless adapter_name
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  adapter_name.start_with?("mysql")
         
     | 
| 
      
 38 
     | 
    
         
            +
                end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                def adapter_name(adapter_class)
         
     | 
| 
      
 41 
     | 
    
         
            +
                  adapter_class.respond_to?(:adapter_name) && adapter_class&.adapter_name
         
     | 
| 
      
 42 
     | 
    
         
            +
                end
         
     | 
| 
       17 
43 
     | 
    
         
             
              end
         
     | 
| 
       18 
44 
     | 
    
         
             
            end
         
     | 
    
        data/lib/statesman/exceptions.rb
    CHANGED
    
    | 
         @@ -4,16 +4,21 @@ module Statesman 
     | 
|
| 
       4 
4 
     | 
    
         
             
              class InvalidStateError < StandardError; end
         
     | 
| 
       5 
5 
     | 
    
         
             
              class InvalidTransitionError < StandardError; end
         
     | 
| 
       6 
6 
     | 
    
         
             
              class InvalidCallbackError < StandardError; end
         
     | 
| 
      
 7 
     | 
    
         
            +
              class TransitionConflictError < StandardError; end
         
     | 
| 
      
 8 
     | 
    
         
            +
              class MissingTransitionAssociation < StandardError; end
         
     | 
| 
       7 
9 
     | 
    
         | 
| 
       8 
10 
     | 
    
         
             
              class TransitionFailedError < StandardError
         
     | 
| 
       9 
11 
     | 
    
         
             
                def initialize(from, to)
         
     | 
| 
       10 
12 
     | 
    
         
             
                  @from = from
         
     | 
| 
       11 
13 
     | 
    
         
             
                  @to = to
         
     | 
| 
      
 14 
     | 
    
         
            +
                  super(_message)
         
     | 
| 
       12 
15 
     | 
    
         
             
                end
         
     | 
| 
       13 
16 
     | 
    
         | 
| 
       14 
17 
     | 
    
         
             
                attr_reader :from, :to
         
     | 
| 
       15 
18 
     | 
    
         | 
| 
       16 
     | 
    
         
            -
                 
     | 
| 
      
 19 
     | 
    
         
            +
                private
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                def _message
         
     | 
| 
       17 
22 
     | 
    
         
             
                  "Cannot transition from '#{from}' to '#{to}'"
         
     | 
| 
       18 
23 
     | 
    
         
             
                end
         
     | 
| 
       19 
24 
     | 
    
         
             
              end
         
     | 
| 
         @@ -22,18 +27,18 @@ module Statesman 
     | 
|
| 
       22 
27 
     | 
    
         
             
                def initialize(from, to)
         
     | 
| 
       23 
28 
     | 
    
         
             
                  @from = from
         
     | 
| 
       24 
29 
     | 
    
         
             
                  @to = to
         
     | 
| 
      
 30 
     | 
    
         
            +
                  super(_message)
         
     | 
| 
       25 
31 
     | 
    
         
             
                end
         
     | 
| 
       26 
32 
     | 
    
         | 
| 
       27 
33 
     | 
    
         
             
                attr_reader :from, :to
         
     | 
| 
       28 
34 
     | 
    
         | 
| 
       29 
     | 
    
         
            -
                 
     | 
| 
      
 35 
     | 
    
         
            +
                private
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                def _message
         
     | 
| 
       30 
38 
     | 
    
         
             
                  "Guard on transition from: '#{from}' to '#{to}' returned false"
         
     | 
| 
       31 
39 
     | 
    
         
             
                end
         
     | 
| 
       32 
40 
     | 
    
         
             
              end
         
     | 
| 
       33 
41 
     | 
    
         | 
| 
       34 
     | 
    
         
            -
              class TransitionConflictError < StandardError; end
         
     | 
| 
       35 
     | 
    
         
            -
              class MissingTransitionAssociation < StandardError; end
         
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
       37 
42 
     | 
    
         
             
              class UnserializedMetadataError < StandardError
         
     | 
| 
       38 
43 
     | 
    
         
             
                def initialize(transition_class_name)
         
     | 
| 
       39 
44 
     | 
    
         
             
                  super(_message(transition_class_name))
         
     | 
    
        data/lib/statesman/machine.rb
    CHANGED
    
    
    
        data/lib/statesman/version.rb
    CHANGED
    
    
| 
         @@ -20,7 +20,9 @@ describe Statesman::Adapters::ActiveRecordQueries, active_record: true do 
     | 
|
| 
       20 
20 
     | 
    
         
             
                prepare_other_model_table
         
     | 
| 
       21 
21 
     | 
    
         
             
                prepare_other_transitions_table
         
     | 
| 
       22 
22 
     | 
    
         | 
| 
       23 
     | 
    
         
            -
                Statesman.configure  
     | 
| 
      
 23 
     | 
    
         
            +
                Statesman.configure do
         
     | 
| 
      
 24 
     | 
    
         
            +
                  storage_adapter(Statesman::Adapters::ActiveRecord)
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
       24 
26 
     | 
    
         
             
              end
         
     | 
| 
       25 
27 
     | 
    
         | 
| 
       26 
28 
     | 
    
         
             
              after { Statesman.configure { storage_adapter(Statesman::Adapters::Memory) } }
         
     | 
| 
         @@ -9,9 +9,19 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do 
     | 
|
| 
       9 
9 
     | 
    
         
             
              before do
         
     | 
| 
       10 
10 
     | 
    
         
             
                prepare_model_table
         
     | 
| 
       11 
11 
     | 
    
         
             
                prepare_transitions_table
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                MyActiveRecordModelTransition.serialize(:metadata, JSON)
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                Statesman.configure do
         
     | 
| 
      
 16 
     | 
    
         
            +
                  # Rubocop requires described_class to be used, but this block
         
     | 
| 
      
 17 
     | 
    
         
            +
                  # is instance_eval'd and described_class won't be defined
         
     | 
| 
      
 18 
     | 
    
         
            +
                  # rubocop:disable RSpec/DescribedClass
         
     | 
| 
      
 19 
     | 
    
         
            +
                  storage_adapter(Statesman::Adapters::ActiveRecord)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  # rubocop:enable RSpec/DescribedClass
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
       12 
22 
     | 
    
         
             
              end
         
     | 
| 
       13 
23 
     | 
    
         | 
| 
       14 
     | 
    
         
            -
               
     | 
| 
      
 24 
     | 
    
         
            +
              after { Statesman.configure { storage_adapter(Statesman::Adapters::Memory) } }
         
     | 
| 
       15 
25 
     | 
    
         | 
| 
       16 
26 
     | 
    
         
             
              let(:observer) { double(Statesman::Machine, execute: nil) }
         
     | 
| 
       17 
27 
     | 
    
         
             
              let(:model) { MyActiveRecordModel.create(current_state: :pending) }
         
     | 
| 
         @@ -345,6 +355,19 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do 
     | 
|
| 
       345 
355 
     | 
    
         
             
                end
         
     | 
| 
       346 
356 
     | 
    
         
             
              end
         
     | 
| 
       347 
357 
     | 
    
         | 
| 
      
 358 
     | 
    
         
            +
              it "resets last with #reload" do
         
     | 
| 
      
 359 
     | 
    
         
            +
                model.save!
         
     | 
| 
      
 360 
     | 
    
         
            +
                ActiveRecord::Base.transaction do
         
     | 
| 
      
 361 
     | 
    
         
            +
                  model.state_machine.transition_to!(:succeeded)
         
     | 
| 
      
 362 
     | 
    
         
            +
                  # force to cache value in last_transition instance variable
         
     | 
| 
      
 363 
     | 
    
         
            +
                  expect(model.state_machine.current_state).to eq("succeeded")
         
     | 
| 
      
 364 
     | 
    
         
            +
                  raise ActiveRecord::Rollback
         
     | 
| 
      
 365 
     | 
    
         
            +
                end
         
     | 
| 
      
 366 
     | 
    
         
            +
                expect(model.state_machine.current_state).to eq("succeeded")
         
     | 
| 
      
 367 
     | 
    
         
            +
                model.reload
         
     | 
| 
      
 368 
     | 
    
         
            +
                expect(model.state_machine.current_state).to eq("initial")
         
     | 
| 
      
 369 
     | 
    
         
            +
              end
         
     | 
| 
      
 370 
     | 
    
         
            +
             
     | 
| 
       348 
371 
     | 
    
         
             
              context "with a namespaced model" do
         
     | 
| 
       349 
372 
     | 
    
         
             
                before do
         
     | 
| 
       350 
373 
     | 
    
         
             
                  CreateNamespacedARModelMigration.migrate(:up)
         
     | 
| 
         @@ -0,0 +1,88 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "spec_helper"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            describe Statesman do
         
     | 
| 
      
 6 
     | 
    
         
            +
              describe "InvalidStateError" do
         
     | 
| 
      
 7 
     | 
    
         
            +
                subject(:error) { Statesman::InvalidStateError.new }
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                its(:message) { is_expected.to eq("Statesman::InvalidStateError") }
         
     | 
| 
      
 10 
     | 
    
         
            +
                its "string matches its message" do
         
     | 
| 
      
 11 
     | 
    
         
            +
                  expect(error.to_s).to eq(error.message)
         
     | 
| 
      
 12 
     | 
    
         
            +
                end
         
     | 
| 
      
 13 
     | 
    
         
            +
              end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              describe "InvalidTransitionError" do
         
     | 
| 
      
 16 
     | 
    
         
            +
                subject(:error) { Statesman::InvalidTransitionError.new }
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                its(:message) { is_expected.to eq("Statesman::InvalidTransitionError") }
         
     | 
| 
      
 19 
     | 
    
         
            +
                its "string matches its message" do
         
     | 
| 
      
 20 
     | 
    
         
            +
                  expect(error.to_s).to eq(error.message)
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
              describe "InvalidCallbackError" do
         
     | 
| 
      
 25 
     | 
    
         
            +
                subject(:error) { Statesman::InvalidTransitionError.new }
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                its(:message) { is_expected.to eq("Statesman::InvalidTransitionError") }
         
     | 
| 
      
 28 
     | 
    
         
            +
                its "string matches its message" do
         
     | 
| 
      
 29 
     | 
    
         
            +
                  expect(error.to_s).to eq(error.message)
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
              end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
              describe "TransitionConflictError" do
         
     | 
| 
      
 34 
     | 
    
         
            +
                subject(:error) { Statesman::TransitionConflictError.new }
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                its(:message) { is_expected.to eq("Statesman::TransitionConflictError") }
         
     | 
| 
      
 37 
     | 
    
         
            +
                its "string matches its message" do
         
     | 
| 
      
 38 
     | 
    
         
            +
                  expect(error.to_s).to eq(error.message)
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
              end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
              describe "MissingTransitionAssociation" do
         
     | 
| 
      
 43 
     | 
    
         
            +
                subject(:error) { Statesman::MissingTransitionAssociation.new }
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                its(:message) { is_expected.to eq("Statesman::MissingTransitionAssociation") }
         
     | 
| 
      
 46 
     | 
    
         
            +
                its "string matches its message" do
         
     | 
| 
      
 47 
     | 
    
         
            +
                  expect(error.to_s).to eq(error.message)
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
              end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
              describe "TransitionFailedError" do
         
     | 
| 
      
 52 
     | 
    
         
            +
                subject(:error) { Statesman::TransitionFailedError.new("from", "to") }
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                its(:message) { is_expected.to eq("Cannot transition from 'from' to 'to'") }
         
     | 
| 
      
 55 
     | 
    
         
            +
                its "string matches its message" do
         
     | 
| 
      
 56 
     | 
    
         
            +
                  expect(error.to_s).to eq(error.message)
         
     | 
| 
      
 57 
     | 
    
         
            +
                end
         
     | 
| 
      
 58 
     | 
    
         
            +
              end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
              describe "GuardFailedError" do
         
     | 
| 
      
 61 
     | 
    
         
            +
                subject(:error) { Statesman::GuardFailedError.new("from", "to") }
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                its(:message) do
         
     | 
| 
      
 64 
     | 
    
         
            +
                  is_expected.to eq("Guard on transition from: 'from' to 'to' returned false")
         
     | 
| 
      
 65 
     | 
    
         
            +
                end
         
     | 
| 
      
 66 
     | 
    
         
            +
                its "string matches its message" do
         
     | 
| 
      
 67 
     | 
    
         
            +
                  expect(error.to_s).to eq(error.message)
         
     | 
| 
      
 68 
     | 
    
         
            +
                end
         
     | 
| 
      
 69 
     | 
    
         
            +
              end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
              describe "UnserializedMetadataError" do
         
     | 
| 
      
 72 
     | 
    
         
            +
                subject(:error) { Statesman::UnserializedMetadataError.new("foo") }
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                its(:message) { is_expected.to match(/foo#metadata is not serialized/) }
         
     | 
| 
      
 75 
     | 
    
         
            +
                its "string matches its message" do
         
     | 
| 
      
 76 
     | 
    
         
            +
                  expect(error.to_s).to eq(error.message)
         
     | 
| 
      
 77 
     | 
    
         
            +
                end
         
     | 
| 
      
 78 
     | 
    
         
            +
              end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
              describe "IncompatibleSerializationError" do
         
     | 
| 
      
 81 
     | 
    
         
            +
                subject(:error) { Statesman::IncompatibleSerializationError.new("foo") }
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                its(:message) { is_expected.to match(/foo#metadata column type cannot be json/) }
         
     | 
| 
      
 84 
     | 
    
         
            +
                its "string matches its message" do
         
     | 
| 
      
 85 
     | 
    
         
            +
                  expect(error.to_s).to eq(error.message)
         
     | 
| 
      
 86 
     | 
    
         
            +
                end
         
     | 
| 
      
 87 
     | 
    
         
            +
              end
         
     | 
| 
      
 88 
     | 
    
         
            +
            end
         
     | 
    
        data/statesman.gemspec
    CHANGED
    
    | 
         @@ -4,6 +4,8 @@ lib = File.expand_path("lib", __dir__) 
     | 
|
| 
       4 
4 
     | 
    
         
             
            $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
         
     | 
| 
       5 
5 
     | 
    
         
             
            require "statesman/version"
         
     | 
| 
       6 
6 
     | 
    
         | 
| 
      
 7 
     | 
    
         
            +
            GITHUB_URL = "https://github.com/gocardless/statesman"
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
       7 
9 
     | 
    
         
             
            Gem::Specification.new do |spec|
         
     | 
| 
       8 
10 
     | 
    
         
             
              spec.name          = "statesman"
         
     | 
| 
       9 
11 
     | 
    
         
             
              spec.version       = Statesman::VERSION
         
     | 
| 
         @@ -11,7 +13,7 @@ Gem::Specification.new do |spec| 
     | 
|
| 
       11 
13 
     | 
    
         
             
              spec.email         = ["developers@gocardless.com"]
         
     | 
| 
       12 
14 
     | 
    
         
             
              spec.description   = "A statesman-like state machine library"
         
     | 
| 
       13 
15 
     | 
    
         
             
              spec.summary       = spec.description
         
     | 
| 
       14 
     | 
    
         
            -
              spec.homepage      =  
     | 
| 
      
 16 
     | 
    
         
            +
              spec.homepage      = GITHUB_URL
         
     | 
| 
       15 
17 
     | 
    
         
             
              spec.license       = "MIT"
         
     | 
| 
       16 
18 
     | 
    
         | 
| 
       17 
19 
     | 
    
         
             
              spec.files         = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
         
     | 
| 
         @@ -25,7 +27,7 @@ Gem::Specification.new do |spec| 
     | 
|
| 
       25 
27 
     | 
    
         
             
              spec.add_development_dependency "bundler", "~> 2.1.4"
         
     | 
| 
       26 
28 
     | 
    
         
             
              spec.add_development_dependency "gc_ruboconfig", "~> 2.3.9"
         
     | 
| 
       27 
29 
     | 
    
         
             
              spec.add_development_dependency "mysql2", ">= 0.4", "< 0.6"
         
     | 
| 
       28 
     | 
    
         
            -
              spec.add_development_dependency "pg", " 
     | 
| 
      
 30 
     | 
    
         
            +
              spec.add_development_dependency "pg", ">= 0.18", "<= 1.3"
         
     | 
| 
       29 
31 
     | 
    
         
             
              spec.add_development_dependency "pry"
         
     | 
| 
       30 
32 
     | 
    
         
             
              spec.add_development_dependency "rails", ">= 5.2"
         
     | 
| 
       31 
33 
     | 
    
         
             
              spec.add_development_dependency "rake", "~> 13.0.0"
         
     | 
| 
         @@ -33,6 +35,14 @@ Gem::Specification.new do |spec| 
     | 
|
| 
       33 
35 
     | 
    
         
             
              spec.add_development_dependency "rspec-its", "~> 1.1"
         
     | 
| 
       34 
36 
     | 
    
         
             
              spec.add_development_dependency "rspec-rails", "~> 3.1"
         
     | 
| 
       35 
37 
     | 
    
         
             
              spec.add_development_dependency "rspec_junit_formatter", "~> 0.4.0"
         
     | 
| 
       36 
     | 
    
         
            -
              spec.add_development_dependency "sqlite3", "~> 1. 
     | 
| 
      
 38 
     | 
    
         
            +
              spec.add_development_dependency "sqlite3", "~> 1.4.2"
         
     | 
| 
       37 
39 
     | 
    
         
             
              spec.add_development_dependency "timecop", "~> 0.9.1"
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
              spec.metadata = {
         
     | 
| 
      
 42 
     | 
    
         
            +
                "bug_tracker_uri" => "#{GITHUB_URL}/issues",
         
     | 
| 
      
 43 
     | 
    
         
            +
                "changelog_uri" => "#{GITHUB_URL}/blob/master/CHANGELOG.md",
         
     | 
| 
      
 44 
     | 
    
         
            +
                "documentation_uri" => "#{GITHUB_URL}/blob/master/README.md",
         
     | 
| 
      
 45 
     | 
    
         
            +
                "homepage_uri" => GITHUB_URL,
         
     | 
| 
      
 46 
     | 
    
         
            +
                "source_code_uri" => GITHUB_URL,
         
     | 
| 
      
 47 
     | 
    
         
            +
              }
         
     | 
| 
       38 
48 
     | 
    
         
             
            end
         
     | 
    
        metadata
    CHANGED
    
    | 
         @@ -1,14 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: statesman
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 7. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 7.4.1
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - GoCardless
         
     | 
| 
       8 
     | 
    
         
            -
            autorequire: 
     | 
| 
      
 8 
     | 
    
         
            +
            autorequire:
         
     | 
| 
       9 
9 
     | 
    
         
             
            bindir: bin
         
     | 
| 
       10 
10 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date: 2020- 
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2020-11-19 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
13 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
14 
     | 
    
         
             
              name: ammeter
         
     | 
| 
         @@ -76,16 +76,22 @@ dependencies: 
     | 
|
| 
       76 
76 
     | 
    
         
             
              name: pg
         
     | 
| 
       77 
77 
     | 
    
         
             
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
       78 
78 
     | 
    
         
             
                requirements:
         
     | 
| 
       79 
     | 
    
         
            -
                - - " 
     | 
| 
      
 79 
     | 
    
         
            +
                - - ">="
         
     | 
| 
       80 
80 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       81 
81 
     | 
    
         
             
                    version: '0.18'
         
     | 
| 
      
 82 
     | 
    
         
            +
                - - "<="
         
     | 
| 
      
 83 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 84 
     | 
    
         
            +
                    version: '1.3'
         
     | 
| 
       82 
85 
     | 
    
         
             
              type: :development
         
     | 
| 
       83 
86 
     | 
    
         
             
              prerelease: false
         
     | 
| 
       84 
87 
     | 
    
         
             
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
       85 
88 
     | 
    
         
             
                requirements:
         
     | 
| 
       86 
     | 
    
         
            -
                - - " 
     | 
| 
      
 89 
     | 
    
         
            +
                - - ">="
         
     | 
| 
       87 
90 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       88 
91 
     | 
    
         
             
                    version: '0.18'
         
     | 
| 
      
 92 
     | 
    
         
            +
                - - "<="
         
     | 
| 
      
 93 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 94 
     | 
    
         
            +
                    version: '1.3'
         
     | 
| 
       89 
95 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       90 
96 
     | 
    
         
             
              name: pry
         
     | 
| 
       91 
97 
     | 
    
         
             
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
         @@ -190,14 +196,14 @@ dependencies: 
     | 
|
| 
       190 
196 
     | 
    
         
             
                requirements:
         
     | 
| 
       191 
197 
     | 
    
         
             
                - - "~>"
         
     | 
| 
       192 
198 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       193 
     | 
    
         
            -
                    version: 1. 
     | 
| 
      
 199 
     | 
    
         
            +
                    version: 1.4.2
         
     | 
| 
       194 
200 
     | 
    
         
             
              type: :development
         
     | 
| 
       195 
201 
     | 
    
         
             
              prerelease: false
         
     | 
| 
       196 
202 
     | 
    
         
             
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
       197 
203 
     | 
    
         
             
                requirements:
         
     | 
| 
       198 
204 
     | 
    
         
             
                - - "~>"
         
     | 
| 
       199 
205 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       200 
     | 
    
         
            -
                    version: 1. 
     | 
| 
      
 206 
     | 
    
         
            +
                    version: 1.4.2
         
     | 
| 
       201 
207 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       202 
208 
     | 
    
         
             
              name: timecop
         
     | 
| 
       203 
209 
     | 
    
         
             
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
         @@ -266,6 +272,7 @@ files: 
     | 
|
| 
       266 
272 
     | 
    
         
             
            - spec/statesman/adapters/shared_examples.rb
         
     | 
| 
       267 
273 
     | 
    
         
             
            - spec/statesman/callback_spec.rb
         
     | 
| 
       268 
274 
     | 
    
         
             
            - spec/statesman/config_spec.rb
         
     | 
| 
      
 275 
     | 
    
         
            +
            - spec/statesman/exceptions_spec.rb
         
     | 
| 
       269 
276 
     | 
    
         
             
            - spec/statesman/guard_spec.rb
         
     | 
| 
       270 
277 
     | 
    
         
             
            - spec/statesman/machine_spec.rb
         
     | 
| 
       271 
278 
     | 
    
         
             
            - spec/statesman/utils_spec.rb
         
     | 
| 
         @@ -275,8 +282,13 @@ files: 
     | 
|
| 
       275 
282 
     | 
    
         
             
            homepage: https://github.com/gocardless/statesman
         
     | 
| 
       276 
283 
     | 
    
         
             
            licenses:
         
     | 
| 
       277 
284 
     | 
    
         
             
            - MIT
         
     | 
| 
       278 
     | 
    
         
            -
            metadata: 
     | 
| 
       279 
     | 
    
         
            -
             
     | 
| 
      
 285 
     | 
    
         
            +
            metadata:
         
     | 
| 
      
 286 
     | 
    
         
            +
              bug_tracker_uri: https://github.com/gocardless/statesman/issues
         
     | 
| 
      
 287 
     | 
    
         
            +
              changelog_uri: https://github.com/gocardless/statesman/blob/master/CHANGELOG.md
         
     | 
| 
      
 288 
     | 
    
         
            +
              documentation_uri: https://github.com/gocardless/statesman/blob/master/README.md
         
     | 
| 
      
 289 
     | 
    
         
            +
              homepage_uri: https://github.com/gocardless/statesman
         
     | 
| 
      
 290 
     | 
    
         
            +
              source_code_uri: https://github.com/gocardless/statesman
         
     | 
| 
      
 291 
     | 
    
         
            +
            post_install_message:
         
     | 
| 
       280 
292 
     | 
    
         
             
            rdoc_options: []
         
     | 
| 
       281 
293 
     | 
    
         
             
            require_paths:
         
     | 
| 
       282 
294 
     | 
    
         
             
            - lib
         
     | 
| 
         @@ -291,8 +303,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement 
     | 
|
| 
       291 
303 
     | 
    
         
             
                - !ruby/object:Gem::Version
         
     | 
| 
       292 
304 
     | 
    
         
             
                  version: '0'
         
     | 
| 
       293 
305 
     | 
    
         
             
            requirements: []
         
     | 
| 
       294 
     | 
    
         
            -
            rubygems_version: 3.1 
     | 
| 
       295 
     | 
    
         
            -
            signing_key: 
     | 
| 
      
 306 
     | 
    
         
            +
            rubygems_version: 3.2.0.rc.1
         
     | 
| 
      
 307 
     | 
    
         
            +
            signing_key:
         
     | 
| 
       296 
308 
     | 
    
         
             
            specification_version: 4
         
     | 
| 
       297 
309 
     | 
    
         
             
            summary: A statesman-like state machine library
         
     | 
| 
       298 
310 
     | 
    
         
             
            test_files:
         
     | 
| 
         @@ -310,6 +322,7 @@ test_files: 
     | 
|
| 
       310 
322 
     | 
    
         
             
            - spec/statesman/adapters/shared_examples.rb
         
     | 
| 
       311 
323 
     | 
    
         
             
            - spec/statesman/callback_spec.rb
         
     | 
| 
       312 
324 
     | 
    
         
             
            - spec/statesman/config_spec.rb
         
     | 
| 
      
 325 
     | 
    
         
            +
            - spec/statesman/exceptions_spec.rb
         
     | 
| 
       313 
326 
     | 
    
         
             
            - spec/statesman/guard_spec.rb
         
     | 
| 
       314 
327 
     | 
    
         
             
            - spec/statesman/machine_spec.rb
         
     | 
| 
       315 
328 
     | 
    
         
             
            - spec/statesman/utils_spec.rb
         
     |