state_machines-activerecord 0.8.0 → 0.100.0

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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +20 -7
  4. data/lib/state_machines/integrations/active_record/locale.rb +12 -9
  5. data/lib/state_machines/integrations/active_record/version.rb +3 -1
  6. data/lib/state_machines/integrations/active_record.rb +88 -175
  7. data/lib/state_machines-activerecord.rb +2 -0
  8. metadata +36 -154
  9. data/.gitignore +0 -22
  10. data/.travis.yml +0 -19
  11. data/Appraisals +0 -34
  12. data/Gemfile +0 -6
  13. data/Rakefile +0 -9
  14. data/gemfiles/active_record_5.1.gemfile +0 -14
  15. data/gemfiles/active_record_5.2.gemfile +0 -14
  16. data/gemfiles/active_record_6.0.gemfile +0 -14
  17. data/gemfiles/active_record_6.1.gemfile +0 -14
  18. data/gemfiles/active_record_edge.gemfile +0 -14
  19. data/log/.gitkeep +0 -0
  20. data/state_machines-activerecord.gemspec +0 -28
  21. data/test/files/en.yml +0 -5
  22. data/test/files/models/post.rb +0 -11
  23. data/test/integration_test.rb +0 -25
  24. data/test/machine_by_default_test.rb +0 -16
  25. data/test/machine_errors_test.rb +0 -19
  26. data/test/machine_multiple_test.rb +0 -17
  27. data/test/machine_nested_action_test.rb +0 -38
  28. data/test/machine_unmigrated_test.rb +0 -14
  29. data/test/machine_with_aliased_attribute_test.rb +0 -23
  30. data/test/machine_with_callbacks_test.rb +0 -172
  31. data/test/machine_with_column_state_attribute_test.rb +0 -44
  32. data/test/machine_with_complex_pluralization_scopes_test.rb +0 -16
  33. data/test/machine_with_conflicting_predicate_test.rb +0 -18
  34. data/test/machine_with_conflicting_state_name_test.rb +0 -29
  35. data/test/machine_with_custom_attribute_test.rb +0 -21
  36. data/test/machine_with_default_scope_test.rb +0 -18
  37. data/test/machine_with_different_column_default_test.rb +0 -27
  38. data/test/machine_with_different_integer_column_default_test.rb +0 -29
  39. data/test/machine_with_dirty_attribute_and_custom_attributes_during_loopback_test.rb +0 -24
  40. data/test/machine_with_dirty_attribute_and_state_events_test.rb +0 -20
  41. data/test/machine_with_dirty_attributes_and_custom_attribute_test.rb +0 -32
  42. data/test/machine_with_dirty_attributes_during_loopback_test.rb +0 -22
  43. data/test/machine_with_dirty_attributes_test.rb +0 -35
  44. data/test/machine_with_dynamic_initial_state_test.rb +0 -99
  45. data/test/machine_with_event_attributes_on_autosave_test.rb +0 -48
  46. data/test/machine_with_event_attributes_on_custom_action_test.rb +0 -41
  47. data/test/machine_with_event_attributes_on_save_bang_test.rb +0 -82
  48. data/test/machine_with_event_attributes_on_save_test.rb +0 -244
  49. data/test/machine_with_event_attributes_on_validation_test.rb +0 -143
  50. data/test/machine_with_events_test.rb +0 -13
  51. data/test/machine_with_failed_action_test.rb +0 -40
  52. data/test/machine_with_failed_after_callbacks_test.rb +0 -35
  53. data/test/machine_with_failed_before_callbacks_test.rb +0 -36
  54. data/test/machine_with_initialized_state_test.rb +0 -41
  55. data/test/machine_with_internationalization_test.rb +0 -180
  56. data/test/machine_with_loopback_test.rb +0 -22
  57. data/test/machine_with_non_column_state_attribute_defined_test.rb +0 -29
  58. data/test/machine_with_same_column_default_test.rb +0 -26
  59. data/test/machine_with_same_integer_column_default_test.rb +0 -30
  60. data/test/machine_with_scopes_and_joins_test.rb +0 -37
  61. data/test/machine_with_scopes_and_owner_subclass_test.rb +0 -27
  62. data/test/machine_with_scopes_test.rb +0 -70
  63. data/test/machine_with_state_driven_validations_test.rb +0 -30
  64. data/test/machine_with_states_test.rb +0 -13
  65. data/test/machine_with_static_initial_state_test.rb +0 -166
  66. data/test/machine_with_transactions_test.rb +0 -26
  67. data/test/machine_with_validations_and_custom_attribute_test.rb +0 -21
  68. data/test/machine_with_validations_test.rb +0 -47
  69. data/test/machine_without_database_test.rb +0 -20
  70. data/test/machine_without_transactions_test.rb +0 -26
  71. data/test/model_test.rb +0 -12
  72. data/test/test_helper.rb +0 -52
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d4d6c3db18b13bbece06c33f047109e49d17eb1b97e8d26d8e001ed02a554b7a
4
- data.tar.gz: ebd085709c837eb3c76994e23780b884136a338f92b63eb77c8c64620bd1980e
3
+ metadata.gz: 8da92c0d0872bf79ae81017dd5d16459e5222d10ee304390ddff3af14f1e1e05
4
+ data.tar.gz: c7ebd9fbdff4ee5bd2b0cca81b5daad186bb92157eddcb912829685571aa84eb
5
5
  SHA512:
6
- metadata.gz: febb1df5b304114be86875716fb620b7c1b7fe1d0d44bea3c52c92451345e2cf2fb57e11cbd87d9b277079a52ebb9f82734d4174ea6722e8c6d933d274f88f6d
7
- data.tar.gz: 3ee22472203164647a9d65ef4a5df8edfeda3874144b57281b9dce79ae210006f171bc02fdcdd24d44afd08ed4b9dff409b61d443a61052a1baeee27d8f3ed16
6
+ metadata.gz: 66d176f9efbc9d44ac2d5a5786f078e593d3590a99d855804ff4775649b18abae64264781f4ddc36a60ac253366aea0a9c02d3d1712d680d78fe443fffd437e7
7
+ data.tar.gz: f5e7a924679499d596439009b9bad2633af5389069959016595d31c44243f7551893cd2e53769e5e1b3a99b979f8d6ad1fa24d86caaa9134f0aa63528739b108
data/LICENSE.txt CHANGED
@@ -1,5 +1,5 @@
1
1
  Copyright (c) 2006-2012 Aaron Pfeifer
2
- Copyright (c) 2014-2021 Abdelkader Boudih
2
+ Copyright (c) 2014-2025 Abdelkader Boudih
3
3
 
4
4
  MIT License
5
5
 
data/README.md CHANGED
@@ -1,11 +1,15 @@
1
- [![Build Status](https://travis-ci.com/state-machines/state_machines-activerecord.svg?branch=master)](https://travis-ci.com/state-machines/state_machines-activerecord)
2
- [![Code Climate](https://codeclimate.com/github/state-machines/state_machines-activerecord.svg)](https://codeclimate.com/github/state-machines/state_machines-activerecord)
1
+ [![Build Status](https://github.com/state-machines/state_machines-activerecord/actions/workflows/ruby.yml/badge.svg)](https://github.com/state-machines/state_machines-activerecord/actions/workflows/ruby.yml)
3
2
 
4
3
  # StateMachines Active Record Integration
5
4
 
6
- The Active Record 5.1+ integration adds support for database transactions, automatically
5
+ The Active Record 7.2+ integration adds support for database transactions, automatically
7
6
  saving the record, named scopes, validation errors.
8
7
 
8
+ ## Requirements
9
+
10
+ - Ruby 3.2+
11
+ - Rails 7.2+
12
+
9
13
  ## Installation
10
14
 
11
15
  Add this line to your application's Gemfile:
@@ -27,7 +31,7 @@ For the complete usage guide, see http://www.rubydoc.info/github/state-machines/
27
31
  ### Example
28
32
 
29
33
  ```ruby
30
- class Vehicle < ActiveRecord::Base
34
+ class Vehicle < ApplicationRecord
31
35
  state_machine :initial => :parked do
32
36
  before_transition :parked => any - :parked, :do => :put_on_seatbelt
33
37
  after_transition any => :parked do |vehicle, transition|
@@ -40,7 +44,7 @@ class Vehicle < ActiveRecord::Base
40
44
  end
41
45
 
42
46
  state :first_gear, :second_gear do
43
- validates_presence_of :seatbelt_on
47
+ validates :seatbelt_on, presence: true
44
48
  end
45
49
  end
46
50
 
@@ -64,6 +68,15 @@ Vehicle.with_state(:parked) # also plural #with_states
64
68
  Vehicle.without_states(:first_gear, :second_gear) # also singular #without_state
65
69
  ```
66
70
 
71
+ #### Transparent Scopes
72
+ State scopes will return all records when `nil` is passed, making them perfect for search filters:
73
+
74
+ ```ruby
75
+ Vehicle.with_state(nil) # Returns all vehicles
76
+ Vehicle.with_state(params[:state]) # Returns all vehicles if params[:state] is nil
77
+ Vehicle.where(color: 'red').with_state(nil) # Returns all red vehicles (chainable)
78
+ ```
79
+
67
80
  ### State driven validations
68
81
 
69
82
  As mentioned in `StateMachines::Machine#state`, you can define behaviors,
@@ -73,7 +86,7 @@ framework, custom validators will not work as expected when defined to run
73
86
  in multiple states. For example:
74
87
 
75
88
  ```ruby
76
- class Vehicle < ActiveRecord::Base
89
+ class Vehicle < ApplicationRecord
77
90
  state_machine do
78
91
  state :first_gear, :second_gear do
79
92
  validate :speed_is_legal
@@ -87,7 +100,7 @@ for the <tt>:second_gear</tt> state. To avoid this, you can define your
87
100
  custom validation like so:
88
101
 
89
102
  ```ruby
90
- class Vehicle < ActiveRecord::Base
103
+ class Vehicle < ApplicationRecord
91
104
  state_machine do
92
105
  state :first_gear, :second_gear do
93
106
  validate {|vehicle| vehicle.speed_is_legal}
@@ -1,12 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Use lazy evaluation to avoid circular dependencies with frozen default_messages
4
+ # This ensures messages can be updated after gem loading while maintaining thread safety
1
5
  { en: {
2
- activerecord: {
3
- errors: {
4
- messages: {
5
- invalid: StateMachines::Machine.default_messages[:invalid],
6
- invalid_event: StateMachines::Machine.default_messages[:invalid_event] % ['%{state}'],
7
- invalid_transition: StateMachines::Machine.default_messages[:invalid_transition] % ['%{event}']
8
- }
9
- }
6
+ activerecord: {
7
+ errors: {
8
+ messages: {
9
+ invalid: ->(*) { StateMachines::Machine.default_messages[:invalid] },
10
+ invalid_event: ->(*) { format(StateMachines::Machine.default_messages[:invalid_event], '%<state>s') },
11
+ invalid_transition: ->(*) { format(StateMachines::Machine.default_messages[:invalid_transition], '%<event>s') }
12
+ }
10
13
  }
14
+ }
11
15
  } }
12
-
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module StateMachines
2
4
  module Integrations
3
5
  module ActiveRecord
4
- VERSION = '0.8.0'
6
+ VERSION = '0.100.0'
5
7
  end
6
8
  end
7
9
  end
@@ -3,7 +3,7 @@ require 'active_record'
3
3
  require 'state_machines/integrations/active_record/version'
4
4
 
5
5
  module StateMachines
6
- module Integrations #:nodoc:
6
+ module Integrations # :nodoc:
7
7
  # Adds support for integrating state machines with ActiveRecord models.
8
8
  #
9
9
  # == Examples
@@ -11,7 +11,7 @@ module StateMachines
11
11
  # Below is an example of a simple state machine defined within an
12
12
  # ActiveRecord model:
13
13
  #
14
- # class Vehicle < ActiveRecord::Base
14
+ # class Vehicle < ApplicationRecord
15
15
  # state_machine :initial => :parked do
16
16
  # event :ignite do
17
17
  # transition :parked => :idling
@@ -78,25 +78,22 @@ module StateMachines
78
78
  # === Security implications
79
79
  #
80
80
  # Beware that public event attributes mean that events can be fired
81
- # whenever mass-assignment is being used. If you want to prevent malicious
82
- # users from tampering with events through URLs / forms, the attribute
83
- # should be protected like so:
84
- #
85
- # class Vehicle < ActiveRecord::Base
86
- # attr_protected :state_event
87
- # # attr_accessible ... # Alternative technique
88
- #
89
- # state_machine do
90
- # ...
81
+ # whenever mass-assignment is being used. If you want to prevent malicious
82
+ # users from tampering with events through URLs / forms, you should use
83
+ # Rails' strong parameters to control which attributes are permitted:
84
+ #
85
+ # class VehiclesController < ApplicationController
86
+ # def vehicle_params
87
+ # params.require(:vehicle).permit(:color, :make, :model)
88
+ # # Exclude state_event to prevent tampering
91
89
  # end
92
90
  # end
93
91
  #
94
92
  # If you want to only have *some* events be able to fire via mass-assignment,
95
93
  # you can build two state machines (one public and one protected) like so:
96
94
  #
97
- # class Vehicle < ActiveRecord::Base
98
- # attr_protected :state_event # Prevent access to events in the first machine
99
- #
95
+ # class Vehicle < ApplicationRecord
96
+ # # Define private machine
100
97
  # state_machine do
101
98
  # # Define private events here
102
99
  # end
@@ -105,6 +102,8 @@ module StateMachines
105
102
  # state_machine :public_state, :attribute => :state do
106
103
  # # Define public events here
107
104
  # end
105
+ #
106
+ # # Control access via strong parameters in your controller
108
107
  # end
109
108
  #
110
109
  # == Transactions
@@ -115,7 +114,7 @@ module StateMachines
115
114
  #
116
115
  # For example,
117
116
  #
118
- # class Message < ActiveRecord::Base
117
+ # class Message < ApplicationRecord
119
118
  # end
120
119
  #
121
120
  # Vehicle.state_machine do
@@ -136,7 +135,7 @@ module StateMachines
136
135
  #
137
136
  # To turn off transactions:
138
137
  #
139
- # class Vehicle < ActiveRecord::Base
138
+ # class Vehicle < ApplicationRecord
140
139
  # state_machine :initial => :parked, :use_transactions => false do
141
140
  # ...
142
141
  # end
@@ -150,7 +149,7 @@ module StateMachines
150
149
  # framework, custom validators will not work as expected when defined to run
151
150
  # in multiple states. For example:
152
151
  #
153
- # class Vehicle < ActiveRecord::Base
152
+ # class Vehicle < ApplicationRecord
154
153
  # state_machine do
155
154
  # ...
156
155
  # state :first_gear, :second_gear do
@@ -163,7 +162,7 @@ module StateMachines
163
162
  # for the <tt>:second_gear</tt> state. To avoid this, you can define your
164
163
  # custom validation like so:
165
164
  #
166
- # class Vehicle < ActiveRecord::Base
165
+ # class Vehicle < ApplicationRecord
167
166
  # state_machine do
168
167
  # ...
169
168
  # state :first_gear, :second_gear do
@@ -199,34 +198,43 @@ module StateMachines
199
198
  #
200
199
  # == Scopes
201
200
  #
202
- # To assist in filtering models with specific states, a series of named
203
- # scopes are defined on the model for finding records with or without a
201
+ # To assist in filtering models with specific states, a series of scopes
202
+ # are defined on the model for finding records with or without a
204
203
  # particular set of states.
205
204
  #
206
- # These named scopes are essentially the functional equivalent of the
205
+ # These scopes are essentially the functional equivalent of the
207
206
  # following definitions:
208
207
  #
209
- # class Vehicle < ActiveRecord::Base
210
- # named_scope :with_states, lambda {|*states| {:conditions => {:state => states}}}
208
+ # class Vehicle < ApplicationRecord
211
209
  # # with_states also aliased to with_state
210
+ # scope :with_states, ->(states) { states.present? ? where(state: states) : all }
212
211
  #
213
- # named_scope :without_states, lambda {|*states| {:conditions => ['state NOT IN (?)', states]}}
214
212
  # # without_states also aliased to without_state
213
+ # scope :without_states, ->(states) { states.present? ? where.not(state: states) : all }
215
214
  # end
216
215
  #
217
216
  # *Note*, however, that the states are converted to their stored values
218
217
  # before being passed into the query.
219
218
  #
220
- # Because of the way named scopes work in ActiveRecord, they can be
219
+ # Because of the way scopes work in ActiveRecord, they can be
221
220
  # chained like so:
222
221
  #
223
- # Vehicle.with_state(:parked).all(:order => 'id DESC')
222
+ # Vehicle.with_state(:parked).order(id: :desc)
224
223
  #
225
224
  # Note that states can also be referenced by the string version of their
226
225
  # name:
227
226
  #
228
227
  # Vehicle.with_state('parked')
229
228
  #
229
+ # === Transparent Scopes
230
+ #
231
+ # When `nil` is passed to any of the state scopes, they return `all` records
232
+ # without applying any filters. This allows for more flexible scope chaining
233
+ # in search interfaces:
234
+ #
235
+ # Vehicle.with_state(params[:state]) # Returns all vehicles if params[:state] is nil
236
+ # Vehicle.where(color: 'red').with_state(nil) # Returns all red vehicles
237
+ #
230
238
  # == Callbacks
231
239
  #
232
240
  # All before/after transition callbacks defined for ActiveRecord models
@@ -235,7 +243,7 @@ module StateMachines
235
243
  #
236
244
  # For example,
237
245
  #
238
- # class Vehicle < ActiveRecord::Base
246
+ # class Vehicle < ApplicationRecord
239
247
  # state_machine :initial => :parked do
240
248
  # before_transition any => :idling do |vehicle|
241
249
  # vehicle.put_on_seatbelt
@@ -266,11 +274,11 @@ module StateMachines
266
274
  # ActiveRecord, a save failure will cause any records that get created in
267
275
  # your callback to roll back. You can work around this issue like so:
268
276
  #
269
- # class TransitionLog < ActiveRecord::Base
270
- # establish_connection Rails.env.to_sym
277
+ # class TransitionLog < ApplicationRecord
278
+ # connects_to database: { writing: :primary, reading: :primary }
271
279
  # end
272
280
  #
273
- # class Vehicle < ActiveRecord::Base
281
+ # class Vehicle < ApplicationRecord
274
282
  # state_machine do
275
283
  # after_failure do |vehicle, transition|
276
284
  # TransitionLog.create(:vehicle => vehicle, :transition => transition)
@@ -280,7 +288,7 @@ module StateMachines
280
288
  # end
281
289
  # end
282
290
  #
283
- # The +TransitionLog+ model establishes a second connection to the database
291
+ # The +TransitionLog+ model establishes a separate connection to the database
284
292
  # that allows new records to be saved without being affected by rollbacks
285
293
  # in the +Vehicle+ model's transaction.
286
294
  #
@@ -305,65 +313,9 @@ module StateMachines
305
313
  # * (-) end transaction (if enabled)
306
314
  # * (9) after_commit
307
315
  #
308
- # == Observers
309
- #
310
- # In addition to support for ActiveRecord-like hooks, there is additional
311
- # support for ActiveRecord observers. Because of the way ActiveRecord
312
- # observers are designed, there is less flexibility around the specific
313
- # transitions that can be hooked in. However, a large number of hooks
314
- # *are* supported. For example, if a transition for a record's +state+
315
- # attribute changes the state from +parked+ to +idling+ via the +ignite+
316
- # event, the following observer methods are supported:
317
- # * before/after/after_failure_to-_ignite_from_parked_to_idling
318
- # * before/after/after_failure_to-_ignite_from_parked
319
- # * before/after/after_failure_to-_ignite_to_idling
320
- # * before/after/after_failure_to-_ignite
321
- # * before/after/after_failure_to-_transition_state_from_parked_to_idling
322
- # * before/after/after_failure_to-_transition_state_from_parked
323
- # * before/after/after_failure_to-_transition_state_to_idling
324
- # * before/after/after_failure_to-_transition_state
325
- # * before/after/after_failure_to-_transition
326
- #
327
- # The following class shows an example of some of these hooks:
328
- #
329
- # class VehicleObserver < ActiveRecord::Observer
330
- # def before_save(vehicle)
331
- # # log message
332
- # end
333
- #
334
- # # Callback for :ignite event *before* the transition is performed
335
- # def before_ignite(vehicle, transition)
336
- # # log message
337
- # end
338
- #
339
- # # Callback for :ignite event *after* the transition has been performed
340
- # def after_ignite(vehicle, transition)
341
- # # put on seatbelt
342
- # end
343
- #
344
- # # Generic transition callback *before* the transition is performed
345
- # def after_transition(vehicle, transition)
346
- # Audit.log(vehicle, transition)
347
- # end
348
- # end
349
- #
350
- # More flexible transition callbacks can be defined directly within the
351
- # model as described in StateMachines::Machine#before_transition
352
- # and StateMachines::Machine#after_transition.
353
- #
354
- # To define a single observer for multiple state machines:
355
- #
356
- # class StateMachineObserver < ActiveRecord::Observer
357
- # observe Vehicle, Switch, Project
358
- #
359
- # def after_transition(record, transition)
360
- # Audit.log(record, transition)
361
- # end
362
- # end
363
- #
364
316
  # == Internationalization
365
317
  #
366
- # In Rails 2.2+, any error message that is generated from performing invalid
318
+ # Any error message that is generated from performing invalid
367
319
  # transitions can be localized. The following default translations are used:
368
320
  #
369
321
  # en:
@@ -376,9 +328,6 @@ module StateMachines
376
328
  # # %{value} = attribute value, %{event} = Human event name, %{state} = Human current state name
377
329
  # invalid_transition: "cannot transition via %{event}"
378
330
  #
379
- # Notice that the interpolation syntax is %{key} in Rails 3+. In Rails 2.x,
380
- # the appropriate syntax is {{key}}.
381
- #
382
331
  # You can override these for a specific model like so:
383
332
  #
384
333
  # en:
@@ -418,7 +367,7 @@ module StateMachines
418
367
  include ActiveModel
419
368
 
420
369
  # The default options to use for state machines using this integration
421
- @defaults = {:action => :save, use_transactions: true}
370
+ @defaults = { action: :save, use_transactions: true }
422
371
  class << self
423
372
  # Classes that inherit from ActiveRecord::Base will automatically use
424
373
  # the ActiveRecord integration.
@@ -435,78 +384,30 @@ module StateMachines
435
384
  end
436
385
 
437
386
  # Gets the db default for the machine's attribute
438
- if ::ActiveRecord.gem_version >= Gem::Version.new('4.2.0')
439
- def owner_class_attribute_default
440
- if owner_class.connected? && owner_class.table_exists?
441
- owner_class.column_defaults[attribute.to_s]
442
- end
443
- end
444
- else
445
- def owner_class_attribute_default
446
- if owner_class.connected? && owner_class.table_exists?
447
- if column = owner_class.columns_hash[attribute.to_s]
448
- column.default
449
- end
450
- end
451
- end
387
+ def owner_class_attribute_default
388
+ return unless owner_class.connected? && owner_class.table_exists?
389
+
390
+ owner_class.column_defaults[attribute.to_s]
452
391
  end
453
392
 
454
393
  def define_state_initializer
455
- if ::ActiveRecord.gem_version >= Gem::Version.new('5.0.0.alpha')
456
- define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
457
- def initialize(attributes = nil, *)
458
- super(attributes) do |*args|
459
- scoped_attributes = (attributes || {}).merge(self.class.scope_attributes)
460
-
461
- self.class.state_machines.initialize_states(self, {}, scoped_attributes)
462
- yield(*args) if block_given?
463
- end
464
- end
465
- end_eval
466
- elsif ::ActiveRecord.gem_version >= Gem::Version.new('4.2')
467
- define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
468
- def initialize(attributes = nil, options = {})
469
- scoped_attributes = (attributes || {}).merge(self.class.scope_attributes)
470
-
471
- super(attributes, options) do |*args|
472
- self.class.state_machines.initialize_states(self, {}, scoped_attributes)
473
- yield(*args) if block_given?
474
- end
475
- end
476
- end_eval
477
- else
478
- # Initializes static states
479
- #
480
- # This is the only available hook where the default set of attributes
481
- # can be overridden for a new object *prior* to the processing of the
482
- # attributes passed into #initialize
483
- define_helper :class, <<-end_eval, __FILE__, __LINE__ + 1
484
- def column_defaults(*) #:nodoc:
485
- result = super
486
- # No need to pass in an object, since the overrides will be forced
487
- self.state_machines.initialize_states(nil, :static => :force, :dynamic => false, :to => result)
488
- result
489
- end
490
- end_eval
491
-
492
- # Initializes dynamic states
493
- define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
494
- def initialize(attributes = nil, options = {})
495
- scoped_attributes = (attributes || {}).merge(self.class.scope_attributes)
394
+ define_helper :instance, <<-END_EVAL, __FILE__, __LINE__ + 1
395
+ def initialize(attributes = nil, *)
396
+ super(attributes) do |*args|
397
+ attributes = (attributes || {}).transform_keys { |key| self.class.attribute_aliases[key.to_s] || key }
398
+ scoped_attributes = attributes.merge(self.class.scope_attributes)
496
399
 
497
- super(attributes, options) do |*args|
498
- self.class.state_machines.initialize_states(self, {}, scoped_attributes)
499
- yield(*args) if block_given?
500
- end
400
+ self.class.state_machines.initialize_states(self, {}, scoped_attributes)
401
+ yield(*args) if block_given?
501
402
  end
502
- end_eval
503
- end
403
+ end
404
+ END_EVAL
504
405
  end
505
406
 
506
407
  # Uses around callbacks to run state events if using the :save hook
507
408
  def define_action_hook
508
409
  if action_hook == :save
509
- define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
410
+ define_helper :instance, <<-END_EVAL, __FILE__, __LINE__ + 1
510
411
  def save(*, **)
511
412
  self.class.state_machine(#{name.inspect}).send(:around_save, self) { super }
512
413
  end
@@ -519,34 +420,44 @@ module StateMachines
519
420
  def changed_for_autosave?
520
421
  super || self.class.state_machines.any? {|name, machine| machine.action == :save && machine.read(self, :event)}
521
422
  end
522
- end_eval
423
+ END_EVAL
523
424
  else
524
425
  super
525
426
  end
526
427
  end
527
428
 
528
429
  # Runs state events around the machine's :save action
529
- def around_save(object)
530
- object.class.state_machines.transitions(object, action).perform { yield }
430
+ def around_save(object, &)
431
+ # Pass fiber: false to avoid deadlocks with ActiveRecord's LoadInterlockAwareMonitor
432
+ object.class.state_machines.transitions(object, action, fiber: false).perform(&)
531
433
  end
532
434
 
533
435
  # Creates a scope for finding records *with* a particular state or
534
436
  # states for the attribute
535
437
  def create_with_scope(name)
536
- create_scope(name, ->(values) { ["#{attribute_column} IN (?)", values] })
438
+ attr_name = attribute
439
+ lambda do |klass, values|
440
+ if values.present?
441
+ klass.where(attr_name => values)
442
+ else
443
+ klass.all
444
+ end
445
+ end
537
446
  end
538
447
 
539
448
  # Creates a scope for finding records *without* a particular state or
540
449
  # states for the attribute
541
450
  def create_without_scope(name)
542
- create_scope(name, ->(values) { ["#{attribute_column} NOT IN (?)", values] })
451
+ attr_name = attribute
452
+ lambda do |klass, values|
453
+ if values.present?
454
+ klass.where.not(attr_name => values)
455
+ else
456
+ klass.all
457
+ end
458
+ end
543
459
  end
544
460
 
545
- # Generates the fully-qualifed column name for this machine's attribute
546
- def attribute_column
547
- connection = owner_class.connection
548
- "#{connection.quote_table_name(owner_class.table_name)}.#{connection.quote_column_name(attribute)}"
549
- end
550
461
 
551
462
  # Runs a new database transaction, rolling back any changes by raising
552
463
  # an ActiveRecord::Rollback exception if the yielded block fails
@@ -565,17 +476,19 @@ module StateMachines
565
476
 
566
477
  private
567
478
 
568
- # Defines a new named scope with the given name
569
- def create_scope(name, scope)
570
- lambda { |model, values| model.where(scope.call(values)) }
571
- end
572
479
 
573
- # ActiveModel's use of method_missing / respond_to for attribute methods
574
- # breaks both ancestor lookups and defined?(super). Need to special-case
575
- # the existence of query attribute methods.
576
- def owner_class_ancestor_has_method?(scope, method)
577
- scope == :instance && method == "#{attribute}?" ? owner_class : super
578
- end
480
+ # Generates the results for the given scope based on one or more states to filter by
481
+ def run_scope(scope, machine, klass, states)
482
+ values = states.flatten.compact.map { |state| machine.states.fetch(state).value }
483
+ scope.call(klass, values)
484
+ end
485
+
486
+ # ActiveModel's use of method_missing / respond_to for attribute methods
487
+ # breaks both ancestor lookups and defined?(super). Need to special-case
488
+ # the existence of query attribute methods.
489
+ def owner_class_ancestor_has_method?(scope, method)
490
+ scope == :instance && method == "#{attribute}?" ? owner_class : super
491
+ end
579
492
  end
580
493
  register(ActiveRecord)
581
494
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support'
2
4
  require 'state_machines/integrations/active_record'
3
5