state_machines-activemodel 0.9.0 → 0.31.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/README.md +7 -26
- data/lib/state_machines/integrations/active_model/locale.rb +7 -3
- data/lib/state_machines/integrations/active_model/version.rb +3 -1
- data/lib/state_machines/integrations/active_model.rb +71 -102
- data/lib/state_machines-activemodel.rb +2 -0
- data/test/event_human_name_test.rb +176 -0
- data/test/human_name_preservation_test.rb +75 -0
- data/test/integration_test.rb +3 -1
- data/test/machine_by_default_test.rb +3 -1
- data/test/machine_errors_test.rb +3 -1
- data/test/machine_initialization_compatibility_test.rb +60 -0
- data/test/machine_multiple_test.rb +3 -1
- data/test/machine_with_callbacks_test.rb +3 -1
- data/test/machine_with_dirty_attribute_and_custom_attributes_during_loopback_test.rb +3 -1
- data/test/machine_with_dirty_attribute_and_state_events_test.rb +3 -1
- data/test/machine_with_dirty_attributes_and_custom_attribute_test.rb +3 -1
- data/test/machine_with_dirty_attributes_during_loopback_test.rb +3 -1
- data/test/machine_with_dirty_attributes_test.rb +3 -1
- data/test/machine_with_dynamic_initial_state_test.rb +3 -1
- data/test/machine_with_events_test.rb +3 -1
- data/test/machine_with_failed_after_callbacks_test.rb +3 -1
- data/test/machine_with_failed_before_callbacks_test.rb +3 -1
- data/test/machine_with_initialized_aliased_attribute_test.rb +3 -1
- data/test/machine_with_initialized_state_test.rb +3 -1
- data/test/machine_with_internationalization_test.rb +12 -10
- data/test/machine_with_model_state_attribute_test.rb +3 -1
- data/test/machine_with_non_model_state_attribute_undefined_test.rb +3 -1
- data/test/machine_with_state_driven_validations_test.rb +4 -2
- data/test/machine_with_states_test.rb +3 -1
- data/test/machine_with_static_initial_state_test.rb +3 -1
- data/test/machine_with_validations_and_custom_attribute_test.rb +3 -1
- data/test/machine_with_validations_test.rb +3 -1
- data/test/state_human_name_test.rb +152 -0
- data/test/test_helper.rb +55 -0
- metadata +18 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ad141a5029b6489fb99da9c9ef4ad7bf2059d9947e993265225bb4ca88a0ff66
|
4
|
+
data.tar.gz: ef7ae48fd6d4d1f712842e2f0057158e5674b5ba59487cc3b0483c89524374e3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 82b54e46bc9713fdda9c22701d4bec699c8065344bb32e10217dc730a0743b0296bb97245d38ffc8d3b18bd65c770058cb1485a723f2620d66051ce93ce4b106
|
7
|
+
data.tar.gz: 3ae6a52dd12a7c7ff56666fc9d691218f043ba2a0915d80262800a873589c0275a538d5b08ab956d046e23a3ba73e9c6d12ceb732ed0c75ee63d89cb77414b09
|
data/README.md
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|

|
2
|
-
[](https://codeclimate.com/github/state-machines/state_machines-activemodel)
|
3
2
|
|
4
3
|
# StateMachines ActiveModel Integration
|
5
4
|
|
@@ -23,7 +22,7 @@ Or install it yourself as:
|
|
23
22
|
|
24
23
|
## Dependencies
|
25
24
|
|
26
|
-
Active Model
|
25
|
+
Active Model 7.1+
|
27
26
|
|
28
27
|
## Usage
|
29
28
|
|
@@ -36,19 +35,19 @@ class Vehicle
|
|
36
35
|
attr_accessor :state
|
37
36
|
define_attribute_methods [:state]
|
38
37
|
|
39
|
-
state_machine :
|
40
|
-
before_transition :
|
41
|
-
after_transition any
|
38
|
+
state_machine initial: :parked do
|
39
|
+
before_transition parked: any - :parked, do: :put_on_seatbelt
|
40
|
+
after_transition any: :parked do |vehicle, transition|
|
42
41
|
vehicle.seatbelt = 'off'
|
43
42
|
end
|
44
43
|
around_transition :benchmark
|
45
44
|
|
46
|
-
event :
|
47
|
-
transition :
|
45
|
+
event ignite: do
|
46
|
+
transition parked: :idling
|
48
47
|
end
|
49
48
|
|
50
49
|
state :first_gear, :second_gear do
|
51
|
-
|
50
|
+
validates :seatbelt_on, presence: true
|
52
51
|
end
|
53
52
|
end
|
54
53
|
|
@@ -62,24 +61,6 @@ class Vehicle
|
|
62
61
|
...
|
63
62
|
end
|
64
63
|
end
|
65
|
-
|
66
|
-
class VehicleObserver < ActiveModel::Observer
|
67
|
-
# Callback for :ignite event *before* the transition is performed
|
68
|
-
def before_ignite(vehicle, transition)
|
69
|
-
# log message
|
70
|
-
end
|
71
|
-
|
72
|
-
# Generic transition callback *after* the transition is performed
|
73
|
-
def after_transition(vehicle, transition)
|
74
|
-
Audit.log(vehicle, transition)
|
75
|
-
end
|
76
|
-
|
77
|
-
# Generic callback after the transition fails to perform
|
78
|
-
def after_failure_to_transition(vehicle, transition)
|
79
|
-
Audit.error(vehicle, transition)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
64
|
```
|
84
65
|
|
85
66
|
## Contributing
|
@@ -1,10 +1,14 @@
|
|
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
6
|
activemodel: {
|
3
7
|
errors: {
|
4
8
|
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}']
|
9
|
+
invalid: lambda { |*| StateMachines::Machine.default_messages[:invalid] },
|
10
|
+
invalid_event: lambda { |*| StateMachines::Machine.default_messages[:invalid_event] % ['%{state}'] },
|
11
|
+
invalid_transition: lambda { |*| StateMachines::Machine.default_messages[:invalid_transition] % ['%{event}'] }
|
8
12
|
}
|
9
13
|
}
|
10
14
|
}
|
@@ -1,12 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_model'
|
2
4
|
require 'active_support/core_ext/hash/keys'
|
3
|
-
require 'active_support/core_ext/module/attribute_accessors
|
5
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
4
6
|
require 'state_machines'
|
5
7
|
require 'state_machines/integrations/base'
|
6
8
|
require 'state_machines/integrations/active_model/version'
|
7
9
|
|
8
10
|
module StateMachines
|
9
|
-
module Integrations
|
11
|
+
module Integrations # :nodoc:
|
10
12
|
# Adds support for integrating state machines with ActiveModel classes.
|
11
13
|
#
|
12
14
|
# == Examples
|
@@ -25,9 +27,9 @@ module StateMachines
|
|
25
27
|
# attr_accessor :state
|
26
28
|
# define_attribute_methods [:state]
|
27
29
|
#
|
28
|
-
# state_machine :
|
30
|
+
# state_machine initial: :parked do
|
29
31
|
# event :ignite do
|
30
|
-
# transition :
|
32
|
+
# transition parked: :idling
|
31
33
|
# end
|
32
34
|
# end
|
33
35
|
# end
|
@@ -45,7 +47,7 @@ module StateMachines
|
|
45
47
|
# include ActiveModel::Validations
|
46
48
|
# attr_accessor :state
|
47
49
|
#
|
48
|
-
# state_machine :
|
50
|
+
# state_machine action: :save do
|
49
51
|
# ...
|
50
52
|
# end
|
51
53
|
#
|
@@ -83,7 +85,7 @@ module StateMachines
|
|
83
85
|
# state_machine do
|
84
86
|
# ...
|
85
87
|
# state :first_gear, :second_gear do
|
86
|
-
# validate {|vehicle| vehicle.speed_is_legal}
|
88
|
+
# validate { |vehicle| vehicle.speed_is_legal }
|
87
89
|
# end
|
88
90
|
# end
|
89
91
|
# end
|
@@ -115,38 +117,44 @@ module StateMachines
|
|
115
117
|
# Beware that public event attributes mean that events can be fired
|
116
118
|
# whenever mass-assignment is being used. If you want to prevent malicious
|
117
119
|
# users from tampering with events through URLs / forms, the attribute
|
118
|
-
# should be protected
|
120
|
+
# should be protected using Strong Parameters in your controllers:
|
119
121
|
#
|
120
122
|
# class Vehicle
|
121
|
-
# include ActiveModel::MassAssignmentSecurity
|
122
123
|
# attr_accessor :state
|
123
124
|
#
|
124
|
-
# attr_protected :state_event
|
125
|
-
# # attr_accessible ... # Alternative technique
|
126
|
-
#
|
127
125
|
# state_machine do
|
128
126
|
# ...
|
129
127
|
# end
|
130
128
|
# end
|
131
129
|
#
|
130
|
+
# # In your controller
|
131
|
+
# def vehicle_params
|
132
|
+
# params.require(:vehicle).permit(:attribute1, :attribute2) # Exclude :state_event
|
133
|
+
# end
|
134
|
+
#
|
132
135
|
# If you want to only have *some* events be able to fire via mass-assignment,
|
133
|
-
# you can build two state machines (one
|
136
|
+
# you can build two state machines (one private and one public) like so:
|
134
137
|
#
|
135
138
|
# class Vehicle
|
136
139
|
# attr_accessor :state
|
137
140
|
#
|
138
|
-
# attr_protected :state_event # Prevent access to events in the first machine
|
139
|
-
#
|
140
141
|
# state_machine do
|
141
142
|
# # Define private events here
|
142
143
|
# end
|
143
144
|
#
|
144
145
|
# # Public machine targets the same state as the private machine
|
145
|
-
# state_machine :public_state, :
|
146
|
+
# state_machine :public_state, attribute: :state do
|
146
147
|
# # Define public events here
|
147
148
|
# end
|
148
149
|
# end
|
149
150
|
#
|
151
|
+
# # In your controller
|
152
|
+
# def vehicle_params
|
153
|
+
# # Only permit events from the public state machine
|
154
|
+
# params.require(:vehicle).permit(:attribute1, :attribute2, :public_state_event)
|
155
|
+
# # The private state_event is not permitted
|
156
|
+
# end
|
157
|
+
#
|
150
158
|
# == Callbacks
|
151
159
|
#
|
152
160
|
# All before/after transition callbacks defined for ActiveModel models
|
@@ -159,7 +167,7 @@ module StateMachines
|
|
159
167
|
# include ActiveModel::Validations
|
160
168
|
# attr_accessor :state
|
161
169
|
#
|
162
|
-
# state_machine :
|
170
|
+
# state_machine initial: :parked do
|
163
171
|
# before_transition any => :idling do |vehicle|
|
164
172
|
# vehicle.put_on_seatbelt
|
165
173
|
# end
|
@@ -169,7 +177,7 @@ module StateMachines
|
|
169
177
|
# end
|
170
178
|
#
|
171
179
|
# event :ignite do
|
172
|
-
# transition :
|
180
|
+
# transition parked: :idling
|
173
181
|
# end
|
174
182
|
# end
|
175
183
|
#
|
@@ -181,64 +189,6 @@ module StateMachines
|
|
181
189
|
# Note, also, that the transition can be accessed by simply defining
|
182
190
|
# additional arguments in the callback block.
|
183
191
|
#
|
184
|
-
# == Observers
|
185
|
-
#
|
186
|
-
# In order to hook in observer support for your application, the
|
187
|
-
# ActiveModel::Observing feature must be included. This can be added by including the
|
188
|
-
# https://github.com/state-machines/state_machines-activemodel-observers gem in your
|
189
|
-
# Gemfile. Because of the way
|
190
|
-
# ActiveModel observers are designed, there is less flexibility around the
|
191
|
-
# specific transitions that can be hooked in. However, a large number of
|
192
|
-
# hooks *are* supported. For example, if a transition for a object's
|
193
|
-
# +state+ attribute changes the state from +parked+ to +idling+ via the
|
194
|
-
# +ignite+ event, the following observer methods are supported:
|
195
|
-
# * before/after/after_failure_to-_ignite_from_parked_to_idling
|
196
|
-
# * before/after/after_failure_to-_ignite_from_parked
|
197
|
-
# * before/after/after_failure_to-_ignite_to_idling
|
198
|
-
# * before/after/after_failure_to-_ignite
|
199
|
-
# * before/after/after_failure_to-_transition_state_from_parked_to_idling
|
200
|
-
# * before/after/after_failure_to-_transition_state_from_parked
|
201
|
-
# * before/after/after_failure_to-_transition_state_to_idling
|
202
|
-
# * before/after/after_failure_to-_transition_state
|
203
|
-
# * before/after/after_failure_to-_transition
|
204
|
-
#
|
205
|
-
# The following class shows an example of some of these hooks:
|
206
|
-
#
|
207
|
-
# class VehicleObserver < ActiveModel::Observer
|
208
|
-
# # Callback for :ignite event *before* the transition is performed
|
209
|
-
# def before_ignite(vehicle, transition)
|
210
|
-
# # log message
|
211
|
-
# end
|
212
|
-
#
|
213
|
-
# # Callback for :ignite event *after* the transition has been performed
|
214
|
-
# def after_ignite(vehicle, transition)
|
215
|
-
# # put on seatbelt
|
216
|
-
# end
|
217
|
-
#
|
218
|
-
# # Generic transition callback *before* the transition is performed
|
219
|
-
# def after_transition(vehicle, transition)
|
220
|
-
# Audit.log(vehicle, transition)
|
221
|
-
# end
|
222
|
-
#
|
223
|
-
# def after_failure_to_transition(vehicle, transition)
|
224
|
-
# Audit.error(vehicle, transition)
|
225
|
-
# end
|
226
|
-
# end
|
227
|
-
#
|
228
|
-
# More flexible transition callbacks can be defined directly within the
|
229
|
-
# model as described in StateMachine::Machine#before_transition
|
230
|
-
# and StateMachine::Machine#after_transition.
|
231
|
-
#
|
232
|
-
# To define a single observer for multiple state machines:
|
233
|
-
#
|
234
|
-
# class StateMachineObserver < ActiveModel::Observer
|
235
|
-
# observe Vehicle, Switch, Project
|
236
|
-
#
|
237
|
-
# def after_transition(object, transition)
|
238
|
-
# Audit.log(object, transition)
|
239
|
-
# end
|
240
|
-
# end
|
241
|
-
#
|
242
192
|
# == Internationalization
|
243
193
|
#
|
244
194
|
# Any error message that is generated from performing invalid transitions
|
@@ -308,9 +258,9 @@ module StateMachines
|
|
308
258
|
# include ActiveModel::Dirty
|
309
259
|
# attr_accessor :state
|
310
260
|
#
|
311
|
-
# state_machine :
|
261
|
+
# state_machine initial: :parked do
|
312
262
|
# event :park do
|
313
|
-
# transition :
|
263
|
+
# transition parked: :parked, ...
|
314
264
|
# end
|
315
265
|
# end
|
316
266
|
# end
|
@@ -322,7 +272,7 @@ module StateMachines
|
|
322
272
|
#
|
323
273
|
# class Vehicle
|
324
274
|
# ...
|
325
|
-
# state_machine :
|
275
|
+
# state_machine initial: :parked do
|
326
276
|
# before_transition all => same do |vehicle|
|
327
277
|
# vehicle.state_will_change!
|
328
278
|
#
|
@@ -344,7 +294,7 @@ module StateMachines
|
|
344
294
|
# module StateMachine::Integrations::MyORM
|
345
295
|
# include ActiveModel
|
346
296
|
#
|
347
|
-
# mattr_accessor(:defaults) { :
|
297
|
+
# mattr_accessor(:defaults) { { action: :persist } }
|
348
298
|
#
|
349
299
|
# def self.matches?(klass)
|
350
300
|
# defined?(::MyORM::Base) && klass <= ::MyORM::Base
|
@@ -374,22 +324,19 @@ module StateMachines
|
|
374
324
|
|
375
325
|
# Adds a validation error to the given object
|
376
326
|
def invalidate(object, attribute, message, values = [])
|
377
|
-
|
378
|
-
attribute = self.attribute(attribute)
|
379
|
-
options = values.reduce({}) do |h, (key, value)|
|
380
|
-
h[key] = value
|
381
|
-
h
|
382
|
-
end
|
327
|
+
return unless supports_validations?
|
383
328
|
|
384
|
-
|
385
|
-
|
386
|
-
|
329
|
+
attribute = self.attribute(attribute)
|
330
|
+
options = values.to_h
|
331
|
+
|
332
|
+
default_options = default_error_message_options(object, attribute, message)
|
333
|
+
object.errors.add(attribute, message, **options, **default_options)
|
387
334
|
end
|
388
335
|
|
389
336
|
# Describes the current validation errors on the given object. If none
|
390
337
|
# are specific, then the default error is interpeted as a "halt".
|
391
338
|
def errors_for(object)
|
392
|
-
object.errors.empty? ? 'Transition halted' : object.errors.full_messages
|
339
|
+
object.errors.empty? ? 'Transition halted' : object.errors.full_messages.join(', ')
|
393
340
|
end
|
394
341
|
|
395
342
|
# Resets any errors previously added when invalidating the given object
|
@@ -398,22 +345,32 @@ module StateMachines
|
|
398
345
|
end
|
399
346
|
|
400
347
|
# Runs state events around the object's validation process
|
401
|
-
def around_validation(object)
|
402
|
-
object.class.state_machines.transitions(object, action, after: false).perform
|
348
|
+
def around_validation(object, &)
|
349
|
+
object.class.state_machines.transitions(object, action, after: false).perform(&)
|
403
350
|
end
|
404
351
|
|
405
352
|
protected
|
406
353
|
|
407
354
|
def define_state_initializer
|
408
|
-
define_helper :instance, <<-
|
409
|
-
def initialize(params =
|
410
|
-
|
355
|
+
define_helper :instance, <<-END_EVAL, __FILE__, __LINE__ + 1
|
356
|
+
def initialize(params = nil, **kwargs)
|
357
|
+
# Support both positional hash and keyword arguments
|
358
|
+
attrs = params.nil? ? kwargs : params
|
359
|
+
#{' '}
|
360
|
+
attrs.transform_keys! do |key|
|
411
361
|
self.class.attribute_aliases[key.to_s] || key.to_s
|
412
362
|
end if self.class.respond_to?(:attribute_aliases)
|
413
363
|
|
414
|
-
|
364
|
+
# Call super with the appropriate arguments based on what we received
|
365
|
+
self.class.state_machines.initialize_states(self, {}, attrs) do
|
366
|
+
if params
|
367
|
+
super(params)
|
368
|
+
else
|
369
|
+
super(**kwargs)
|
370
|
+
end
|
371
|
+
end
|
415
372
|
end
|
416
|
-
|
373
|
+
END_EVAL
|
417
374
|
end
|
418
375
|
|
419
376
|
# Whether validations are supported in the integration. Only true if
|
@@ -431,7 +388,7 @@ module StateMachines
|
|
431
388
|
|
432
389
|
# Gets the terminator to use for callbacks
|
433
390
|
def callback_terminator
|
434
|
-
@
|
391
|
+
@callback_terminator ||= ->(result) { result == false }
|
435
392
|
end
|
436
393
|
|
437
394
|
# Determines the base scope to use when looking up translations
|
@@ -461,7 +418,7 @@ module StateMachines
|
|
461
418
|
# Generate all possible translation keys
|
462
419
|
translations = ancestors.map { |ancestor| :"#{ancestor.model_name.to_s.underscore}.#{name}.#{group}.#{value}" }
|
463
420
|
translations.concat(ancestors.map { |ancestor| :"#{ancestor.model_name.to_s.underscore}.#{group}.#{value}" })
|
464
|
-
translations.
|
421
|
+
translations.push(:"#{name}.#{group}.#{value}", :"#{group}.#{value}", value.humanize.downcase)
|
465
422
|
I18n.translate(translations.shift, default: translations, scope: [i18n_scope(klass), :state_machines])
|
466
423
|
end
|
467
424
|
|
@@ -475,10 +432,12 @@ module StateMachines
|
|
475
432
|
def define_state_accessor
|
476
433
|
name = self.name
|
477
434
|
|
435
|
+
return unless supports_validations?
|
436
|
+
|
478
437
|
owner_class.validates_each(attribute) do |object|
|
479
438
|
machine = object.class.state_machine(name)
|
480
439
|
machine.invalidate(object, :state, :invalid) unless machine.states.match(object)
|
481
|
-
end
|
440
|
+
end
|
482
441
|
end
|
483
442
|
|
484
443
|
# Adds hooks into validation for automatically firing events
|
@@ -496,7 +455,7 @@ module StateMachines
|
|
496
455
|
# Creates a new callback in the callback chain, always inserting it
|
497
456
|
# before the default Observer callbacks that were created after
|
498
457
|
# initialization.
|
499
|
-
def add_callback(type, options, &
|
458
|
+
def add_callback(type, options, &)
|
500
459
|
options[:terminator] = callback_terminator
|
501
460
|
super
|
502
461
|
end
|
@@ -504,14 +463,24 @@ module StateMachines
|
|
504
463
|
# Configures new states with the built-in humanize scheme
|
505
464
|
def add_states(*)
|
506
465
|
super.each do |new_state|
|
507
|
-
|
466
|
+
# Only set the translation lambda if human_name is the default auto-generated value
|
467
|
+
# This preserves user-specified human names while still applying translations for defaults
|
468
|
+
default_human_name = new_state.name ? new_state.name.to_s.tr('_', ' ') : 'nil'
|
469
|
+
if new_state.human_name == default_human_name
|
470
|
+
new_state.human_name = ->(state, klass) { translate(klass, :state, state.name) }
|
471
|
+
end
|
508
472
|
end
|
509
473
|
end
|
510
474
|
|
511
475
|
# Configures new event with the built-in humanize scheme
|
512
476
|
def add_events(*)
|
513
477
|
super.each do |new_event|
|
514
|
-
|
478
|
+
# Only set the translation lambda if human_name is the default auto-generated value
|
479
|
+
# This preserves user-specified human names while still applying translations for defaults
|
480
|
+
default_human_name = new_event.name ? new_event.name.to_s.tr('_', ' ') : 'nil'
|
481
|
+
if new_event.human_name == default_human_name
|
482
|
+
new_event.human_name = ->(event, klass) { translate(klass, :event, event.name) }
|
483
|
+
end
|
515
484
|
end
|
516
485
|
end
|
517
486
|
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'test_helper'
|
4
|
+
|
5
|
+
class EventHumanNameTest < BaseTestCase
|
6
|
+
def setup
|
7
|
+
@model = new_model do
|
8
|
+
include ActiveModel::Validations
|
9
|
+
attr_accessor :status
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_should_allow_custom_human_name_on_event
|
14
|
+
machine = StateMachines::Machine.new(@model, :status, initial: :parked) do
|
15
|
+
event :start, human_name: 'Start Engine' do
|
16
|
+
transition parked: :running
|
17
|
+
end
|
18
|
+
|
19
|
+
event :stop do
|
20
|
+
transition running: :parked
|
21
|
+
end
|
22
|
+
|
23
|
+
event :pause, human_name: 'Temporarily Pause' do
|
24
|
+
transition running: :paused
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
assert_equal 'Start Engine', machine.events[:start].human_name(@model)
|
29
|
+
assert_equal 'Temporarily Pause', machine.events[:pause].human_name(@model)
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_should_not_override_custom_event_human_name_with_translation
|
33
|
+
# Set up I18n translations
|
34
|
+
I18n.backend.store_translations(:en, {
|
35
|
+
activemodel: {
|
36
|
+
state_machines: {
|
37
|
+
events: {
|
38
|
+
ignite: 'Translation for Ignite',
|
39
|
+
park: 'Translation for Park',
|
40
|
+
repair: 'Translation for Repair'
|
41
|
+
}
|
42
|
+
}
|
43
|
+
}
|
44
|
+
})
|
45
|
+
|
46
|
+
machine = StateMachines::Machine.new(@model, :status, initial: :parked) do
|
47
|
+
event :ignite, human_name: 'Custom Ignition' do
|
48
|
+
transition parked: :idling
|
49
|
+
end
|
50
|
+
|
51
|
+
event :park do
|
52
|
+
transition idling: :parked
|
53
|
+
end
|
54
|
+
|
55
|
+
event :repair, human_name: 'Custom Repair Process' do
|
56
|
+
transition any => :parked
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Custom human names should be preserved
|
61
|
+
assert_equal 'Custom Ignition', machine.events[:ignite].human_name(@model)
|
62
|
+
assert_equal 'Custom Repair Process', machine.events[:repair].human_name(@model)
|
63
|
+
|
64
|
+
# Event without custom human_name should use translation
|
65
|
+
assert_equal 'Translation for Park', machine.events[:park].human_name(@model)
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_should_allow_custom_event_human_name_as_string
|
69
|
+
machine = StateMachines::Machine.new(@model, :status) do
|
70
|
+
event :activate, human_name: 'Turn On'
|
71
|
+
end
|
72
|
+
|
73
|
+
assert_equal 'Turn On', machine.events[:activate].human_name(@model)
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_should_allow_custom_event_human_name_as_lambda
|
77
|
+
machine = StateMachines::Machine.new(@model, :status) do
|
78
|
+
event :process, human_name: ->(event, klass) { "#{klass.name}: #{event.name.to_s.capitalize} Action" }
|
79
|
+
end
|
80
|
+
|
81
|
+
assert_equal 'Foo: Process Action', machine.events[:process].human_name(@model)
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_should_use_default_translation_when_no_custom_event_human_name
|
85
|
+
machine = StateMachines::Machine.new(@model, :status) do
|
86
|
+
event :idle
|
87
|
+
end
|
88
|
+
|
89
|
+
# Should fall back to humanized version when no translation exists
|
90
|
+
assert_equal 'idle', machine.events[:idle].human_name(@model)
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_should_handle_nil_event_human_name
|
94
|
+
machine = StateMachines::Machine.new(@model, :status) do
|
95
|
+
event :wait
|
96
|
+
end
|
97
|
+
|
98
|
+
# Explicitly set to nil
|
99
|
+
machine.events[:wait].human_name = nil
|
100
|
+
|
101
|
+
# When human_name is nil, Event#human_name returns nil
|
102
|
+
assert_nil machine.events[:wait].human_name(@model)
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_should_preserve_event_human_name_through_multiple_definitions
|
106
|
+
machine = StateMachines::Machine.new(@model, :status, initial: :draft)
|
107
|
+
|
108
|
+
# First define event with custom human name
|
109
|
+
machine.event :publish, human_name: 'Make Public' do
|
110
|
+
transition draft: :published
|
111
|
+
end
|
112
|
+
|
113
|
+
# Redefine the same event (this should not override the human_name)
|
114
|
+
machine.event :publish do
|
115
|
+
transition pending: :published
|
116
|
+
end
|
117
|
+
|
118
|
+
assert_equal 'Make Public', machine.events[:publish].human_name(@model)
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_should_work_with_state_machine_helper_method
|
122
|
+
@model.class_eval do
|
123
|
+
state_machine :status, initial: :pending do
|
124
|
+
event :approve, human_name: 'Grant Approval' do
|
125
|
+
transition pending: :approved
|
126
|
+
end
|
127
|
+
|
128
|
+
event :reject do
|
129
|
+
transition pending: :rejected
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
machine = @model.state_machine(:status)
|
135
|
+
assert_equal 'Grant Approval', machine.events[:approve].human_name(@model)
|
136
|
+
end
|
137
|
+
|
138
|
+
def test_should_handle_complex_i18n_lookup_with_custom_event_human_name
|
139
|
+
# Set up complex I18n structure
|
140
|
+
I18n.backend.store_translations(:en, {
|
141
|
+
activemodel: {
|
142
|
+
state_machines: {
|
143
|
+
foo: {
|
144
|
+
status: {
|
145
|
+
events: {
|
146
|
+
submit: 'Model Specific Submit'
|
147
|
+
}
|
148
|
+
}
|
149
|
+
},
|
150
|
+
status: {
|
151
|
+
events: {
|
152
|
+
submit: 'Machine Specific Submit'
|
153
|
+
}
|
154
|
+
},
|
155
|
+
events: {
|
156
|
+
submit: 'Generic Submit'
|
157
|
+
}
|
158
|
+
}
|
159
|
+
}
|
160
|
+
})
|
161
|
+
|
162
|
+
machine = StateMachines::Machine.new(@model, :status) do
|
163
|
+
event :submit, human_name: 'Send for Review' do
|
164
|
+
transition draft: :pending
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Should use the custom human_name, not any of the I18n translations
|
169
|
+
assert_equal 'Send for Review', machine.events[:submit].human_name(@model)
|
170
|
+
end
|
171
|
+
|
172
|
+
def teardown
|
173
|
+
# Clear I18n translations after each test
|
174
|
+
I18n.backend.reload!
|
175
|
+
end
|
176
|
+
end
|