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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 14dc3dff76ded6b1aea680c93860bdd7aad7edb2c11b65e4e0a741ebd9d71e92
4
- data.tar.gz: 5a30e39776ee6de5073e987863e55a768faf88f68cc8075e49dbf01fc335d908
3
+ metadata.gz: aee9e7e0ece2e73f55fdd9c7180c3babfc1b2bed804cb3475fb1867b0dba757d
4
+ data.tar.gz: a9b418f9678c7567baf43d84ac427249580fd4bc976b616677ecb52c89b082ec
5
5
  SHA512:
6
- metadata.gz: 516f79da30dc8ca2ac1aaa152a566fc30b681de1f0e4091001715a6598517d6630b274b6e215b4563a8afc411adf52731e76efa7aa948e7b34218631fcdb29e1
7
- data.tar.gz: 23b0ca783312d70257e4654550810585c3ccf7ce69014f729e11d719147116d3db7bbd6496bc0120f8d0362544d3078f353f29c64cb06a14e6f98539f6167442
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 'https://rubygems.org'
3
+ source "https://rubygems.org"
4
4
 
5
5
  gemspec
6
6
 
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'
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: 4d390b6cdd2b27f151fe0535bd266525fbf8f25d
11
+ revision: 867f7e1351c3730897cacdc20ce2d727604de245
12
12
  specs:
13
- rubocop-gp (0.0.2)
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 (1.0.0)
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 (6.1.4.6)
31
- activesupport (= 6.1.4.6)
32
- activerecord (6.1.4.6)
33
- activemodel (= 6.1.4.6)
34
- activesupport (= 6.1.4.6)
35
- activesupport (6.1.4.6)
36
- concurrent-ruby (~> 1.0, >= 1.0.2)
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
- tzinfo (~> 2.0)
40
- zeitwerk (~> 2.3)
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.1.9)
45
- diff-lcs (1.5.0)
46
- dry-configurable (0.14.0)
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
- dry-core (~> 0.6)
49
- dry-container (0.9.0)
61
+ zeitwerk (~> 2.6)
62
+ dry-inflector (1.1.0)
63
+ dry-logic (1.5.0)
50
64
  concurrent-ruby (~> 1.0)
51
- dry-configurable (~> 0.13, >= 0.13.0)
52
- dry-core (0.7.1)
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
- dry-inflector (0.2.1)
55
- dry-logic (1.2.0)
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
- dry-core (~> 0.5, >= 0.5)
58
- dry-types (1.5.1)
59
- concurrent-ruby (~> 1.0)
60
- dry-container (~> 0.3)
61
- dry-core (~> 0.5, >= 0.5)
62
- dry-inflector (~> 0.1, >= 0.1.2)
63
- dry-logic (~> 1.0, >= 1.0.2)
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
- pry (0.14.1)
84
+ racc
85
+ pry (0.14.2)
72
86
  coderay (~> 1.1)
73
87
  method_source (~> 1.0)
74
- rack (2.2.3)
88
+ racc (1.8.1)
89
+ rack (3.1.8)
75
90
  rainbow (3.1.1)
76
- rake (13.0.6)
77
- regexp_parser (2.2.1)
78
- rexml (3.2.5)
79
- rspec (3.11.0)
80
- rspec-core (~> 3.11.0)
81
- rspec-expectations (~> 3.11.0)
82
- rspec-mocks (~> 3.11.0)
83
- rspec-core (3.11.0)
84
- rspec-support (~> 3.11.0)
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.11.0)
88
- rspec-mocks (3.11.0)
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.11.0)
91
- rspec-support (3.11.0)
92
- rubocop (1.25.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.1.0.0)
110
+ parser (>= 3.3.0.2)
95
111
  rainbow (>= 2.2.2, < 4.0)
96
- regexp_parser (>= 1.8, < 3.0)
97
- rexml
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 (>= 1.4.0, < 3.0)
101
- rubocop-ast (1.15.1)
102
- parser (>= 3.0.1.1)
103
- rubocop-performance (1.13.2)
104
- rubocop (>= 1.7.0, < 2.0)
105
- rubocop-ast (>= 0.4.0)
106
- rubocop-rails (2.13.2)
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.7.0, < 2.0)
110
- rubocop-rspec (2.8.0)
111
- rubocop (~> 1.19)
112
- ruby-progressbar (1.11.0)
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
- sqlite3 (1.4.2)
115
- tzinfo (2.0.4)
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 (2.1.0)
118
- zeitwerk (2.5.4)
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 machine
1
+ # Enum Machine
2
2
 
3
- Enum machine is a library for defining enums and setting state machines for attributes in ActiveRecord models and plain Ruby classes.
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 the state machine with [enum_machine-contrib](https://github.com/corp-gp/enum_machine-contrib)
5
+ You can visualize transitions map with [enum_machine-contrib](https://github.com/corp-gp/enum_machine-contrib)
6
6
 
7
- ## Why not state_machines/aasm?
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
- The [aasm](https://github.com/aasm/aasm) and [state_machines](https://github.com/state-machines/state_machines) gems suggest calling special methods to change the `state`. In practice, this can lead to errors. In enum_machine, the `state` is changed by updating the corresponding field, and the validation of the ability to change from one state to another is done in the `after_validation` callback. This allows the `state` of a model to be changed consistently with the usual `save!`. In addition aasm/state_machines add many autogenerated methods to the model class and instances. This makes it much more difficult to search by the project. Pollutes the method's space. Adds a bottleneck in method naming because you have to remember these methods in your code.
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
- Performance comparison (see [test/performance.rb](../master/test/performance.rb))
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
- | Gem | Method | |
14
- | :--- | ---: | :--- |
15
- | enum_machine | order.state.forming? | 894921.3 i/s |
16
- | state_machines | order.forming? | 189901.8 i/s - 4.71x slower |
17
- | aasm | order.forming? | 127073.7 i/s - 7.04x slower |
18
- | | | |
19
- | enum_machine | order.state.can_closed? | 473150.4 i/s |
20
- | aasm | order.may_to_closed? | 24459.1 i/s - 19.34x slower |
21
- | state_machines | order.can_to_closed? | 12136.8 i/s - 38.98x slower |
22
- | | | |
23
- | enum_machine | Order::STATE.values | 6353820.4 i/s |
24
- | aasm | Order.aasm(:state).states.map(&:name) | 131390.5 i/s - 48.36x slower |
25
- | state_machines | Order.state_machines[:state].states.map(&:value) | 108449.7 i/s - 58.59x slower |
26
- | | | |
27
- | enum_machine | order.state = "forming" and order.valid? | 13873.4 i/s |
28
- | state_machines | order.state_event = "to_forming" and order.valid? | 6173.6 i/s - 2.25x slower |
29
- | aasm | order.to_forming | 3095.9 i/s - 4.48x slower |
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 'enum_machine'
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(red green)
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 = 'red'
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
- 'forming' => %w[created approved],
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: 'created')
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 => 'red',
88
- 'created' => [nil, 'approved'],
89
- %w[cancelled approved] => 'activated',
90
- 'activated' => %w[created cancelled],
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 'created' => 'approved' do |product|
95
- product.color = 'green' if product.color.red?
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 = 'red'
156
+ product.color = "red"
101
157
  end
102
158
 
103
- after_transition any => 'cancelled' do |product|
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: 'created')
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: 'approved')`
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(red green)
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: 'product' }]
213
+ include EnumMachine[color: { enum: %w[red green], i18n_scope: "product" }]
138
214
  end
139
215
 
140
- Product::COLOR.human_name_for('red') # => 'Красный'
216
+ Product::COLOR.human_name_for("red") # => "Красный"
141
217
  Product::COLOR.values_for_form # => [["Красный", "red"], ["Зеленый", "green"]]
142
218
 
143
- product = Product.new(color: 'red')
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(red green), i18n_scope: 'users.product'
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: 'users.product' }]
233
+ include EnumMachine[color: { enum: %w[red green], i18n_scope: "users.product" }]
158
234
  end
159
235
  ```
160
236
 
161
- **ru.yml**
162
- ```yml
163
- ru:
164
- enums:
165
- users:
166
- product:
167
- color:
168
- red: Красный
169
- green: Зеленый
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 'bundler/gem_tasks'
4
- require 'rspec/core/rake_task'
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
5
 
6
6
  RSpec::Core::RakeTask.new(:spec)
7
7
 
8
- require 'rubocop/rake_task'
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 BuildClass
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('__').map { |i| const_get(i) }.freeze
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 BuildAttribute
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:BuildAttribute value=#{self}>"
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
- enum_klass = BuildClass.call(enum_values: enum_values, i18n_scope: i18n_scope, machine: machine)
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
- enum_klass.const_set :VALUE_KLASS, enum_value_klass
17
+ value_class.extend(AttributePersistenceMethods[attr, enum_values])
21
18
 
22
- # Hash.new with default_proc for working with custom values not defined in enum list
23
- value_attribute_mapping = Hash.new { |hash, enum_value| hash[enum_value] = enum_klass::VALUE_KLASS.new(enum_value).freeze }
24
- enum_klass.define_singleton_method(:value_attribute_mapping) { value_attribute_mapping }
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 |decorating_klass|
107
- decorating_klass.prepend define_methods
108
- decorating_klass.const_set enum_const_name, enum_klass
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
- enum_klass.define_singleton_method(:decorator_module) { enum_decorator }
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 = params.fetch(:enum)
16
- i18n_scope = params.fetch(:i18n_scope, nil)
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
- enum_klass = BuildClass.call(enum_values: enum_values, i18n_scope: i18n_scope)
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 |decorating_klass|
42
- decorating_klass.prepend define_methods
43
- decorating_klass.const_set enum_const_name, enum_klass
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
- enum_klass.define_singleton_method(:decorator_module) { enum_decorator }
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
@@ -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
- attr_reader :enum_values, :base_klass, :enum_const_name
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
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EnumMachine
4
-
5
- VERSION = '1.0.0'
6
-
4
+ VERSION = "2.0.1"
7
5
  end
data/lib/enum_machine.rb CHANGED
@@ -1,37 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'enum_machine/version'
4
- require_relative 'enum_machine/driver_simple_class'
5
- require_relative 'enum_machine/build_attribute'
6
- require_relative 'enum_machine/attribute_persistence_methods'
7
- require_relative 'enum_machine/build_class'
8
- require_relative 'enum_machine/machine'
9
- require 'active_support'
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 = machine.base_klass.const_get(machine.enum_const_name)
23
- super "Transition #{from.inspect} => #{to.inspect} not defined in enum_machine #{enum_const.name}"
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 'enum_machine/driver_active_record'
36
- ActiveRecord::Base.extend(EnumMachine::DriverActiveRecord)
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: 1.0.0
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-09-28 00:00:00.000000000 Z
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/build_attribute.rb
76
- - lib/enum_machine/build_class.rb
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.1.6
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: []