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.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/README.md +20 -7
- data/lib/state_machines/integrations/active_record/locale.rb +12 -9
- data/lib/state_machines/integrations/active_record/version.rb +3 -1
- data/lib/state_machines/integrations/active_record.rb +88 -175
- data/lib/state_machines-activerecord.rb +2 -0
- metadata +36 -154
- data/.gitignore +0 -22
- data/.travis.yml +0 -19
- data/Appraisals +0 -34
- data/Gemfile +0 -6
- data/Rakefile +0 -9
- data/gemfiles/active_record_5.1.gemfile +0 -14
- data/gemfiles/active_record_5.2.gemfile +0 -14
- data/gemfiles/active_record_6.0.gemfile +0 -14
- data/gemfiles/active_record_6.1.gemfile +0 -14
- data/gemfiles/active_record_edge.gemfile +0 -14
- data/log/.gitkeep +0 -0
- data/state_machines-activerecord.gemspec +0 -28
- data/test/files/en.yml +0 -5
- data/test/files/models/post.rb +0 -11
- data/test/integration_test.rb +0 -25
- data/test/machine_by_default_test.rb +0 -16
- data/test/machine_errors_test.rb +0 -19
- data/test/machine_multiple_test.rb +0 -17
- data/test/machine_nested_action_test.rb +0 -38
- data/test/machine_unmigrated_test.rb +0 -14
- data/test/machine_with_aliased_attribute_test.rb +0 -23
- data/test/machine_with_callbacks_test.rb +0 -172
- data/test/machine_with_column_state_attribute_test.rb +0 -44
- data/test/machine_with_complex_pluralization_scopes_test.rb +0 -16
- data/test/machine_with_conflicting_predicate_test.rb +0 -18
- data/test/machine_with_conflicting_state_name_test.rb +0 -29
- data/test/machine_with_custom_attribute_test.rb +0 -21
- data/test/machine_with_default_scope_test.rb +0 -18
- data/test/machine_with_different_column_default_test.rb +0 -27
- data/test/machine_with_different_integer_column_default_test.rb +0 -29
- data/test/machine_with_dirty_attribute_and_custom_attributes_during_loopback_test.rb +0 -24
- data/test/machine_with_dirty_attribute_and_state_events_test.rb +0 -20
- data/test/machine_with_dirty_attributes_and_custom_attribute_test.rb +0 -32
- data/test/machine_with_dirty_attributes_during_loopback_test.rb +0 -22
- data/test/machine_with_dirty_attributes_test.rb +0 -35
- data/test/machine_with_dynamic_initial_state_test.rb +0 -99
- data/test/machine_with_event_attributes_on_autosave_test.rb +0 -48
- data/test/machine_with_event_attributes_on_custom_action_test.rb +0 -41
- data/test/machine_with_event_attributes_on_save_bang_test.rb +0 -82
- data/test/machine_with_event_attributes_on_save_test.rb +0 -244
- data/test/machine_with_event_attributes_on_validation_test.rb +0 -143
- data/test/machine_with_events_test.rb +0 -13
- data/test/machine_with_failed_action_test.rb +0 -40
- data/test/machine_with_failed_after_callbacks_test.rb +0 -35
- data/test/machine_with_failed_before_callbacks_test.rb +0 -36
- data/test/machine_with_initialized_state_test.rb +0 -41
- data/test/machine_with_internationalization_test.rb +0 -180
- data/test/machine_with_loopback_test.rb +0 -22
- data/test/machine_with_non_column_state_attribute_defined_test.rb +0 -29
- data/test/machine_with_same_column_default_test.rb +0 -26
- data/test/machine_with_same_integer_column_default_test.rb +0 -30
- data/test/machine_with_scopes_and_joins_test.rb +0 -37
- data/test/machine_with_scopes_and_owner_subclass_test.rb +0 -27
- data/test/machine_with_scopes_test.rb +0 -70
- data/test/machine_with_state_driven_validations_test.rb +0 -30
- data/test/machine_with_states_test.rb +0 -13
- data/test/machine_with_static_initial_state_test.rb +0 -166
- data/test/machine_with_transactions_test.rb +0 -26
- data/test/machine_with_validations_and_custom_attribute_test.rb +0 -21
- data/test/machine_with_validations_test.rb +0 -47
- data/test/machine_without_database_test.rb +0 -20
- data/test/machine_without_transactions_test.rb +0 -26
- data/test/model_test.rb +0 -12
- data/test/test_helper.rb +0 -52
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8da92c0d0872bf79ae81017dd5d16459e5222d10ee304390ddff3af14f1e1e05
|
|
4
|
+
data.tar.gz: c7ebd9fbdff4ee5bd2b0cca81b5daad186bb92157eddcb912829685571aa84eb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 66d176f9efbc9d44ac2d5a5786f078e593d3590a99d855804ff4775649b18abae64264781f4ddc36a60ac253366aea0a9c02d3d1712d680d78fe443fffd437e7
|
|
7
|
+
data.tar.gz: f5e7a924679499d596439009b9bad2633af5389069959016595d31c44243f7551893cd2e53769e5e1b3a99b979f8d6ad1fa24d86caaa9134f0aa63528739b108
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
[](https://codeclimate.com/github/state-machines/state_machines-activerecord)
|
|
1
|
+
[](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
|
+
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 <
|
|
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
|
-
|
|
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 <
|
|
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 <
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
@@ -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
|
|
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 <
|
|
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.
|
|
82
|
-
# users from tampering with events through URLs / forms,
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
# class
|
|
86
|
-
#
|
|
87
|
-
#
|
|
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 <
|
|
98
|
-
#
|
|
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 <
|
|
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 <
|
|
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 <
|
|
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 <
|
|
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
|
|
203
|
-
#
|
|
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
|
|
205
|
+
# These scopes are essentially the functional equivalent of the
|
|
207
206
|
# following definitions:
|
|
208
207
|
#
|
|
209
|
-
# class Vehicle <
|
|
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
|
|
219
|
+
# Because of the way scopes work in ActiveRecord, they can be
|
|
221
220
|
# chained like so:
|
|
222
221
|
#
|
|
223
|
-
# Vehicle.with_state(:parked).
|
|
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 <
|
|
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 <
|
|
270
|
-
#
|
|
277
|
+
# class TransitionLog < ApplicationRecord
|
|
278
|
+
# connects_to database: { writing: :primary, reading: :primary }
|
|
271
279
|
# end
|
|
272
280
|
#
|
|
273
|
-
# class Vehicle <
|
|
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
|
|
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
|
-
#
|
|
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 = {:
|
|
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
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
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
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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
|
-
|
|
498
|
-
|
|
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
|
-
|
|
503
|
-
|
|
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, <<-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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
|