enum_machine 1.0.0 → 2.0.1
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/.rubocop.yml +8 -0
- data/Gemfile +8 -8
- data/Gemfile.lock +100 -70
- data/README.md +142 -55
- data/Rakefile +3 -3
- data/lib/enum_machine/attribute_persistence_methods.rb +0 -6
- data/lib/enum_machine/{build_class.rb → build_enum_class.rb} +16 -8
- data/lib/enum_machine/{build_attribute.rb → build_value_class.rb} +5 -5
- data/lib/enum_machine/driver_active_record.rb +14 -16
- data/lib/enum_machine/driver_simple_class.rb +10 -15
- data/lib/enum_machine/machine.rb +3 -5
- data/lib/enum_machine/version.rb +1 -3
- data/lib/enum_machine.rb +15 -15
- metadata +8 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aee9e7e0ece2e73f55fdd9c7180c3babfc1b2bed804cb3475fb1867b0dba757d
|
4
|
+
data.tar.gz: a9b418f9678c7567baf43d84ac427249580fd4bc976b616677ecb52c89b082ec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b09927e13245d01a177fbc80b38714021d8c74c2d28a9b3538a3290e7aeb9b298c409e0571fcd9f5db92ddf152eeab8d15edc153315bd75884efadfa77c7b1db
|
7
|
+
data.tar.gz: 422c5c3de070e51c9314dab1fcb724fd20c3c256433026c32bae4288757438dd51cefe43236ff5256af1577a16a06f343276ea7d4d26f620d433e17e89b2a3f9
|
data/.rubocop.yml
CHANGED
@@ -8,6 +8,9 @@ AllCops:
|
|
8
8
|
Rails/ApplicationRecord:
|
9
9
|
Enabled: false
|
10
10
|
|
11
|
+
Rails/Output:
|
12
|
+
Enabled: false
|
13
|
+
|
11
14
|
Metrics/ParameterLists:
|
12
15
|
CountKeywordArgs: false
|
13
16
|
|
@@ -16,3 +19,8 @@ Style/Next:
|
|
16
19
|
|
17
20
|
Style/ClassVars:
|
18
21
|
Enabled: false
|
22
|
+
|
23
|
+
Gp/UnsafeYamlMarshal:
|
24
|
+
Enabled: true
|
25
|
+
Exclude:
|
26
|
+
- spec/**/*.rb
|
data/Gemfile
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
source
|
3
|
+
source "https://rubygems.org"
|
4
4
|
|
5
5
|
gemspec
|
6
6
|
|
7
|
-
gem
|
8
|
-
gem
|
9
|
-
gem
|
10
|
-
gem
|
11
|
-
gem
|
12
|
-
gem
|
13
|
-
gem
|
7
|
+
gem "dry-types"
|
8
|
+
gem "priscilla", github: "corp-gp/priscilla"
|
9
|
+
gem "pry", "~> 0.12"
|
10
|
+
gem "rake", "~> 13.0"
|
11
|
+
gem "rspec", "~> 3.9"
|
12
|
+
gem "rubocop-gp", github: "corp-gp/rubocop-gp"
|
13
|
+
gem "sqlite3", "~> 1.4"
|
data/Gemfile.lock
CHANGED
@@ -8,18 +8,21 @@ GIT
|
|
8
8
|
|
9
9
|
GIT
|
10
10
|
remote: https://github.com/corp-gp/rubocop-gp.git
|
11
|
-
revision:
|
11
|
+
revision: 867f7e1351c3730897cacdc20ce2d727604de245
|
12
12
|
specs:
|
13
|
-
rubocop-gp (0.0.
|
13
|
+
rubocop-gp (0.0.4)
|
14
14
|
rubocop
|
15
|
+
rubocop-capybara
|
16
|
+
rubocop-factory_bot
|
15
17
|
rubocop-performance
|
16
18
|
rubocop-rails
|
17
19
|
rubocop-rspec
|
20
|
+
rubocop-rspec_rails
|
18
21
|
|
19
22
|
PATH
|
20
23
|
remote: .
|
21
24
|
specs:
|
22
|
-
enum_machine (
|
25
|
+
enum_machine (2.0.1)
|
23
26
|
activemodel
|
24
27
|
activerecord
|
25
28
|
activesupport
|
@@ -27,98 +30,125 @@ PATH
|
|
27
30
|
GEM
|
28
31
|
remote: https://rubygems.org/
|
29
32
|
specs:
|
30
|
-
activemodel (
|
31
|
-
activesupport (=
|
32
|
-
activerecord (
|
33
|
-
activemodel (=
|
34
|
-
activesupport (=
|
35
|
-
|
36
|
-
|
33
|
+
activemodel (7.2.1)
|
34
|
+
activesupport (= 7.2.1)
|
35
|
+
activerecord (7.2.1)
|
36
|
+
activemodel (= 7.2.1)
|
37
|
+
activesupport (= 7.2.1)
|
38
|
+
timeout (>= 0.4.0)
|
39
|
+
activesupport (7.2.1)
|
40
|
+
base64
|
41
|
+
bigdecimal
|
42
|
+
concurrent-ruby (~> 1.0, >= 1.3.1)
|
43
|
+
connection_pool (>= 2.2.5)
|
44
|
+
drb
|
37
45
|
i18n (>= 1.6, < 2)
|
46
|
+
logger (>= 1.4.2)
|
38
47
|
minitest (>= 5.1)
|
39
|
-
|
40
|
-
|
48
|
+
securerandom (>= 0.3)
|
49
|
+
tzinfo (~> 2.0, >= 2.0.5)
|
41
50
|
ast (2.4.2)
|
51
|
+
base64 (0.2.0)
|
52
|
+
bigdecimal (3.1.8)
|
42
53
|
coderay (1.1.3)
|
43
54
|
colorize (0.8.1)
|
44
|
-
concurrent-ruby (1.
|
45
|
-
|
46
|
-
|
55
|
+
concurrent-ruby (1.3.4)
|
56
|
+
connection_pool (2.4.1)
|
57
|
+
diff-lcs (1.5.1)
|
58
|
+
drb (2.2.1)
|
59
|
+
dry-core (1.0.1)
|
47
60
|
concurrent-ruby (~> 1.0)
|
48
|
-
|
49
|
-
dry-
|
61
|
+
zeitwerk (~> 2.6)
|
62
|
+
dry-inflector (1.1.0)
|
63
|
+
dry-logic (1.5.0)
|
50
64
|
concurrent-ruby (~> 1.0)
|
51
|
-
dry-
|
52
|
-
|
65
|
+
dry-core (~> 1.0, < 2)
|
66
|
+
zeitwerk (~> 2.6)
|
67
|
+
dry-types (1.7.2)
|
68
|
+
bigdecimal (~> 3.0)
|
53
69
|
concurrent-ruby (~> 1.0)
|
54
|
-
|
55
|
-
|
70
|
+
dry-core (~> 1.0)
|
71
|
+
dry-inflector (~> 1.0)
|
72
|
+
dry-logic (~> 1.4)
|
73
|
+
zeitwerk (~> 2.6)
|
74
|
+
i18n (1.14.6)
|
56
75
|
concurrent-ruby (~> 1.0)
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
i18n (1.9.1)
|
65
|
-
concurrent-ruby (~> 1.0)
|
66
|
-
method_source (1.0.0)
|
67
|
-
minitest (5.15.0)
|
68
|
-
parallel (1.21.0)
|
69
|
-
parser (3.1.0.0)
|
76
|
+
json (2.8.2)
|
77
|
+
language_server-protocol (3.17.0.3)
|
78
|
+
logger (1.6.1)
|
79
|
+
method_source (1.1.0)
|
80
|
+
minitest (5.25.2)
|
81
|
+
parallel (1.26.3)
|
82
|
+
parser (3.3.6.0)
|
70
83
|
ast (~> 2.4.1)
|
71
|
-
|
84
|
+
racc
|
85
|
+
pry (0.14.2)
|
72
86
|
coderay (~> 1.1)
|
73
87
|
method_source (~> 1.0)
|
74
|
-
|
88
|
+
racc (1.8.1)
|
89
|
+
rack (3.1.8)
|
75
90
|
rainbow (3.1.1)
|
76
|
-
rake (13.
|
77
|
-
regexp_parser (2.2
|
78
|
-
|
79
|
-
|
80
|
-
rspec-
|
81
|
-
rspec-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
rspec-expectations (3.11.0)
|
91
|
+
rake (13.2.1)
|
92
|
+
regexp_parser (2.9.2)
|
93
|
+
rspec (3.13.0)
|
94
|
+
rspec-core (~> 3.13.0)
|
95
|
+
rspec-expectations (~> 3.13.0)
|
96
|
+
rspec-mocks (~> 3.13.0)
|
97
|
+
rspec-core (3.13.1)
|
98
|
+
rspec-support (~> 3.13.0)
|
99
|
+
rspec-expectations (3.13.3)
|
86
100
|
diff-lcs (>= 1.2.0, < 2.0)
|
87
|
-
rspec-support (~> 3.
|
88
|
-
rspec-mocks (3.
|
101
|
+
rspec-support (~> 3.13.0)
|
102
|
+
rspec-mocks (3.13.1)
|
89
103
|
diff-lcs (>= 1.2.0, < 2.0)
|
90
|
-
rspec-support (~> 3.
|
91
|
-
rspec-support (3.
|
92
|
-
rubocop (1.
|
104
|
+
rspec-support (~> 3.13.0)
|
105
|
+
rspec-support (3.13.1)
|
106
|
+
rubocop (1.69.0)
|
107
|
+
json (~> 2.3)
|
108
|
+
language_server-protocol (>= 3.17.0)
|
93
109
|
parallel (~> 1.10)
|
94
|
-
parser (>= 3.
|
110
|
+
parser (>= 3.3.0.2)
|
95
111
|
rainbow (>= 2.2.2, < 4.0)
|
96
|
-
regexp_parser (>=
|
97
|
-
|
98
|
-
rubocop-ast (>= 1.15.1, < 2.0)
|
112
|
+
regexp_parser (>= 2.4, < 3.0)
|
113
|
+
rubocop-ast (>= 1.36.1, < 2.0)
|
99
114
|
ruby-progressbar (~> 1.7)
|
100
|
-
unicode-display_width (>=
|
101
|
-
rubocop-ast (1.
|
102
|
-
parser (>= 3.
|
103
|
-
rubocop-
|
104
|
-
rubocop (
|
105
|
-
|
106
|
-
|
115
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
116
|
+
rubocop-ast (1.36.1)
|
117
|
+
parser (>= 3.3.1.0)
|
118
|
+
rubocop-capybara (2.21.0)
|
119
|
+
rubocop (~> 1.41)
|
120
|
+
rubocop-factory_bot (2.26.1)
|
121
|
+
rubocop (~> 1.61)
|
122
|
+
rubocop-performance (1.23.0)
|
123
|
+
rubocop (>= 1.48.1, < 2.0)
|
124
|
+
rubocop-ast (>= 1.31.1, < 2.0)
|
125
|
+
rubocop-rails (2.27.0)
|
107
126
|
activesupport (>= 4.2.0)
|
108
127
|
rack (>= 1.1)
|
109
|
-
rubocop (>= 1.
|
110
|
-
|
111
|
-
|
112
|
-
|
128
|
+
rubocop (>= 1.52.0, < 2.0)
|
129
|
+
rubocop-ast (>= 1.31.1, < 2.0)
|
130
|
+
rubocop-rspec (3.2.0)
|
131
|
+
rubocop (~> 1.61)
|
132
|
+
rubocop-rspec_rails (2.30.0)
|
133
|
+
rubocop (~> 1.61)
|
134
|
+
rubocop-rspec (~> 3, >= 3.0.1)
|
135
|
+
ruby-progressbar (1.13.0)
|
113
136
|
rumoji (0.5.0)
|
114
|
-
|
115
|
-
|
137
|
+
securerandom (0.3.2)
|
138
|
+
sqlite3 (1.7.3-arm64-darwin)
|
139
|
+
sqlite3 (1.7.3-x86_64-darwin)
|
140
|
+
sqlite3 (1.7.3-x86_64-linux)
|
141
|
+
timeout (0.4.2)
|
142
|
+
tzinfo (2.0.6)
|
116
143
|
concurrent-ruby (~> 1.0)
|
117
|
-
unicode-display_width (
|
118
|
-
|
144
|
+
unicode-display_width (3.1.2)
|
145
|
+
unicode-emoji (~> 4.0, >= 4.0.4)
|
146
|
+
unicode-emoji (4.0.4)
|
147
|
+
zeitwerk (2.6.18)
|
119
148
|
|
120
149
|
PLATFORMS
|
121
150
|
arm64-darwin-21
|
151
|
+
arm64-darwin-23
|
122
152
|
x86_64-darwin-21
|
123
153
|
x86_64-linux
|
124
154
|
|
data/README.md
CHANGED
@@ -1,39 +1,55 @@
|
|
1
|
-
# Enum
|
1
|
+
# Enum Machine
|
2
2
|
|
3
|
-
Enum
|
3
|
+
`Enum Machine` is a library for defining enums and setting state machines for attributes in ActiveRecord models and plain Ruby classes.
|
4
4
|
|
5
|
-
You can visualize
|
5
|
+
You can visualize transitions map with [enum_machine-contrib](https://github.com/corp-gp/enum_machine-contrib)
|
6
6
|
|
7
|
-
## Why
|
7
|
+
## Why is `enum_machine` better then [state_machines](https://github.com/state-machines/state_machines) / [aasm](https://github.com/aasm/aasm)?
|
8
8
|
|
9
|
-
|
9
|
+
- faster [5x](#Benchmarks)
|
10
|
+
- code lines: `enum_machine` - 348, `AASM` - 2139
|
11
|
+
- namespaced (via attr) by default: `order.state.to_collected`
|
12
|
+
- [aliases](Aliases)
|
13
|
+
- guarantees of existing transitions
|
14
|
+
- simple run transitions with callbacks `order.update(state: "collected")` or `order.state.to_collected`
|
15
|
+
- `aasm` / `state_machines` **event driven**, `enum_machine` **state driven**
|
10
16
|
|
11
|
-
|
17
|
+
```ruby
|
18
|
+
# aasm
|
19
|
+
event :complete do # complete/collected - dichotomy between states and events
|
20
|
+
before { puts "event complete" }
|
21
|
+
transitions from: :collecting, to: :collected
|
22
|
+
end
|
12
23
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
24
|
+
# pay/archived difficult to remember the relationship between statuses and events
|
25
|
+
# try to explain this to the logic of business stakeholders
|
26
|
+
event :pay do
|
27
|
+
transitions from: [:created, :collected], to: :archived
|
28
|
+
end
|
29
|
+
|
30
|
+
order = Order.create(state: "collecting")
|
31
|
+
order.update(state: "archived") # not check transitions, invalid logic
|
32
|
+
order.update(state: "collected") # not run callbacks
|
33
|
+
order.complete # need use event for transition, but your object in UI and DB have only states
|
34
|
+
|
35
|
+
# enum_machine
|
36
|
+
transitions( # simple readable transitions map
|
37
|
+
"collecting" => "collected",
|
38
|
+
"collected" => "archived",
|
39
|
+
)
|
40
|
+
before_transition("collecting" => "collected") { puts "event complete" }
|
41
|
+
|
42
|
+
order = Order.create(state: "collecting")
|
43
|
+
order.update(state: "archived") # checked transitions, raise exception
|
44
|
+
order.update(state: "collected") # run callbacks
|
45
|
+
```
|
30
46
|
|
31
47
|
## Installation
|
32
48
|
|
33
49
|
Add to your Gemfile:
|
34
50
|
|
35
51
|
```ruby
|
36
|
-
gem
|
52
|
+
gem "enum_machine"
|
37
53
|
```
|
38
54
|
|
39
55
|
## Usage
|
@@ -43,22 +59,31 @@ gem 'enum_machine'
|
|
43
59
|
```ruby
|
44
60
|
# With ActiveRecord
|
45
61
|
class Product < ActiveRecord::Base
|
46
|
-
enum_machine :color, %w
|
62
|
+
enum_machine :color, %w[red green]
|
47
63
|
end
|
48
64
|
|
49
65
|
# Or with plain class
|
50
66
|
class Product
|
67
|
+
# attributes must be defined before including the EnumMachine module
|
68
|
+
attr_accessor :color
|
69
|
+
|
51
70
|
include EnumMachine[color: { enum: %w[red green] }]
|
71
|
+
# or reuse from model
|
72
|
+
Product::COLOR.enum_decorator
|
52
73
|
end
|
53
74
|
|
54
75
|
Product::COLOR.values # => ["red", "green"]
|
55
76
|
Product::COLOR::RED # => "red"
|
56
77
|
Product::COLOR::RED__GREEN # => ["red", "green"]
|
57
78
|
|
79
|
+
Product::COLOR["red"].red? # => true
|
80
|
+
Product::COLOR["red"].human_name # => "Красный"
|
81
|
+
|
58
82
|
product = Product.new
|
59
83
|
product.color # => nil
|
60
|
-
product.color =
|
84
|
+
product.color = "red"
|
61
85
|
product.color.red? # => true
|
86
|
+
product.color.human_name # => "Красный"
|
62
87
|
```
|
63
88
|
|
64
89
|
### Aliases
|
@@ -67,50 +92,99 @@ product.color.red? # => true
|
|
67
92
|
class Product < ActiveRecord::Base
|
68
93
|
enum_machine :state, %w[created approved published] do
|
69
94
|
aliases(
|
70
|
-
|
95
|
+
"forming" => %w[created approved],
|
71
96
|
)
|
97
|
+
end
|
72
98
|
end
|
73
99
|
|
74
100
|
Product::STATE.forming # => %w[created approved]
|
75
101
|
|
76
|
-
product = Product.new(state:
|
102
|
+
product = Product.new(state: "created")
|
77
103
|
product.state.forming? # => true
|
78
104
|
```
|
79
105
|
|
106
|
+
### Value decorator
|
107
|
+
|
108
|
+
You can extend value object with decorator
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
# Value classes nested from base class
|
112
|
+
module ColorDecorator
|
113
|
+
def hex
|
114
|
+
case self
|
115
|
+
when Product::COLOR::RED then "#ff0000"
|
116
|
+
when Product::COLOR::GREEN then "#00ff00"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class Product
|
122
|
+
attr_accessor :color
|
123
|
+
|
124
|
+
include EnumMachine[color: {
|
125
|
+
enum: %w[red green],
|
126
|
+
value_decorator: ColorDecorator
|
127
|
+
}]
|
128
|
+
end
|
129
|
+
|
130
|
+
product = Product.new
|
131
|
+
product.color = "red"
|
132
|
+
product.color.hex # => "#ff0000"
|
133
|
+
```
|
134
|
+
|
80
135
|
### Transitions
|
81
136
|
|
82
137
|
```ruby
|
83
138
|
class Product < ActiveRecord::Base
|
84
139
|
enum_machine :color, %w[red green blue]
|
85
140
|
enum_machine :state, %w[created approved cancelled activated] do
|
141
|
+
# transitions(any => any) - allow all transitions
|
86
142
|
transitions(
|
87
|
-
nil =>
|
88
|
-
|
89
|
-
%w[cancelled approved] =>
|
90
|
-
|
143
|
+
nil => "created",
|
144
|
+
"created" => [nil, "approved"],
|
145
|
+
%w[cancelled approved] => "activated",
|
146
|
+
"activated" => %w[created cancelled],
|
91
147
|
)
|
92
148
|
|
93
149
|
# Will be executed in `before_save` callback
|
94
|
-
before_transition
|
95
|
-
product.color =
|
150
|
+
before_transition "created" => "approved" do |product|
|
151
|
+
product.color = "green" if product.color.red?
|
96
152
|
end
|
97
153
|
|
98
154
|
# Will be executed in `after_save` callback
|
99
155
|
after_transition %w[created] => %w[approved] do |product|
|
100
|
-
product.color =
|
156
|
+
product.color = "red"
|
101
157
|
end
|
102
158
|
|
103
|
-
after_transition any =>
|
159
|
+
after_transition any => "cancelled" do |product|
|
104
160
|
product.cancelled_at = Time.zone.now
|
105
161
|
end
|
106
162
|
end
|
107
163
|
end
|
108
164
|
|
109
|
-
product = Product.create(state:
|
165
|
+
product = Product.create(state: "created")
|
110
166
|
product.state.possible_transitions # => [nil, "approved"]
|
111
167
|
product.state.can_activated? # => false
|
112
168
|
product.state.to_activated! # => EnumMachine::Error: transition "created" => "activated" not defined in enum_machine
|
113
|
-
product.state.to_approved! # => true; equal to `product.update!(state:
|
169
|
+
product.state.to_approved! # => true; equal to `product.update!(state: "approve")`
|
170
|
+
```
|
171
|
+
|
172
|
+
#### Skip transitions
|
173
|
+
```ruby
|
174
|
+
product = Product.new(state: "created")
|
175
|
+
product.skip_state_transitions { product.save }
|
176
|
+
```
|
177
|
+
|
178
|
+
method generated as `skip_#{enum_name}_transitions`
|
179
|
+
|
180
|
+
#### Skip in factories
|
181
|
+
```ruby
|
182
|
+
FactoryBot.define do
|
183
|
+
factory :product do
|
184
|
+
name { Faker::Commerce.product_name }
|
185
|
+
to_create { |product| product.skip_state_transitions { product.save! } }
|
186
|
+
end
|
187
|
+
end
|
114
188
|
```
|
115
189
|
|
116
190
|
### I18n
|
@@ -128,20 +202,22 @@ ru:
|
|
128
202
|
```ruby
|
129
203
|
# ActiveRecord
|
130
204
|
class Product < ActiveRecord::Base
|
131
|
-
enum_machine :color, %w
|
205
|
+
enum_machine :color, %w[red green]
|
132
206
|
end
|
133
207
|
|
134
208
|
# Plain class
|
135
209
|
class Product
|
210
|
+
# attributes must be defined before including the EnumMachine module
|
211
|
+
attr_accessor :color
|
136
212
|
# `i18n_scope` option must be explicitly set to use methods below
|
137
|
-
include EnumMachine[color: { enum: %w[red green], i18n_scope:
|
213
|
+
include EnumMachine[color: { enum: %w[red green], i18n_scope: "product" }]
|
138
214
|
end
|
139
215
|
|
140
|
-
Product::COLOR.human_name_for(
|
216
|
+
Product::COLOR.human_name_for("red") # => "Красный"
|
141
217
|
Product::COLOR.values_for_form # => [["Красный", "red"], ["Зеленый", "green"]]
|
142
218
|
|
143
|
-
product = Product.new(color:
|
144
|
-
product.color.human_name # =>
|
219
|
+
product = Product.new(color: "red")
|
220
|
+
product.color.human_name # => "Красный"
|
145
221
|
```
|
146
222
|
|
147
223
|
I18n scope can be changed with `i18n_scope` option:
|
@@ -149,25 +225,36 @@ I18n scope can be changed with `i18n_scope` option:
|
|
149
225
|
```ruby
|
150
226
|
# For AciveRecord
|
151
227
|
class Product < ActiveRecord::Base
|
152
|
-
enum_machine :color, %w
|
228
|
+
enum_machine :color, %w[red green], i18n_scope: "users.product"
|
153
229
|
end
|
154
230
|
|
155
231
|
# For plain class
|
156
232
|
class Product
|
157
|
-
include EnumMachine[color: { enum: %w[red green], i18n_scope:
|
233
|
+
include EnumMachine[color: { enum: %w[red green], i18n_scope: "users.product" }]
|
158
234
|
end
|
159
235
|
```
|
160
236
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
237
|
+
## Benchmarks
|
238
|
+
[test/performance.rb](../master/test/performance.rb)
|
239
|
+
|
240
|
+
| Gem | Method | |
|
241
|
+
| :--- | ---: | :--- |
|
242
|
+
| enum_machine | order.state.forming? | 894921.3 i/s |
|
243
|
+
| state_machines | order.forming? | 189901.8 i/s - 4.71x slower |
|
244
|
+
| aasm | order.forming? | 127073.7 i/s - 7.04x slower |
|
245
|
+
| | | |
|
246
|
+
| enum_machine | order.state.can_closed? | 473150.4 i/s |
|
247
|
+
| aasm | order.may_to_closed? | 24459.1 i/s - 19.34x slower |
|
248
|
+
| state_machines | order.can_to_closed? | 12136.8 i/s - 38.98x slower |
|
249
|
+
| | | |
|
250
|
+
| enum_machine | Order::STATE.values | 6353820.4 i/s |
|
251
|
+
| aasm | Order.aasm(:state).states.map(&:name) | 131390.5 i/s - 48.36x slower |
|
252
|
+
| state_machines | Order.state_machines[:state].states.map(&:value) | 108449.7 i/s - 58.59x slower |
|
253
|
+
| | | |
|
254
|
+
| enum_machine | order.state = "forming" and order.valid? | 13873.4 i/s |
|
255
|
+
| state_machines | order.state_event = "to_forming" and order.valid? | 6173.6 i/s - 2.25x slower |
|
256
|
+
| aasm | order.to_forming | 3095.9 i/s - 4.48x slower |
|
257
|
+
|
171
258
|
|
172
259
|
## License
|
173
260
|
|
data/Rakefile
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rspec/core/rake_task"
|
5
5
|
|
6
6
|
RSpec::Core::RakeTask.new(:spec)
|
7
7
|
|
8
|
-
require
|
8
|
+
require "rubocop/rake_task"
|
9
9
|
|
10
10
|
RuboCop::RakeTask.new
|
11
11
|
|
@@ -2,16 +2,11 @@
|
|
2
2
|
|
3
3
|
module EnumMachine
|
4
4
|
module AttributePersistenceMethods
|
5
|
-
|
6
5
|
def self.[](attr, enum_values)
|
7
6
|
Module.new do
|
8
7
|
define_singleton_method(:extended) do |klass|
|
9
8
|
klass.attr_accessor :parent
|
10
9
|
|
11
|
-
klass.define_method(:inspect) do
|
12
|
-
"#<EnumMachine:BuildAttribute value=#{self} parent=#{parent.inspect}>"
|
13
|
-
end
|
14
|
-
|
15
10
|
enum_values.each do |enum_value|
|
16
11
|
enum_name = enum_value.underscore
|
17
12
|
|
@@ -28,6 +23,5 @@ module EnumMachine
|
|
28
23
|
end
|
29
24
|
end
|
30
25
|
end
|
31
|
-
|
32
26
|
end
|
33
27
|
end
|
@@ -1,14 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module EnumMachine
|
4
|
-
module
|
5
|
-
|
6
|
-
def self.call(enum_values:, i18n_scope:, machine: nil)
|
4
|
+
module BuildEnumClass
|
5
|
+
def self.call(enum_values:, i18n_scope:, value_class:, machine: nil)
|
7
6
|
aliases = machine&.instance_variable_get(:@aliases) || {}
|
8
7
|
|
9
8
|
Class.new do
|
9
|
+
const_set(:VALUE_CLASS, value_class)
|
10
|
+
|
10
11
|
define_singleton_method(:machine) { machine } if machine
|
11
|
-
define_singleton_method(:values) { enum_values }
|
12
|
+
define_singleton_method(:values) { enum_values.map { value_class.new(_1).freeze } }
|
13
|
+
|
14
|
+
value_attribute_mapping = values.to_h { [_1.to_s, _1] }
|
15
|
+
define_singleton_method(:value_attribute_mapping) { value_attribute_mapping }
|
16
|
+
define_singleton_method(:[]) do |enum_value|
|
17
|
+
key = enum_value.to_s
|
18
|
+
# Check for key existence because `[]` will call `default_proc`, and we don’t want that
|
19
|
+
value_attribute_mapping[key] if value_attribute_mapping.key?(key)
|
20
|
+
end
|
12
21
|
|
13
22
|
if i18n_scope
|
14
23
|
def self.values_for_form(specific_values = nil) # rubocop:disable Gp/OptArgParameters
|
@@ -27,7 +36,7 @@ module EnumMachine
|
|
27
36
|
end
|
28
37
|
|
29
38
|
enum_values.each do |enum_value|
|
30
|
-
const_set enum_value.underscore.upcase, enum_value.freeze
|
39
|
+
const_set enum_value.underscore.upcase, enum_value.to_s.freeze
|
31
40
|
end
|
32
41
|
|
33
42
|
aliases.each_key do |key|
|
@@ -44,12 +53,11 @@ module EnumMachine
|
|
44
53
|
|
45
54
|
private_class_method def self.const_missing(name)
|
46
55
|
name_s = name.to_s
|
47
|
-
return super unless name_s.include?(
|
56
|
+
return super unless name_s.include?("__")
|
48
57
|
|
49
|
-
const_set name_s, name_s.split(
|
58
|
+
const_set name_s, name_s.split("__").map { |i| const_get(i) }.freeze
|
50
59
|
end
|
51
60
|
end
|
52
61
|
end
|
53
|
-
|
54
62
|
end
|
55
63
|
end
|
@@ -1,16 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module EnumMachine
|
4
|
-
module
|
5
|
-
|
6
|
-
def self.call(enum_values:, i18n_scope:, machine: nil)
|
4
|
+
module BuildValueClass
|
5
|
+
def self.call(enum_values:, i18n_scope:, value_decorator:, machine: nil)
|
7
6
|
aliases = machine&.instance_variable_get(:@aliases) || {}
|
8
7
|
|
9
8
|
Class.new(String) do
|
9
|
+
include(value_decorator) if value_decorator
|
10
|
+
|
10
11
|
define_method(:machine) { machine } if machine
|
11
12
|
|
12
13
|
def inspect
|
13
|
-
"#<EnumMachine
|
14
|
+
"#<EnumMachine \"#{self}\">"
|
14
15
|
end
|
15
16
|
|
16
17
|
if machine&.transitions?
|
@@ -74,6 +75,5 @@ module EnumMachine
|
|
74
75
|
end
|
75
76
|
end
|
76
77
|
end
|
77
|
-
|
78
78
|
end
|
79
79
|
end
|
@@ -2,26 +2,25 @@
|
|
2
2
|
|
3
3
|
module EnumMachine
|
4
4
|
module DriverActiveRecord
|
5
|
-
|
6
|
-
def enum_machine(attr, enum_values, i18n_scope: nil, &block)
|
5
|
+
def enum_machine(attr, enum_values, i18n_scope: nil, value_decorator: nil, &block)
|
7
6
|
klass = self
|
8
7
|
|
9
8
|
i18n_scope ||= "#{klass.base_class.to_s.underscore}.#{attr}"
|
10
9
|
|
11
10
|
enum_const_name = attr.to_s.upcase
|
12
|
-
machine = Machine.new(enum_values, klass, enum_const_name)
|
11
|
+
machine = Machine.new(enum_values, klass, enum_const_name, attr)
|
13
12
|
machine.instance_eval(&block) if block
|
14
13
|
|
15
|
-
|
16
|
-
|
17
|
-
enum_value_klass = BuildAttribute.call(enum_values: enum_values, i18n_scope: i18n_scope, machine: machine)
|
18
|
-
enum_value_klass.extend(AttributePersistenceMethods[attr, enum_values])
|
14
|
+
value_class = BuildValueClass.call(enum_values: enum_values, i18n_scope: i18n_scope, machine: machine, value_decorator: value_decorator)
|
15
|
+
enum_class = BuildEnumClass.call(enum_values: enum_values, i18n_scope: i18n_scope, machine: machine, value_class: value_class)
|
19
16
|
|
20
|
-
|
17
|
+
value_class.extend(AttributePersistenceMethods[attr, enum_values])
|
21
18
|
|
22
|
-
#
|
23
|
-
value_attribute_mapping
|
24
|
-
|
19
|
+
# default_proc for working with custom values not defined in enum list but may exists in db
|
20
|
+
enum_class.value_attribute_mapping.default_proc =
|
21
|
+
proc do |hash, enum_value|
|
22
|
+
hash[enum_value] = value_class.new(enum_value).freeze
|
23
|
+
end
|
25
24
|
|
26
25
|
if machine.transitions?
|
27
26
|
klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1 # rubocop:disable Style/DocumentDynamicEvalDefinition
|
@@ -103,17 +102,16 @@ module EnumMachine
|
|
103
102
|
|
104
103
|
enum_decorator =
|
105
104
|
Module.new do
|
106
|
-
define_singleton_method(:included) do |
|
107
|
-
|
108
|
-
|
105
|
+
define_singleton_method(:included) do |decorating_class|
|
106
|
+
decorating_class.prepend define_methods
|
107
|
+
decorating_class.const_set enum_const_name, enum_class
|
109
108
|
end
|
110
109
|
end
|
111
|
-
|
110
|
+
enum_class.define_singleton_method(:enum_decorator) { enum_decorator }
|
112
111
|
|
113
112
|
klass.include(enum_decorator)
|
114
113
|
|
115
114
|
enum_decorator
|
116
115
|
end
|
117
|
-
|
118
116
|
end
|
119
117
|
end
|
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
module EnumMachine
|
4
4
|
module DriverSimpleClass
|
5
|
-
|
6
5
|
# include EnumMachine[
|
7
6
|
# state: { enum: %w[choice in_delivery], i18n_scope: 'line_item.state' },
|
8
7
|
# color: { enum: %w[red green yellow] },
|
@@ -12,19 +11,16 @@ module EnumMachine
|
|
12
11
|
Module.new do
|
13
12
|
define_singleton_method(:included) do |klass|
|
14
13
|
args.each do |attr, params|
|
15
|
-
enum_values
|
16
|
-
i18n_scope
|
14
|
+
enum_values = params.fetch(:enum)
|
15
|
+
i18n_scope = params.fetch(:i18n_scope, nil)
|
16
|
+
value_decorator = params.fetch(:value_decorator, nil)
|
17
17
|
|
18
18
|
if defined?(ActiveRecord) && klass <= ActiveRecord::Base
|
19
19
|
klass.enum_machine(attr, enum_values, i18n_scope: i18n_scope)
|
20
20
|
else
|
21
21
|
enum_const_name = attr.to_s.upcase
|
22
|
-
|
23
|
-
|
24
|
-
enum_value_klass = BuildAttribute.call(enum_values: enum_values, i18n_scope: i18n_scope)
|
25
|
-
enum_klass.const_set :VALUE_KLASS, enum_value_klass
|
26
|
-
|
27
|
-
value_attribute_mapping = enum_values.to_h { |enum_value| [enum_value, enum_klass::VALUE_KLASS.new(enum_value).freeze] }
|
22
|
+
value_class = BuildValueClass.call(enum_values: enum_values, i18n_scope: i18n_scope, value_decorator: value_decorator)
|
23
|
+
enum_class = BuildEnumClass.call(enum_values: enum_values, i18n_scope: i18n_scope, value_class: value_class)
|
28
24
|
|
29
25
|
define_methods =
|
30
26
|
Module.new do
|
@@ -32,18 +28,18 @@ module EnumMachine
|
|
32
28
|
enum_value = super()
|
33
29
|
return unless enum_value
|
34
30
|
|
35
|
-
value_attribute_mapping.fetch(enum_value)
|
31
|
+
enum_class.value_attribute_mapping.fetch(enum_value)
|
36
32
|
end
|
37
33
|
end
|
38
34
|
|
39
35
|
enum_decorator =
|
40
36
|
Module.new do
|
41
|
-
define_singleton_method(:included) do |
|
42
|
-
|
43
|
-
|
37
|
+
define_singleton_method(:included) do |decorating_class|
|
38
|
+
decorating_class.prepend define_methods
|
39
|
+
decorating_class.const_set enum_const_name, enum_class
|
44
40
|
end
|
45
41
|
end
|
46
|
-
|
42
|
+
enum_class.define_singleton_method(:enum_decorator) { enum_decorator }
|
47
43
|
|
48
44
|
klass.include(enum_decorator)
|
49
45
|
enum_decorator
|
@@ -52,6 +48,5 @@ module EnumMachine
|
|
52
48
|
end
|
53
49
|
end
|
54
50
|
end
|
55
|
-
|
56
51
|
end
|
57
52
|
end
|
data/lib/enum_machine/machine.rb
CHANGED
@@ -2,13 +2,13 @@
|
|
2
2
|
|
3
3
|
module EnumMachine
|
4
4
|
class Machine
|
5
|
+
attr_reader :enum_values, :base_klass, :enum_const_name, :attr_name
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
def initialize(enum_values, base_klass = nil, enum_const_name = nil) # rubocop:disable Gp/OptArgParameters
|
7
|
+
def initialize(enum_values, base_klass = nil, enum_const_name = nil, attr_name = nil) # rubocop:disable Gp/OptArgParameters
|
9
8
|
@enum_values = enum_values
|
10
9
|
@base_klass = base_klass
|
11
10
|
@enum_const_name = enum_const_name
|
11
|
+
@attr_name = attr_name
|
12
12
|
@transitions = {}
|
13
13
|
@before_transition = {}
|
14
14
|
@after_transition = {}
|
@@ -131,8 +131,6 @@ module EnumMachine
|
|
131
131
|
end
|
132
132
|
|
133
133
|
class AnyEnumValues < Array
|
134
|
-
|
135
134
|
end
|
136
|
-
|
137
135
|
end
|
138
136
|
end
|
data/lib/enum_machine/version.rb
CHANGED
data/lib/enum_machine.rb
CHANGED
@@ -1,37 +1,37 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
4
|
-
require_relative
|
5
|
-
require_relative
|
6
|
-
require_relative
|
7
|
-
require_relative
|
8
|
-
require_relative
|
9
|
-
require
|
3
|
+
require_relative "enum_machine/version"
|
4
|
+
require_relative "enum_machine/driver_simple_class"
|
5
|
+
require_relative "enum_machine/build_value_class"
|
6
|
+
require_relative "enum_machine/attribute_persistence_methods"
|
7
|
+
require_relative "enum_machine/build_enum_class"
|
8
|
+
require_relative "enum_machine/machine"
|
9
|
+
require "active_support"
|
10
10
|
|
11
11
|
module EnumMachine
|
12
|
-
|
13
12
|
class Error < StandardError; end
|
14
13
|
|
15
14
|
class InvalidTransition < Error
|
16
|
-
|
17
15
|
attr_reader :from, :to, :enum_const
|
18
16
|
|
19
17
|
def initialize(machine, from, to)
|
20
18
|
@from = from
|
21
19
|
@to = to
|
22
|
-
@enum_const =
|
23
|
-
|
20
|
+
@enum_const =
|
21
|
+
begin
|
22
|
+
machine.base_klass.const_get(machine.enum_const_name)
|
23
|
+
rescue NameError # rubocop:disable Lint/SuppressedException
|
24
|
+
end
|
25
|
+
super("Transition #{from.inspect} => #{to.inspect} not defined in enum_machine :#{machine.attr_name}")
|
24
26
|
end
|
25
|
-
|
26
27
|
end
|
27
28
|
|
28
29
|
def self.[](args)
|
29
30
|
DriverSimpleClass.call(args)
|
30
31
|
end
|
31
|
-
|
32
32
|
end
|
33
33
|
|
34
34
|
ActiveSupport.on_load(:active_record) do
|
35
|
-
require_relative
|
36
|
-
|
35
|
+
require_relative "enum_machine/driver_active_record"
|
36
|
+
ActiveSupport.on_load(:active_record) { extend EnumMachine::DriverActiveRecord }
|
37
37
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: enum_machine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ermolaev Andrey
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-12-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -72,8 +72,8 @@ files:
|
|
72
72
|
- docs/MIGRATING_FROM_RAILS_STRING_ENUM.ru.md
|
73
73
|
- lib/enum_machine.rb
|
74
74
|
- lib/enum_machine/attribute_persistence_methods.rb
|
75
|
-
- lib/enum_machine/
|
76
|
-
- lib/enum_machine/
|
75
|
+
- lib/enum_machine/build_enum_class.rb
|
76
|
+
- lib/enum_machine/build_value_class.rb
|
77
77
|
- lib/enum_machine/driver_active_record.rb
|
78
78
|
- lib/enum_machine/driver_simple_class.rb
|
79
79
|
- lib/enum_machine/machine.rb
|
@@ -86,7 +86,7 @@ metadata:
|
|
86
86
|
homepage_uri: https://github.com/corp-gp/enum_machine
|
87
87
|
source_code_uri: https://github.com/corp-gp/enum_machine
|
88
88
|
rubygems_mfa_required: 'true'
|
89
|
-
post_install_message:
|
89
|
+
post_install_message:
|
90
90
|
rdoc_options: []
|
91
91
|
require_paths:
|
92
92
|
- lib
|
@@ -101,8 +101,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
101
101
|
- !ruby/object:Gem::Version
|
102
102
|
version: '0'
|
103
103
|
requirements: []
|
104
|
-
rubygems_version: 3.
|
105
|
-
signing_key:
|
104
|
+
rubygems_version: 3.3.17
|
105
|
+
signing_key:
|
106
106
|
specification_version: 4
|
107
107
|
summary: fast and siple usage state machine in your app
|
108
108
|
test_files: []
|