statesman 7.4.0 → 10.2.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- <p align="center"><img src="http://f.cl.ly/items/410n2A0S3l1W0i3i0o2K/statesman.png" alt="Statesman"></p>
1
+ <p align="center"><img src="https://user-images.githubusercontent.com/110275/106792848-96e4ee80-664e-11eb-8fd1-16ff24b41eb2.png" alt="Statesman" width="512"></p>
2
2
 
3
3
  A statesmanlike state machine library.
4
4
 
@@ -30,7 +30,7 @@ protection.
30
30
  To get started, just add Statesman to your `Gemfile`, and then run `bundle`:
31
31
 
32
32
  ```ruby
33
- gem 'statesman', '~> 7.1.0'
33
+ gem 'statesman', '~> 10.0.0'
34
34
  ```
35
35
 
36
36
  ## Usage
@@ -109,11 +109,59 @@ Order.first.state_machine.allowed_transitions # => ["checking_out", "cancelled"]
109
109
  Order.first.state_machine.can_transition_to?(:cancelled) # => true/false
110
110
  Order.first.state_machine.transition_to(:cancelled, optional: :metadata) # => true/false
111
111
  Order.first.state_machine.transition_to!(:cancelled) # => true/exception
112
+ Order.first.state_machine.last_transition # => transition model or nil
113
+ Order.first.state_machine.last_transition_to(:pending) # => transition model or nil
112
114
 
113
115
  Order.in_state(:cancelled) # => [#<Order id: "123">]
114
116
  Order.not_in_state(:checking_out) # => [#<Order id: "123">]
115
117
  ```
116
118
 
119
+ If you'd like, you can also define a template for a generic state machine, then alter classes which extend it as required:
120
+
121
+ ```ruby
122
+ module Template
123
+ def define_states
124
+ state :a, initial: true
125
+ state :b
126
+ state :c
127
+ end
128
+
129
+ def define_transitions
130
+ transition from: :a, to: :b
131
+ transition from: :b, to: :c
132
+ transition from: :c, to: :a
133
+ end
134
+ end
135
+
136
+ class Circular
137
+ include Statesman::Machine
138
+ extend Template
139
+
140
+ define_states
141
+ define_transitions
142
+ end
143
+
144
+ class Linear
145
+ include Statesman::Machine
146
+ extend Template
147
+
148
+ define_states
149
+ define_transitions
150
+
151
+ remove_transitions from: :c, to: :a
152
+ end
153
+
154
+ class Shorter
155
+ include Statesman::Machine
156
+ extend Template
157
+
158
+ define_states
159
+ define_transitions
160
+
161
+ remove_state :c
162
+ end
163
+ ```
164
+
117
165
  ## Persistence
118
166
 
119
167
  By default Statesman stores transition history in memory only. It can be
@@ -159,7 +207,8 @@ class Order < ActiveRecord::Base
159
207
 
160
208
  # Optionally delegate some methods
161
209
 
162
- delegate :can_transition_to?, :current_state, :history, :last_transition,
210
+ delegate :can_transition_to?,
211
+ :current_state, :history, :last_transition, :last_transition_to,
163
212
  :transition_to!, :transition_to, :in_state?, to: :state_machine
164
213
  end
165
214
  ```
@@ -322,6 +371,10 @@ Machine.successors
322
371
  #### `Machine#current_state`
323
372
  Returns the current state based on existing transition objects.
324
373
 
374
+ Takes an optional keyword argument to force a reload of data from the
375
+ database.
376
+ e.g `current_state(force_reload: true)`
377
+
325
378
  #### `Machine#in_state?(:state_1, :state_2, ...)`
326
379
  Returns true if the machine is in any of the given states.
327
380
 
@@ -331,6 +384,9 @@ Returns a sorted array of all transition objects.
331
384
  #### `Machine#last_transition`
332
385
  Returns the most recent transition object.
333
386
 
387
+ #### `Machine#last_transition_to(:state)`
388
+ Returns the most recent transition object to a given state.
389
+
334
390
  #### `Machine#allowed_transitions`
335
391
  Returns an array of states you can `transition_to` from current state.
336
392
 
@@ -347,6 +403,66 @@ Transition to the passed state, returning `true` on success. Swallows all
347
403
  Statesman exceptions and returns false on failure. (NB. if your guard or
348
404
  callback code throws an exception, it will not be caught.)
349
405
 
406
+
407
+ ## Errors
408
+
409
+ ### Initialization errors
410
+ These errors are raised when the Machine and/or Model is initialized. A simple spec like
411
+ ```ruby
412
+ expect { OrderStateMachine.new(Order.new, transition_class: OrderTransition) }.to_not raise_error
413
+ ```
414
+ will expose these errors as part of your test suite
415
+
416
+ #### InvalidStateError
417
+ Raised if:
418
+ * Attempting to define a transition without a `to` state.
419
+ * Attempting to define a transition with a non-existent state.
420
+ * Attempting to define multiple states as `initial`.
421
+
422
+ #### InvalidTransitionError
423
+ Raised if:
424
+ * Attempting to define a callback `from` a state that has no valid transitions (A terminal state).
425
+ * Attempting to define a callback `to` the `initial` state if that state has no transitions to it.
426
+ * Attempting to define a callback with `from` and `to` where any of the pairs have no transition between them.
427
+
428
+ #### InvalidCallbackError
429
+ Raised if:
430
+ * Attempting to define a callback without a block.
431
+
432
+ #### UnserializedMetadataError
433
+ Raised if:
434
+ * ActiveRecord is configured to not serialize the `metadata` attribute into
435
+ to Database column backing it. See the `Using PostgreSQL JSON column` section.
436
+
437
+ #### IncompatibleSerializationError
438
+ Raised if:
439
+ * There is a mismatch between the column type of the `metadata` in the
440
+ Database and the model. See the `Using PostgreSQL JSON column` section.
441
+
442
+ #### MissingTransitionAssociation
443
+ Raised if:
444
+ * The model that `Statesman::Adapters::ActiveRecordQueries` is included in
445
+ does not have a `has_many` association to the `transition_class`.
446
+
447
+ ### Runtime errors
448
+ These errors are raised by `transition_to!`. Using `transition_to` will
449
+ supress `GuardFailedError` and `TransitionFailedError` and return `false` instead.
450
+
451
+ #### GuardFailedError
452
+ Raised if:
453
+ * A guard callback between `from` and `to` state returned a falsey value.
454
+
455
+ #### TransitionFailedError
456
+ Raised if:
457
+ * A transition is attempted but `current_state -> new_state` is not a valid pair.
458
+
459
+ #### TransitionConflictError
460
+ Raised if:
461
+ * A database conflict affecting the `sort_key` or `most_recent` columns occurs
462
+ when attempting a transition.
463
+ Retried automatically if it occurs wrapped in `retry_conflicts`.
464
+
465
+
350
466
  ## Model scopes
351
467
 
352
468
  A mixin is provided for the ActiveRecord adapter which adds scopes to easily
@@ -404,10 +520,12 @@ Model.in_state(:state_1).or(
404
520
  #### Storing the state on the model object
405
521
 
406
522
  If you wish to store the model state on the model directly, you can keep it up
407
- to date using an `after_transition` hook:
523
+ to date using an `after_transition` hook.
524
+ Combine it with the `after_commit` option to ensure the model state will only be
525
+ saved once the transition has made it irreversibly to the database:
408
526
 
409
527
  ```ruby
410
- after_transition do |model, transition|
528
+ after_transition(after_commit: true) do |model, transition|
411
529
  model.state = transition.to_state
412
530
  model.save!
413
531
  end
@@ -493,6 +611,30 @@ describe "some callback" do
493
611
  end
494
612
  ```
495
613
 
614
+ ## Compatibility with type checkers
615
+
616
+ Including ActiveRecordQueries to your model can cause issues with type checkers
617
+ such as Sorbet, this is because this technically is using a dynamic include,
618
+ which is not supported by Sorbet.
619
+
620
+ To avoid these issues you can instead include the TypeSafeActiveRecordQueries
621
+ module and pass in configuration.
622
+
623
+ ```ruby
624
+ class Order < ActiveRecord::Base
625
+ has_many :order_transitions, autosave: false
626
+
627
+ include Statesman::Adapters::TypeSafeActiveRecordQueries
628
+
629
+ configure_state_machine transition_class: OrderTransition,
630
+ initial_state: :pending
631
+
632
+ def state_machine
633
+ @state_machine ||= OrderStateMachine.new(self, transition_class: OrderTransition)
634
+ end
635
+ end
636
+ ```
637
+
496
638
  # Third-party extensions
497
639
 
498
640
  [statesman-sequel](https://github.com/badosu/statesman-sequel) - An adapter to make Statesman work with [Sequel](https://github.com/jeremyevans/sequel)
@@ -7,7 +7,7 @@ module Statesman
7
7
  class ActiveRecordTransitionGenerator < Rails::Generators::Base
8
8
  include Statesman::GeneratorHelpers
9
9
 
10
- desc "Create an ActiveRecord-based transition model"\
10
+ desc "Create an ActiveRecord-based transition model" \
11
11
  "with the required attributes"
12
12
 
13
13
  argument :parent, type: :string, desc: "Your parent model name"
@@ -11,7 +11,7 @@ module Statesman
11
11
  end
12
12
 
13
13
  def migration_class_name
14
- klass.gsub(/::/, "").pluralize
14
+ klass.gsub("::", "").pluralize
15
15
  end
16
16
 
17
17
  def next_migration_number
@@ -39,8 +39,16 @@ module Statesman
39
39
  end
40
40
 
41
41
  def mysql?
42
- ActiveRecord::Base.configurations[Rails.env].
43
- try(:[], "adapter").try(:match, /mysql/)
42
+ configuration.try(:[], "adapter").try(:match, /mysql/)
43
+ end
44
+
45
+ # [] is deprecated and will be removed in 6.2
46
+ def configuration
47
+ if ActiveRecord::Base.configurations.respond_to?(:configs_for)
48
+ ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).first
49
+ else
50
+ ActiveRecord::Base.configurations[Rails.env]
51
+ end
44
52
  end
45
53
 
46
54
  def database_supports_partial_indexes?
@@ -42,11 +42,17 @@ module Statesman
42
42
  def create(from, to, metadata = {})
43
43
  create_transition(from.to_s, to.to_s, metadata)
44
44
  rescue ::ActiveRecord::RecordNotUnique => e
45
- raise TransitionConflictError, e.message if transition_conflict_error? e
45
+ if transition_conflict_error? e
46
+ # The history has the invalid transition on the end of it, which means
47
+ # `current_state` would then be incorrect. We force a reload of the history to
48
+ # avoid this.
49
+ transitions_for_parent.reload
50
+ raise TransitionConflictError, e.message
51
+ end
46
52
 
47
53
  raise
48
54
  ensure
49
- @last_transition = nil
55
+ reset
50
56
  end
51
57
 
52
58
  def history(force_reload: false)
@@ -62,14 +68,21 @@ module Statesman
62
68
  def last(force_reload: false)
63
69
  if force_reload
64
70
  @last_transition = history(force_reload: true).last
71
+ elsif instance_variable_defined?(:@last_transition)
72
+ @last_transition
65
73
  else
66
- @last_transition ||= history.last
74
+ @last_transition = history.last
75
+ end
76
+ end
77
+
78
+ def reset
79
+ if instance_variable_defined?(:@last_transition)
80
+ remove_instance_variable(:@last_transition)
67
81
  end
68
82
  end
69
83
 
70
84
  private
71
85
 
72
- # rubocop:disable Metrics/MethodLength
73
86
  def create_transition(from, to, metadata)
74
87
  transition = transitions_for_parent.build(
75
88
  default_transition_attributes(to, metadata),
@@ -106,7 +119,6 @@ module Statesman
106
119
 
107
120
  transition
108
121
  end
109
- # rubocop:enable Metrics/MethodLength
110
122
 
111
123
  def default_transition_attributes(to, metadata)
112
124
  {
@@ -147,13 +159,24 @@ module Statesman
147
159
 
148
160
  def most_recent_transitions(most_recent_id = nil)
149
161
  if most_recent_id
150
- transitions_of_parent.and(
162
+ concrete_transitions_of_parent.and(
151
163
  transition_table[:id].eq(most_recent_id).or(
152
164
  transition_table[:most_recent].eq(true),
153
165
  ),
154
166
  )
155
167
  else
156
- transitions_of_parent.and(transition_table[:most_recent].eq(true))
168
+ concrete_transitions_of_parent.and(transition_table[:most_recent].eq(true))
169
+ end
170
+ end
171
+
172
+ def concrete_transitions_of_parent
173
+ if transition_sti?
174
+ transitions_of_parent.and(
175
+ transition_table[transition_class.inheritance_column].
176
+ eq(transition_class.name),
177
+ )
178
+ else
179
+ transitions_of_parent
157
180
  end
158
181
  end
159
182
 
@@ -219,7 +242,7 @@ module Statesman
219
242
  end
220
243
 
221
244
  def next_sort_key
222
- (last && last.sort_key + 10) || 10
245
+ (last && (last.sort_key + 10)) || 10
223
246
  end
224
247
 
225
248
  def serialized?(transition_class)
@@ -252,13 +275,18 @@ module Statesman
252
275
  end
253
276
  end
254
277
 
255
- def parent_join_foreign_key
256
- association =
257
- parent_model.class.
258
- reflect_on_all_associations(:has_many).
259
- find { |r| r.name.to_s == @association_name.to_s }
278
+ def transition_sti?
279
+ transition_class.column_names.include?(transition_class.inheritance_column)
280
+ end
281
+
282
+ def parent_association
283
+ parent_model.class.
284
+ reflect_on_all_associations(:has_many).
285
+ find { |r| r.name.to_s == @association_name.to_s }
286
+ end
260
287
 
261
- association_join_primary_key(association)
288
+ def parent_join_foreign_key
289
+ association_join_primary_key(parent_association)
262
290
  end
263
291
 
264
292
  def association_join_primary_key(association)
@@ -292,34 +320,42 @@ module Statesman
292
320
  return nil if column.nil?
293
321
 
294
322
  [
295
- column, ::ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
323
+ column, default_timezone == :utc ? Time.now.utc : Time.now
296
324
  ]
297
325
  end
298
326
 
327
+ def default_timezone
328
+ # Rails 7 deprecates ActiveRecord::Base.default_timezone
329
+ # in favour of ActiveRecord.default_timezone
330
+ if ::ActiveRecord.respond_to?(:default_timezone)
331
+ return ::ActiveRecord.default_timezone
332
+ end
333
+
334
+ ::ActiveRecord::Base.default_timezone
335
+ end
336
+
299
337
  def mysql_gaplock_protection?
300
338
  Statesman.mysql_gaplock_protection?
301
339
  end
302
340
 
303
341
  def db_true
304
- value = ::ActiveRecord::Base.connection.type_cast(
305
- true,
306
- transition_class.columns_hash["most_recent"],
307
- )
308
- ::ActiveRecord::Base.connection.quote(value)
342
+ ::ActiveRecord::Base.connection.quote(type_cast(true))
309
343
  end
310
344
 
311
345
  def db_false
312
- value = ::ActiveRecord::Base.connection.type_cast(
313
- false,
314
- transition_class.columns_hash["most_recent"],
315
- )
316
- ::ActiveRecord::Base.connection.quote(value)
346
+ ::ActiveRecord::Base.connection.quote(type_cast(false))
317
347
  end
318
348
 
319
349
  def db_null
320
350
  Arel::Nodes::SqlLiteral.new("NULL")
321
351
  end
322
352
 
353
+ # Type casting against a column is deprecated and will be removed in Rails 6.2.
354
+ # See https://github.com/rails/arel/commit/6160bfbda1d1781c3b08a33ec4955f170e95be11
355
+ def type_cast(value)
356
+ ::ActiveRecord::Base.connection.type_cast(value)
357
+ end
358
+
323
359
  # Check whether the `most_recent` column allows null values. If it doesn't, set old
324
360
  # records to `false`, otherwise, set them to `NULL`.
325
361
  #
@@ -49,6 +49,14 @@ module Statesman
49
49
 
50
50
  define_in_state(base, query_builder)
51
51
  define_not_in_state(base, query_builder)
52
+
53
+ define_method(:reload) do |*a|
54
+ instance = super(*a)
55
+ if instance.respond_to?(:state_machine, true)
56
+ instance.send(:state_machine).reset
57
+ end
58
+ instance
59
+ end
52
60
  end
53
61
 
54
62
  private
@@ -95,18 +103,18 @@ module Statesman
95
103
  def states_where(states)
96
104
  if initial_state.to_s.in?(states.map(&:to_s))
97
105
  "#{most_recent_transition_alias}.to_state IN (?) OR " \
98
- "#{most_recent_transition_alias}.to_state IS NULL"
106
+ "#{most_recent_transition_alias}.to_state IS NULL"
99
107
  else
100
108
  "#{most_recent_transition_alias}.to_state IN (?) AND " \
101
- "#{most_recent_transition_alias}.to_state IS NOT NULL"
109
+ "#{most_recent_transition_alias}.to_state IS NOT NULL"
102
110
  end
103
111
  end
104
112
 
105
113
  def most_recent_transition_join
106
114
  "LEFT OUTER JOIN #{model_table} AS #{most_recent_transition_alias} " \
107
- "ON #{model.table_name}.id = " \
108
- "#{most_recent_transition_alias}.#{model_foreign_key} " \
109
- "AND #{most_recent_transition_alias}.most_recent = #{db_true}"
115
+ "ON #{model.table_name}.#{model_primary_key} = " \
116
+ "#{most_recent_transition_alias}.#{model_foreign_key} " \
117
+ "AND #{most_recent_transition_alias}.most_recent = #{db_true}"
110
118
  end
111
119
 
112
120
  private
@@ -127,6 +135,10 @@ module Statesman
127
135
  "and #{transition_class}."
128
136
  end
129
137
 
138
+ def model_primary_key
139
+ transition_reflection.active_record_primary_key
140
+ end
141
+
130
142
  def model_foreign_key
131
143
  transition_reflection.foreign_key
132
144
  end
@@ -37,10 +37,14 @@ module Statesman
37
37
  @history
38
38
  end
39
39
 
40
+ def reset
41
+ @history = []
42
+ end
43
+
40
44
  private
41
45
 
42
46
  def next_sort_key
43
- (last && last.sort_key + 10) || 10
47
+ (last && (last.sort_key + 10)) || 10
44
48
  end
45
49
  end
46
50
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Statesman
4
+ module Adapters
5
+ module TypeSafeActiveRecordQueries
6
+ def configure_state_machine(args = {})
7
+ transition_class = args.fetch(:transition_class)
8
+ initial_state = args.fetch(:initial_state)
9
+
10
+ include(
11
+ ActiveRecordQueries::ClassMethods.new(
12
+ transition_class: transition_class,
13
+ initial_state: initial_state,
14
+ most_recent_transition_alias: try(:most_recent_transition_alias),
15
+ transition_name: try(:transition_name),
16
+ ),
17
+ )
18
+ end
19
+ end
20
+ end
21
+ end
@@ -2,9 +2,13 @@
2
2
 
3
3
  module Statesman
4
4
  class InvalidStateError < StandardError; end
5
+
5
6
  class InvalidTransitionError < StandardError; end
7
+
6
8
  class InvalidCallbackError < StandardError; end
9
+
7
10
  class TransitionConflictError < StandardError; end
11
+
8
12
  class MissingTransitionAssociation < StandardError; end
9
13
 
10
14
  class TransitionFailedError < StandardError
@@ -24,13 +28,15 @@ module Statesman
24
28
  end
25
29
 
26
30
  class GuardFailedError < StandardError
27
- def initialize(from, to)
31
+ def initialize(from, to, callback)
28
32
  @from = from
29
33
  @to = to
34
+ @callback = callback
30
35
  super(_message)
36
+ set_backtrace(callback.source_location.join(":")) if callback&.source_location
31
37
  end
32
38
 
33
- attr_reader :from, :to
39
+ attr_reader :from, :to, :callback
34
40
 
35
41
  private
36
42
 
@@ -48,8 +54,8 @@ module Statesman
48
54
 
49
55
  def _message(transition_class_name)
50
56
  "#{transition_class_name}#metadata is not serialized. If you " \
51
- "are using a non-json column type, you should `include " \
52
- "Statesman::Adapters::ActiveRecordTransition`"
57
+ "are using a non-json column type, you should `include " \
58
+ "Statesman::Adapters::ActiveRecordTransition`"
53
59
  end
54
60
  end
55
61
 
@@ -62,9 +68,9 @@ module Statesman
62
68
 
63
69
  def _message(transition_class_name)
64
70
  "#{transition_class_name}#metadata column type cannot be json " \
65
- "and serialized simultaneously. If you are using a json " \
66
- "column type, it is not necessary to `include " \
67
- "Statesman::Adapters::ActiveRecordTransition`"
71
+ "and serialized simultaneously. If you are using a json " \
72
+ "column type, it is not necessary to `include " \
73
+ "Statesman::Adapters::ActiveRecordTransition`"
68
74
  end
69
75
  end
70
76
  end
@@ -6,7 +6,7 @@ require_relative "exceptions"
6
6
  module Statesman
7
7
  class Guard < Callback
8
8
  def call(*args)
9
- raise GuardFailedError.new(from, to) unless super(*args)
9
+ raise GuardFailedError.new(from, to, callback) unless super(*args)
10
10
  end
11
11
  end
12
12
  end
@@ -42,6 +42,17 @@ module Statesman
42
42
  states << name
43
43
  end
44
44
 
45
+ def remove_state(state_name)
46
+ state_name = state_name.to_s
47
+
48
+ remove_transitions(from: state_name)
49
+ remove_transitions(to: state_name)
50
+ remove_callbacks(from: state_name)
51
+ remove_callbacks(to: state_name)
52
+
53
+ @states.delete(state_name.to_s)
54
+ end
55
+
45
56
  def successors
46
57
  @successors ||= {}
47
58
  end
@@ -70,6 +81,20 @@ module Statesman
70
81
  successors[from] += to
71
82
  end
72
83
 
84
+ def remove_transitions(from: nil, to: nil)
85
+ raise ArgumentError, "Both from and to can't be nil!" if from.nil? && to.nil?
86
+ return if successors.nil?
87
+
88
+ if from.present?
89
+ @successors[from.to_s].delete(to.to_s) if to.present?
90
+ @successors.delete(from.to_s) if to.nil? || successors[from.to_s].empty?
91
+ elsif to.present?
92
+ @successors.
93
+ transform_values! { |to_states| to_states - [to.to_s] }.
94
+ filter! { |_from_state, to_states| to_states.any? }
95
+ end
96
+ end
97
+
73
98
  def before_transition(options = {}, &block)
74
99
  add_callback(callback_type: :before, callback_class: Callback,
75
100
  from: options[:from], to: options[:to], &block)
@@ -151,6 +176,33 @@ module Statesman
151
176
  callback_class.new(from: from, to: to, callback: block)
152
177
  end
153
178
 
179
+ def remove_callbacks(from: nil, to: nil)
180
+ raise ArgumentError, "Both from and to can't be nil!" if from.nil? && to.nil?
181
+ return if callbacks.nil?
182
+
183
+ @callbacks.transform_values! do |callbacks|
184
+ filter_callbacks(callbacks, from: from, to: to)
185
+ end
186
+ end
187
+
188
+ def filter_callbacks(callbacks, from: nil, to: nil)
189
+ callbacks.filter_map do |callback|
190
+ next if callback.from == from && to.nil?
191
+
192
+ if callback.to.include?(to) && (from.nil? || callback.from == from)
193
+ next if callback.to == [to]
194
+
195
+ callback = Statesman::Callback.new({
196
+ from: callback.from,
197
+ to: callback.to - [to],
198
+ callback: callback.callback,
199
+ })
200
+ end
201
+
202
+ callback
203
+ end
204
+ end
205
+
154
206
  def validate_callback_type_and_class(callback_type, callback_class)
155
207
  raise ArgumentError, "missing keyword: callback_type" if callback_type.nil?
156
208
  raise ArgumentError, "missing keyword: callback_class" if callback_class.nil?
@@ -209,6 +261,10 @@ module Statesman
209
261
  @storage_adapter.last(force_reload: force_reload)
210
262
  end
211
263
 
264
+ def last_transition_to(state)
265
+ history.reverse.find { |transition| transition.to_state.to_sym == state.to_sym }
266
+ end
267
+
212
268
  def can_transition_to?(new_state, metadata = {})
213
269
  validate_transition(from: current_state,
214
270
  to: new_state,
@@ -257,6 +313,10 @@ module Statesman
257
313
  false
258
314
  end
259
315
 
316
+ def reset
317
+ @storage_adapter.reset
318
+ end
319
+
260
320
  private
261
321
 
262
322
  def adapter_class(transition_class)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Statesman
4
- VERSION = "7.4.0"
4
+ VERSION = "10.2.3"
5
5
  end
data/lib/statesman.rb CHANGED
@@ -14,6 +14,8 @@ module Statesman
14
14
  "statesman/adapters/active_record_transition"
15
15
  autoload :ActiveRecordQueries,
16
16
  "statesman/adapters/active_record_queries"
17
+ autoload :TypeSafeActiveRecordQueries,
18
+ "statesman/adapters/type_safe_active_record_queries"
17
19
  end
18
20
  require "statesman/railtie" if defined?(::Rails::Railtie)
19
21