state_machines-activemodel 0.10.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 +0 -1
- data/lib/state_machines/integrations/active_model/locale.rb +5 -3
- data/lib/state_machines/integrations/active_model/version.rb +1 -1
- data/lib/state_machines/integrations/active_model.rb +42 -20
- data/test/event_human_name_test.rb +176 -0
- data/test/human_name_preservation_test.rb +75 -0
- data/test/machine_initialization_compatibility_test.rb +60 -0
- data/test/machine_with_internationalization_test.rb +9 -9
- data/test/state_human_name_test.rb +152 -0
- metadata +12 -4
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
|
|
@@ -1,12 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
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
|
3
5
|
{ en: {
|
4
6
|
activemodel: {
|
5
7
|
errors: {
|
6
8
|
messages: {
|
7
|
-
invalid: StateMachines::Machine.default_messages[:invalid],
|
8
|
-
invalid_event: StateMachines::Machine.default_messages[:invalid_event] % ['%{state}'],
|
9
|
-
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}'] }
|
10
12
|
}
|
11
13
|
}
|
12
14
|
}
|
@@ -8,7 +8,7 @@ require 'state_machines/integrations/base'
|
|
8
8
|
require 'state_machines/integrations/active_model/version'
|
9
9
|
|
10
10
|
module StateMachines
|
11
|
-
module Integrations
|
11
|
+
module Integrations # :nodoc:
|
12
12
|
# Adds support for integrating state machines with ActiveModel classes.
|
13
13
|
#
|
14
14
|
# == Examples
|
@@ -324,13 +324,13 @@ module StateMachines
|
|
324
324
|
|
325
325
|
# Adds a validation error to the given object
|
326
326
|
def invalidate(object, attribute, message, values = [])
|
327
|
-
|
328
|
-
attribute = self.attribute(attribute)
|
329
|
-
options = values.to_h { |key, value| [key, value] }
|
327
|
+
return unless supports_validations?
|
330
328
|
|
331
|
-
|
332
|
-
|
333
|
-
|
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)
|
334
334
|
end
|
335
335
|
|
336
336
|
# Describes the current validation errors on the given object. If none
|
@@ -345,22 +345,32 @@ module StateMachines
|
|
345
345
|
end
|
346
346
|
|
347
347
|
# Runs state events around the object's validation process
|
348
|
-
def around_validation(object)
|
349
|
-
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(&)
|
350
350
|
end
|
351
351
|
|
352
352
|
protected
|
353
353
|
|
354
354
|
def define_state_initializer
|
355
|
-
define_helper :instance, <<-
|
356
|
-
def initialize(params =
|
357
|
-
|
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|
|
358
361
|
self.class.attribute_aliases[key.to_s] || key.to_s
|
359
362
|
end if self.class.respond_to?(:attribute_aliases)
|
360
363
|
|
361
|
-
|
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
|
362
372
|
end
|
363
|
-
|
373
|
+
END_EVAL
|
364
374
|
end
|
365
375
|
|
366
376
|
# Whether validations are supported in the integration. Only true if
|
@@ -378,7 +388,7 @@ module StateMachines
|
|
378
388
|
|
379
389
|
# Gets the terminator to use for callbacks
|
380
390
|
def callback_terminator
|
381
|
-
@
|
391
|
+
@callback_terminator ||= ->(result) { result == false }
|
382
392
|
end
|
383
393
|
|
384
394
|
# Determines the base scope to use when looking up translations
|
@@ -408,7 +418,7 @@ module StateMachines
|
|
408
418
|
# Generate all possible translation keys
|
409
419
|
translations = ancestors.map { |ancestor| :"#{ancestor.model_name.to_s.underscore}.#{name}.#{group}.#{value}" }
|
410
420
|
translations.concat(ancestors.map { |ancestor| :"#{ancestor.model_name.to_s.underscore}.#{group}.#{value}" })
|
411
|
-
translations.
|
421
|
+
translations.push(:"#{name}.#{group}.#{value}", :"#{group}.#{value}", value.humanize.downcase)
|
412
422
|
I18n.translate(translations.shift, default: translations, scope: [i18n_scope(klass), :state_machines])
|
413
423
|
end
|
414
424
|
|
@@ -422,10 +432,12 @@ module StateMachines
|
|
422
432
|
def define_state_accessor
|
423
433
|
name = self.name
|
424
434
|
|
435
|
+
return unless supports_validations?
|
436
|
+
|
425
437
|
owner_class.validates_each(attribute) do |object|
|
426
438
|
machine = object.class.state_machine(name)
|
427
439
|
machine.invalidate(object, :state, :invalid) unless machine.states.match(object)
|
428
|
-
end
|
440
|
+
end
|
429
441
|
end
|
430
442
|
|
431
443
|
# Adds hooks into validation for automatically firing events
|
@@ -443,7 +455,7 @@ module StateMachines
|
|
443
455
|
# Creates a new callback in the callback chain, always inserting it
|
444
456
|
# before the default Observer callbacks that were created after
|
445
457
|
# initialization.
|
446
|
-
def add_callback(type, options, &
|
458
|
+
def add_callback(type, options, &)
|
447
459
|
options[:terminator] = callback_terminator
|
448
460
|
super
|
449
461
|
end
|
@@ -451,14 +463,24 @@ module StateMachines
|
|
451
463
|
# Configures new states with the built-in humanize scheme
|
452
464
|
def add_states(*)
|
453
465
|
super.each do |new_state|
|
454
|
-
|
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
|
455
472
|
end
|
456
473
|
end
|
457
474
|
|
458
475
|
# Configures new event with the built-in humanize scheme
|
459
476
|
def add_events(*)
|
460
477
|
super.each do |new_event|
|
461
|
-
|
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
|
462
484
|
end
|
463
485
|
end
|
464
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
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'test_helper'
|
4
|
+
|
5
|
+
class HumanNamePreservationTest < 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_preserve_custom_state_human_name_when_using_activemodel_integration
|
14
|
+
# This test specifically verifies that PR #38's fix works:
|
15
|
+
# Using ||= instead of = in add_states method
|
16
|
+
|
17
|
+
@model.class_eval do
|
18
|
+
state_machine :status, initial: :pending do
|
19
|
+
# Define a state with a custom human_name
|
20
|
+
state :pending, human_name: 'My Custom Pending'
|
21
|
+
state :approved
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
machine = @model.state_machine(:status)
|
26
|
+
|
27
|
+
# The custom human_name should be preserved, not overwritten by the integration
|
28
|
+
assert_equal 'My Custom Pending', machine.states[:pending].human_name(@model)
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_should_preserve_custom_event_human_name_when_using_activemodel_integration
|
32
|
+
# This test verifies our additional fix for events:
|
33
|
+
# Using ||= instead of = in add_events method
|
34
|
+
|
35
|
+
@model.class_eval do
|
36
|
+
state_machine :status, initial: :pending do
|
37
|
+
event :approve, human_name: 'Grant Authorization' do
|
38
|
+
transition pending: :approved
|
39
|
+
end
|
40
|
+
|
41
|
+
event :reject do
|
42
|
+
transition pending: :rejected
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
machine = @model.state_machine(:status)
|
48
|
+
|
49
|
+
# The custom human_name should be preserved, not overwritten by the integration
|
50
|
+
assert_equal 'Grant Authorization', machine.events[:approve].human_name(@model)
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_regression_issue_37_hard_coded_human_name_preserved
|
54
|
+
# This is the exact regression test for issue #37
|
55
|
+
# "Hard-coded human_name is being overwritten"
|
56
|
+
|
57
|
+
@model.class_eval do
|
58
|
+
state_machine :status do
|
59
|
+
state :pending, human_name: 'Pending Approval'
|
60
|
+
state :active, human_name: 'Active State'
|
61
|
+
|
62
|
+
event :activate, human_name: 'Activate Now' do
|
63
|
+
transition pending: :active
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
machine = @model.state_machine(:status)
|
69
|
+
|
70
|
+
# Both states and events should preserve their hard-coded human names
|
71
|
+
assert_equal 'Pending Approval', machine.states[:pending].human_name(@model)
|
72
|
+
assert_equal 'Active State', machine.states[:active].human_name(@model)
|
73
|
+
assert_equal 'Activate Now', machine.events[:activate].human_name(@model)
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'test_helper'
|
4
|
+
|
5
|
+
class MachineInitializationCompatibilityTest < BaseTestCase
|
6
|
+
def setup
|
7
|
+
@model = new_model do
|
8
|
+
include ActiveModel::Validations
|
9
|
+
attr_accessor :state
|
10
|
+
end
|
11
|
+
|
12
|
+
@machine = StateMachines::Machine.new(@model, initial: :parked)
|
13
|
+
@machine.state :parked, :idling
|
14
|
+
@machine.event :ignite
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_should_accept_positional_hash_argument
|
18
|
+
record = @model.new({ state: 'idling' })
|
19
|
+
assert_equal 'idling', record.state
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_should_accept_keyword_arguments
|
23
|
+
record = @model.new(state: 'idling')
|
24
|
+
assert_equal 'idling', record.state
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_should_accept_empty_initialization
|
28
|
+
record = @model.new
|
29
|
+
assert_equal 'parked', record.state
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_should_handle_attribute_aliases
|
33
|
+
@model.class_eval do
|
34
|
+
def self.attribute_aliases
|
35
|
+
{ 'status' => 'state' }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
record = @model.new(status: 'idling')
|
40
|
+
assert_equal 'idling', record.state
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_should_prefer_positional_hash_over_keywords_when_both_present
|
44
|
+
# If someone accidentally provides both, positional takes precedence
|
45
|
+
record = @model.new({ state: 'idling' }, state: 'parked')
|
46
|
+
assert_equal 'idling', record.state
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_should_handle_empty_positional_hash
|
50
|
+
# Empty hash should still be treated as positional argument
|
51
|
+
record = @model.new({})
|
52
|
+
assert_equal 'parked', record.state # Gets default initial state
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_should_use_keywords_when_empty_hash_and_keywords_present
|
56
|
+
# With the fix, keywords are ignored even with empty positional hash
|
57
|
+
record = @model.new({}, state: 'idling')
|
58
|
+
assert_equal 'parked', record.state # Empty hash takes precedence
|
59
|
+
end
|
60
|
+
end
|
@@ -56,7 +56,7 @@ class MachineWithInternationalizationTest < BaseTestCase
|
|
56
56
|
|
57
57
|
machine = StateMachines::Machine.new(@model)
|
58
58
|
machine.state :parked
|
59
|
-
assert_equal 'shutdown', machine.state(:parked).human_name
|
59
|
+
assert_equal 'shutdown', machine.state(:parked).human_name(@model)
|
60
60
|
end
|
61
61
|
|
62
62
|
def test_should_allow_customized_state_key_scoped_to_class
|
@@ -67,7 +67,7 @@ class MachineWithInternationalizationTest < BaseTestCase
|
|
67
67
|
machine = StateMachines::Machine.new(@model)
|
68
68
|
machine.state :parked
|
69
69
|
|
70
|
-
assert_equal 'shutdown', machine.state(:parked).human_name
|
70
|
+
assert_equal 'shutdown', machine.state(:parked).human_name(@model)
|
71
71
|
end
|
72
72
|
|
73
73
|
def test_should_allow_customized_state_key_scoped_to_machine
|
@@ -78,7 +78,7 @@ class MachineWithInternationalizationTest < BaseTestCase
|
|
78
78
|
machine = StateMachines::Machine.new(@model)
|
79
79
|
machine.state :parked
|
80
80
|
|
81
|
-
assert_equal 'shutdown', machine.state(:parked).human_name
|
81
|
+
assert_equal 'shutdown', machine.state(:parked).human_name(@model)
|
82
82
|
end
|
83
83
|
|
84
84
|
def test_should_allow_customized_state_key_unscoped
|
@@ -89,7 +89,7 @@ class MachineWithInternationalizationTest < BaseTestCase
|
|
89
89
|
machine = StateMachines::Machine.new(@model)
|
90
90
|
machine.state :parked
|
91
91
|
|
92
|
-
assert_equal 'shutdown', machine.state(:parked).human_name
|
92
|
+
assert_equal 'shutdown', machine.state(:parked).human_name(@model)
|
93
93
|
end
|
94
94
|
|
95
95
|
def test_should_support_nil_state_key
|
@@ -99,7 +99,7 @@ class MachineWithInternationalizationTest < BaseTestCase
|
|
99
99
|
|
100
100
|
machine = StateMachines::Machine.new(@model)
|
101
101
|
|
102
|
-
assert_equal 'empty', machine.state(nil).human_name
|
102
|
+
assert_equal 'empty', machine.state(nil).human_name(@model)
|
103
103
|
end
|
104
104
|
|
105
105
|
def test_should_allow_customized_event_key_scoped_to_class_and_machine
|
@@ -110,7 +110,7 @@ class MachineWithInternationalizationTest < BaseTestCase
|
|
110
110
|
machine = StateMachines::Machine.new(@model)
|
111
111
|
machine.event :park
|
112
112
|
|
113
|
-
assert_equal 'stop', machine.event(:park).human_name
|
113
|
+
assert_equal 'stop', machine.event(:park).human_name(@model)
|
114
114
|
end
|
115
115
|
|
116
116
|
def test_should_allow_customized_event_key_scoped_to_class
|
@@ -121,7 +121,7 @@ class MachineWithInternationalizationTest < BaseTestCase
|
|
121
121
|
machine = StateMachines::Machine.new(@model)
|
122
122
|
machine.event :park
|
123
123
|
|
124
|
-
assert_equal 'stop', machine.event(:park).human_name
|
124
|
+
assert_equal 'stop', machine.event(:park).human_name(@model)
|
125
125
|
end
|
126
126
|
|
127
127
|
def test_should_allow_customized_event_key_scoped_to_machine
|
@@ -132,7 +132,7 @@ class MachineWithInternationalizationTest < BaseTestCase
|
|
132
132
|
machine = StateMachines::Machine.new(@model)
|
133
133
|
machine.event :park
|
134
134
|
|
135
|
-
assert_equal 'stop', machine.event(:park).human_name
|
135
|
+
assert_equal 'stop', machine.event(:park).human_name(@model)
|
136
136
|
end
|
137
137
|
|
138
138
|
def test_should_allow_customized_event_key_unscoped
|
@@ -143,7 +143,7 @@ class MachineWithInternationalizationTest < BaseTestCase
|
|
143
143
|
machine = StateMachines::Machine.new(@model)
|
144
144
|
machine.event :park
|
145
145
|
|
146
|
-
assert_equal 'stop', machine.event(:park).human_name
|
146
|
+
assert_equal 'stop', machine.event(:park).human_name(@model)
|
147
147
|
end
|
148
148
|
|
149
149
|
def test_should_have_locale_once_in_load_path
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'test_helper'
|
4
|
+
|
5
|
+
class StateHumanNameTest < 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_state
|
14
|
+
machine = StateMachines::Machine.new(@model, :status, initial: :pending) do
|
15
|
+
state :pending, human_name: 'Awaiting Approval'
|
16
|
+
state :approved
|
17
|
+
state :rejected, human_name: 'Denied'
|
18
|
+
end
|
19
|
+
|
20
|
+
assert_equal 'Awaiting Approval', machine.states[:pending].human_name(@model)
|
21
|
+
assert_equal 'Denied', machine.states[:rejected].human_name(@model)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_should_not_override_custom_human_name_with_translation
|
25
|
+
# Set up I18n translations
|
26
|
+
I18n.backend.store_translations(:en, {
|
27
|
+
activemodel: {
|
28
|
+
state_machines: {
|
29
|
+
states: {
|
30
|
+
pending: 'Translation for Pending',
|
31
|
+
approved: 'Translation for Approved',
|
32
|
+
rejected: 'Translation for Rejected'
|
33
|
+
}
|
34
|
+
}
|
35
|
+
}
|
36
|
+
})
|
37
|
+
|
38
|
+
machine = StateMachines::Machine.new(@model, :status, initial: :pending) do
|
39
|
+
state :pending, human_name: 'Custom Pending Name'
|
40
|
+
state :approved
|
41
|
+
state :rejected, human_name: 'Custom Rejected Name'
|
42
|
+
end
|
43
|
+
|
44
|
+
# Custom human names should be preserved
|
45
|
+
assert_equal 'Custom Pending Name', machine.states[:pending].human_name(@model)
|
46
|
+
assert_equal 'Custom Rejected Name', machine.states[:rejected].human_name(@model)
|
47
|
+
|
48
|
+
# State without custom human_name gets default behavior (which might not use translations in this test setup)
|
49
|
+
# The key test is that custom human names are preserved, not overwritten
|
50
|
+
refute_equal 'Custom Pending Name', machine.states[:approved].human_name(@model)
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_should_allow_custom_human_name_as_string
|
54
|
+
machine = StateMachines::Machine.new(@model, :status) do
|
55
|
+
state :active, human_name: 'Currently Active'
|
56
|
+
end
|
57
|
+
|
58
|
+
assert_equal 'Currently Active', machine.states[:active].human_name(@model)
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_should_allow_custom_human_name_as_lambda
|
62
|
+
machine = StateMachines::Machine.new(@model, :status) do
|
63
|
+
state :processing, human_name: ->(state, klass) { "#{klass.name} is #{state.name.to_s.upcase}" }
|
64
|
+
end
|
65
|
+
|
66
|
+
assert_equal 'Foo is PROCESSING', machine.states[:processing].human_name(@model)
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_should_use_default_translation_when_no_custom_human_name
|
70
|
+
machine = StateMachines::Machine.new(@model, :status) do
|
71
|
+
state :idle
|
72
|
+
end
|
73
|
+
|
74
|
+
# Should fall back to humanized version when no translation exists
|
75
|
+
assert_equal 'idle', machine.states[:idle].human_name(@model)
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_should_handle_nil_human_name
|
79
|
+
machine = StateMachines::Machine.new(@model, :status) do
|
80
|
+
state :waiting
|
81
|
+
end
|
82
|
+
|
83
|
+
# Explicitly set to nil (should still get default behavior)
|
84
|
+
machine.states[:waiting].human_name = nil
|
85
|
+
|
86
|
+
# When human_name is nil, State#human_name returns nil
|
87
|
+
assert_nil machine.states[:waiting].human_name(@model)
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_should_preserve_human_name_through_multiple_state_definitions
|
91
|
+
machine = StateMachines::Machine.new(@model, :status)
|
92
|
+
|
93
|
+
# First define state with custom human name
|
94
|
+
machine.state :draft, human_name: 'Work in Progress'
|
95
|
+
|
96
|
+
# Redefine the same state (this should not override the human_name)
|
97
|
+
machine.state :draft do
|
98
|
+
# Add some behavior
|
99
|
+
end
|
100
|
+
|
101
|
+
assert_equal 'Work in Progress', machine.states[:draft].human_name(@model)
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_should_work_with_state_machine_helper_method
|
105
|
+
@model.class_eval do
|
106
|
+
state_machine :status, initial: :pending do
|
107
|
+
state :pending, human_name: 'Awaiting Review'
|
108
|
+
state :reviewed
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
machine = @model.state_machine(:status)
|
113
|
+
assert_equal 'Awaiting Review', machine.states[:pending].human_name(@model)
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_should_handle_complex_i18n_lookup_with_custom_human_name
|
117
|
+
# Set up complex I18n structure
|
118
|
+
I18n.backend.store_translations(:en, {
|
119
|
+
activemodel: {
|
120
|
+
state_machines: {
|
121
|
+
foo: {
|
122
|
+
status: {
|
123
|
+
states: {
|
124
|
+
pending: 'Model Specific Pending'
|
125
|
+
}
|
126
|
+
}
|
127
|
+
},
|
128
|
+
status: {
|
129
|
+
states: {
|
130
|
+
pending: 'Machine Specific Pending'
|
131
|
+
}
|
132
|
+
},
|
133
|
+
states: {
|
134
|
+
pending: 'Generic Pending'
|
135
|
+
}
|
136
|
+
}
|
137
|
+
}
|
138
|
+
})
|
139
|
+
|
140
|
+
machine = StateMachines::Machine.new(@model, :status) do
|
141
|
+
state :pending, human_name: 'Overridden Pending'
|
142
|
+
end
|
143
|
+
|
144
|
+
# Should use the custom human_name, not any of the I18n translations
|
145
|
+
assert_equal 'Overridden Pending', machine.states[:pending].human_name(@model)
|
146
|
+
end
|
147
|
+
|
148
|
+
def teardown
|
149
|
+
# Clear I18n translations after each test
|
150
|
+
I18n.backend.reload!
|
151
|
+
end
|
152
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: state_machines-activemodel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.31.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Abdelkader Boudih
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
19
|
+
version: 0.31.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.
|
26
|
+
version: 0.31.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activemodel
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,9 +122,12 @@ files:
|
|
122
122
|
- lib/state_machines/integrations/active_model.rb
|
123
123
|
- lib/state_machines/integrations/active_model/locale.rb
|
124
124
|
- lib/state_machines/integrations/active_model/version.rb
|
125
|
+
- test/event_human_name_test.rb
|
126
|
+
- test/human_name_preservation_test.rb
|
125
127
|
- test/integration_test.rb
|
126
128
|
- test/machine_by_default_test.rb
|
127
129
|
- test/machine_errors_test.rb
|
130
|
+
- test/machine_initialization_compatibility_test.rb
|
128
131
|
- test/machine_multiple_test.rb
|
129
132
|
- test/machine_with_callbacks_test.rb
|
130
133
|
- test/machine_with_dirty_attribute_and_custom_attributes_during_loopback_test.rb
|
@@ -146,6 +149,7 @@ files:
|
|
146
149
|
- test/machine_with_static_initial_state_test.rb
|
147
150
|
- test/machine_with_validations_and_custom_attribute_test.rb
|
148
151
|
- test/machine_with_validations_test.rb
|
152
|
+
- test/state_human_name_test.rb
|
149
153
|
- test/test_helper.rb
|
150
154
|
homepage: https://github.com/state-machines/state_machines-activemodel
|
151
155
|
licenses:
|
@@ -165,13 +169,16 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
165
169
|
- !ruby/object:Gem::Version
|
166
170
|
version: '0'
|
167
171
|
requirements: []
|
168
|
-
rubygems_version: 3.6.
|
172
|
+
rubygems_version: 3.6.9
|
169
173
|
specification_version: 4
|
170
174
|
summary: ActiveModel integration for State Machines
|
171
175
|
test_files:
|
176
|
+
- test/event_human_name_test.rb
|
177
|
+
- test/human_name_preservation_test.rb
|
172
178
|
- test/integration_test.rb
|
173
179
|
- test/machine_by_default_test.rb
|
174
180
|
- test/machine_errors_test.rb
|
181
|
+
- test/machine_initialization_compatibility_test.rb
|
175
182
|
- test/machine_multiple_test.rb
|
176
183
|
- test/machine_with_callbacks_test.rb
|
177
184
|
- test/machine_with_dirty_attribute_and_custom_attributes_during_loopback_test.rb
|
@@ -193,4 +200,5 @@ test_files:
|
|
193
200
|
- test/machine_with_static_initial_state_test.rb
|
194
201
|
- test/machine_with_validations_and_custom_attribute_test.rb
|
195
202
|
- test/machine_with_validations_test.rb
|
203
|
+
- test/state_human_name_test.rb
|
196
204
|
- test/test_helper.rb
|