ruby-enum 1.1.0 → 1.2.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/CHANGELOG.md +6 -0
- data/Gemfile.lock +1 -1
- data/README.md +35 -2
- data/RELEASING.md +14 -5
- data/Rakefile +10 -0
- data/UPGRADING.md +39 -0
- data/benchmarks/basic.rb +61 -0
- data/benchmarks/inheritance.rb +53 -0
- data/lib/ruby-enum/enum.rb +64 -27
- data/lib/ruby-enum/version.rb +1 -1
- data/pkg/ruby-enum-1.1.0.gem +0 -0
- data/spec/ruby-enum/enum_spec.rb +115 -0
- data/spec_i18n/Gemfile.lock +45 -0
- metadata +6 -61
- data/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_asc.png +0 -0
- data/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_asc_disabled.png +0 -0
- data/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_both.png +0 -0
- data/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_desc.png +0 -0
- data/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_desc_disabled.png +0 -0
- data/coverage/assets/0.12.3/application.css +0 -1
- data/coverage/assets/0.12.3/application.js +0 -7
- data/coverage/assets/0.12.3/colorbox/border.png +0 -0
- data/coverage/assets/0.12.3/colorbox/controls.png +0 -0
- data/coverage/assets/0.12.3/colorbox/loading.gif +0 -0
- data/coverage/assets/0.12.3/colorbox/loading_background.png +0 -0
- data/coverage/assets/0.12.3/favicon_green.png +0 -0
- data/coverage/assets/0.12.3/favicon_red.png +0 -0
- data/coverage/assets/0.12.3/favicon_yellow.png +0 -0
- data/coverage/assets/0.12.3/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/coverage/assets/0.12.3/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/coverage/assets/0.12.3/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/coverage/assets/0.12.3/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/coverage/assets/0.12.3/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/coverage/assets/0.12.3/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/coverage/assets/0.12.3/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/coverage/assets/0.12.3/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/coverage/assets/0.12.3/images/ui-icons_222222_256x240.png +0 -0
- data/coverage/assets/0.12.3/images/ui-icons_2e83ff_256x240.png +0 -0
- data/coverage/assets/0.12.3/images/ui-icons_454545_256x240.png +0 -0
- data/coverage/assets/0.12.3/images/ui-icons_888888_256x240.png +0 -0
- data/coverage/assets/0.12.3/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/coverage/assets/0.12.3/loading.gif +0 -0
- data/coverage/assets/0.12.3/magnify.png +0 -0
- data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_asc.png +0 -0
- data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_asc_disabled.png +0 -0
- data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_both.png +0 -0
- data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_desc.png +0 -0
- data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_desc_disabled.png +0 -0
- data/coverage/assets/0.13.2/application.css +0 -1
- data/coverage/assets/0.13.2/application.js +0 -7
- data/coverage/assets/0.13.2/colorbox/border.png +0 -0
- data/coverage/assets/0.13.2/colorbox/controls.png +0 -0
- data/coverage/assets/0.13.2/colorbox/loading.gif +0 -0
- data/coverage/assets/0.13.2/colorbox/loading_background.png +0 -0
- data/coverage/assets/0.13.2/favicon_green.png +0 -0
- data/coverage/assets/0.13.2/favicon_red.png +0 -0
- data/coverage/assets/0.13.2/favicon_yellow.png +0 -0
- data/coverage/assets/0.13.2/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/coverage/assets/0.13.2/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/coverage/assets/0.13.2/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/coverage/assets/0.13.2/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/coverage/assets/0.13.2/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/coverage/assets/0.13.2/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/coverage/assets/0.13.2/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/coverage/assets/0.13.2/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/coverage/assets/0.13.2/images/ui-icons_222222_256x240.png +0 -0
- data/coverage/assets/0.13.2/images/ui-icons_2e83ff_256x240.png +0 -0
- data/coverage/assets/0.13.2/images/ui-icons_454545_256x240.png +0 -0
- data/coverage/assets/0.13.2/images/ui-icons_888888_256x240.png +0 -0
- data/coverage/assets/0.13.2/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/coverage/assets/0.13.2/loading.gif +0 -0
- data/coverage/assets/0.13.2/magnify.png +0 -0
- data/coverage/index.html +0 -8636
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0424fcb26d8fc260165a669fa7be1d83e41a3a8e967ad606c5eb72abb656b349
|
|
4
|
+
data.tar.gz: 2ea8e9dc094a9612447679c7d5bebf88ac466b1c1b9a5f666d3ce5a8d0981965
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 024aac1db4f01d3528a1130817e3db22e7e413d598e99d5dc333a040057896129420193731cec1706922a5c462322809e4e6ce5de2d871f1d2492e67990b56ab
|
|
7
|
+
data.tar.gz: c8a5b49346df275afd18502410a9e24a5806fdfdb0d31d2a7a53783edf8b592b3fe27789ca19d26f32d3937142f40c561b2a9d87ef5cff66e9aa7d05c3054cf6
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
### 1.2.0 (2026/8/14)
|
|
2
|
+
|
|
3
|
+
* [#49](https://github.com/dblock/ruby-enum/issues/49): Fixed `NoMethodError` on `keys`, `values`, `key?`, `value?`, `key`, `value`, `to_h`, `parse` and `each` when a subclass defines no enums of its own - [@dblock](https://github.com/dblock).
|
|
4
|
+
* [#49](https://github.com/dblock/ruby-enum/issues/49): `keys`, `key?`, `value?`, `key`, `value`, `to_h`, `parse` and `each` now include enums defined in a superclass, matching the existing behavior of `values` - [@dblock](https://github.com/dblock).
|
|
5
|
+
* [#61](https://github.com/dblock/ruby-enum/pull/61): Document performance overhead in README, add `rake benchmark:basic` - [@dblock](https://github.com/dblock).
|
|
6
|
+
|
|
1
7
|
### 1.1.0 (2026/6/20)
|
|
2
8
|
|
|
3
9
|
* [#56](https://github.com/dblock/ruby-enum/pull/56): Fix `DuplicateKeyError` when the enum class is reloaded - [@flvrone](https://github.com/flvrone).
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -27,6 +27,7 @@ Enum-like behavior for Ruby, heavily inspired by [this](http://www.rubyfleebie.c
|
|
|
27
27
|
- [Exhaustive case matcher](#exhaustive-case-matcher)
|
|
28
28
|
- [I18n support](#i18n-support)
|
|
29
29
|
- [Benchmarks](#benchmarks)
|
|
30
|
+
- [Performance](#performance)
|
|
30
31
|
- [Contributing](#contributing)
|
|
31
32
|
- [Copyright and License](#copyright-and-license)
|
|
32
33
|
- [Related Projects](#related-projects)
|
|
@@ -232,7 +233,7 @@ The `DuplicateValueError` exception is raised to be consistent with the unique k
|
|
|
232
233
|
|
|
233
234
|
### Inheritance
|
|
234
235
|
|
|
235
|
-
When inheriting from a `Ruby::Enum` class, all defined enums in the parent class will be accessible in
|
|
236
|
+
When inheriting from a `Ruby::Enum` class, all defined enums in the parent class will be accessible in subclasses as well. Subclasses can also provide extra enums, as usual.
|
|
236
237
|
|
|
237
238
|
``` ruby
|
|
238
239
|
class OrderState
|
|
@@ -262,6 +263,26 @@ OrderState.values # ['CREATED', 'PAID']
|
|
|
262
263
|
ShippedOrderState.values # ['CREATED', 'PAID', 'PREPARED', SHIPPED']
|
|
263
264
|
```
|
|
264
265
|
|
|
266
|
+
All other enumerating and hashing methods (`keys`, `key?`, `value?`, `key`, `value`, `to_h`, `parse` and `each`) also consider enums defined anywhere in the class hierarchy.
|
|
267
|
+
|
|
268
|
+
``` ruby
|
|
269
|
+
ShippedOrderState.keys # [:CREATED, :PAID, :PREPARED, :SHIPPED]
|
|
270
|
+
ShippedOrderState.key?(:CREATED) # true
|
|
271
|
+
ShippedOrderState.value(:CREATED) # 'CREATED'
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
A subclass may redefine a key or value already used by a parent class without raising `DuplicateKeyError` or `DuplicateValueError`; its own definition takes precedence.
|
|
275
|
+
|
|
276
|
+
``` ruby
|
|
277
|
+
class ShippedOrderState < OrderState
|
|
278
|
+
define :CREATED, 'RECREATED' # does not raise, overrides the parent class' definition
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
ShippedOrderState::CREATED # 'RECREATED'
|
|
282
|
+
ShippedOrderState.value(:CREATED) # 'RECREATED'
|
|
283
|
+
OrderState.value(:CREATED) # 'CREATED', unaffected
|
|
284
|
+
```
|
|
285
|
+
|
|
265
286
|
### Exhaustive case matcher
|
|
266
287
|
|
|
267
288
|
If you want to make sure that you cover all cases in a case stament, you can use the exhaustive case matcher: `Ruby::Enum::Case`. It will raise an error if a case/enum value is not handled, or if a value is specified that's not part of the enum. This is inspired by the [Rust Pattern Syntax](https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html). If multiple cases match, all matches are being executed. The return value is the value from the matched case, or an array of return values if multiple cases matched.
|
|
@@ -315,9 +336,21 @@ gem "i18n"
|
|
|
315
336
|
Benchmark scripts are defined in the [`benchmarks`](benchmarks) folder and can be run with Rake:
|
|
316
337
|
|
|
317
338
|
```console
|
|
318
|
-
rake
|
|
339
|
+
rake benchmark:basic
|
|
340
|
+
rake benchmark:case
|
|
341
|
+
rake benchmark:inheritance
|
|
319
342
|
```
|
|
320
343
|
|
|
344
|
+
### Performance
|
|
345
|
+
|
|
346
|
+
Constant access (e.g. `Colors::RED`) has no measurable overhead versus a plain Ruby constant, since it's just a constant lookup either way. Basic operations backed by a hash lookup - `value`, `key`, `key?`, `value?`, `keys`, `values` - carry some overhead (roughly 3-5x) compared to using a plain `Hash` directly, due to the extra method dispatch and object wrapping `Ruby::Enum` does internally. Run `rake benchmark:basic` to measure this on your own machine.
|
|
347
|
+
|
|
348
|
+
This overhead is constant regardless of how deep a subclass hierarchy is - `keys`, `key?`, `value?`, `key`, `value`, `to_h`, `parse` and `each` merge and memoize enums inherited from superclasses, so a subclass' lookups are effectively as fast as the base class' (see `rake benchmark:inheritance`).
|
|
349
|
+
|
|
350
|
+
The one notable exception is `Ruby::Enum::Case`, whose exhaustive `case`-like matcher is significantly slower (on the order of 50-100x, see `rake benchmark:case`) than a native Ruby `case`/`when` statement, since it builds and evaluates lambdas on every call rather than being optimized by the Ruby VM. Prefer a native `case` statement in hot code paths and reserve `Ruby::Enum::Case` for cases where its exhaustiveness check is worth the overhead.
|
|
351
|
+
|
|
352
|
+
For most applications this overhead is negligible in absolute terms (low single-digit microseconds per call), but it's worth being aware of in very hot code paths.
|
|
353
|
+
|
|
321
354
|
## Contributing
|
|
322
355
|
|
|
323
356
|
You're encouraged to contribute to ruby-enum. See [CONTRIBUTING](CONTRIBUTING.md) for details.
|
data/RELEASING.md
CHANGED
|
@@ -11,7 +11,7 @@ bundle install
|
|
|
11
11
|
rake
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
Check that the last build succeeded in [
|
|
14
|
+
Check that the last build succeeded in [GitHub Actions](https://github.com/dblock/ruby-enum/actions) for all supported platforms.
|
|
15
15
|
|
|
16
16
|
Add a date to this release in [CHANGELOG.md](CHANGELOG.md).
|
|
17
17
|
|
|
@@ -28,15 +28,24 @@ git add README.md CHANGELOG.md lib/ruby-enum/version.rb
|
|
|
28
28
|
git commit -m "Preparing for release, 0.2.2."
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
Release.
|
|
31
|
+
Release. If you have MFA enabled on RubyGems (you should), `rake release` will build the gem, tag it, and push commits/tags to GitHub, but fail at the RubyGems push. Push the gem manually with your OTP.
|
|
32
32
|
|
|
33
33
|
```
|
|
34
34
|
$ rake release
|
|
35
35
|
|
|
36
|
-
ruby-enum
|
|
37
|
-
Tagged
|
|
36
|
+
ruby-enum 1.1.0 built to pkg/ruby-enum-1.1.0.gem.
|
|
37
|
+
Tagged v1.1.0.
|
|
38
38
|
Pushed git commits and tags.
|
|
39
|
-
|
|
39
|
+
...
|
|
40
|
+
You have enabled multi-factor authentication. Please enter OTP code.
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Then push to RubyGems manually:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
$ gem push pkg/ruby-enum-1.1.0.gem --otp <OTP>
|
|
47
|
+
Pushing gem to https://rubygems.org...
|
|
48
|
+
Successfully registered gem: ruby-enum (1.1.0)
|
|
40
49
|
```
|
|
41
50
|
|
|
42
51
|
### Prepare for the Next Version
|
data/Rakefile
CHANGED
|
@@ -18,8 +18,18 @@ RuboCop::RakeTask.new(:rubocop)
|
|
|
18
18
|
task default: %i[rubocop spec]
|
|
19
19
|
|
|
20
20
|
namespace :benchmark do
|
|
21
|
+
desc 'Run benchmark for basic Ruby::Enum operations vs. plain Ruby equivalents'
|
|
22
|
+
task :basic do
|
|
23
|
+
require_relative 'benchmarks/basic'
|
|
24
|
+
end
|
|
25
|
+
|
|
21
26
|
desc 'Run benchmark for the Ruby::Enum::Case'
|
|
22
27
|
task :case do
|
|
23
28
|
require_relative 'benchmarks/case'
|
|
24
29
|
end
|
|
30
|
+
|
|
31
|
+
desc 'Run benchmark for enum lookups on classes with inheritance'
|
|
32
|
+
task :inheritance do
|
|
33
|
+
require_relative 'benchmarks/inheritance'
|
|
34
|
+
end
|
|
25
35
|
end
|
data/UPGRADING.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# Upgrading Ruby::Enum
|
|
2
2
|
|
|
3
|
+
## Upgrading to >= 1.2.0
|
|
4
|
+
|
|
5
|
+
### Inheritance & `keys`, `key?`, `value?`, `key`, `value`, `to_h`, `parse` and `each`
|
|
6
|
+
|
|
7
|
+
This only applies to classes that inherit from another which is a `Ruby::Enum`.
|
|
8
|
+
|
|
9
|
+
Prior to version `1.2.0`, only `values` enumerated enums defined in the entire class hierarchy; `keys`, `key?`, `value?`, `key`, `value`, `to_h`, `parse` and `each` only considered enums defined directly on the class, silently ignoring anything defined in a superclass.
|
|
10
|
+
|
|
11
|
+
As of version `1.2.0`, these methods behave consistently with `values` and also enumerate/consider enums defined anywhere in the class hierarchy, ancestors first. A subclass may still redefine a key or value already used by a superclass; its own definition takes precedence.
|
|
12
|
+
|
|
13
|
+
``` ruby
|
|
14
|
+
class PrimaryColors
|
|
15
|
+
include Ruby::Enum
|
|
16
|
+
|
|
17
|
+
define :RED, 'RED'
|
|
18
|
+
define :GREEN, 'GREEN'
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class RainbowColors < PrimaryColors
|
|
22
|
+
define :ORANGE, 'ORANGE'
|
|
23
|
+
end
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`gem 'ruby-enum', '< 1.2.0'`
|
|
27
|
+
|
|
28
|
+
``` ruby
|
|
29
|
+
RainbowColors.keys # => [:ORANGE]
|
|
30
|
+
RainbowColors.key?(:RED) # => false
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`gem 'ruby-enum', '>= 1.2.0'`
|
|
34
|
+
|
|
35
|
+
``` ruby
|
|
36
|
+
RainbowColors.keys # => [:RED, :GREEN, :ORANGE]
|
|
37
|
+
RainbowColors.key?(:RED) # => true
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
See [#49](https://github.com/dblock/ruby-enum/issues/49) for more information.
|
|
41
|
+
|
|
3
42
|
## Upgrading to >= 0.9.0
|
|
4
43
|
|
|
5
44
|
### Inheritance & `Ruby::Enum.values`
|
data/benchmarks/basic.rb
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
4
|
+
|
|
5
|
+
require 'benchmark'
|
|
6
|
+
require 'ruby-enum'
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
# Ruby::Enum equivalent of a plain Hash/constant lookup.
|
|
10
|
+
class Colors
|
|
11
|
+
include Ruby::Enum
|
|
12
|
+
|
|
13
|
+
define :RED, 'red'
|
|
14
|
+
define :GREEN, 'green'
|
|
15
|
+
define :BLUE, 'blue'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Plain Ruby equivalents, doing the same lookups without Ruby::Enum.
|
|
19
|
+
PLAIN_HASH = { RED: 'red', GREEN: 'green', BLUE: 'blue' }.freeze
|
|
20
|
+
|
|
21
|
+
module PlainConstants
|
|
22
|
+
RED = 'red'
|
|
23
|
+
GREEN = 'green'
|
|
24
|
+
BLUE = 'blue'
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
n = 1_000_000
|
|
28
|
+
|
|
29
|
+
def benchmark(label, iterations, &block)
|
|
30
|
+
time = Benchmark.realtime { iterations.times(&block) }
|
|
31
|
+
puts "#{label}: #{time.round(4)}"
|
|
32
|
+
time
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
puts "Running #{n} iterations of each scenario below\n\n"
|
|
36
|
+
|
|
37
|
+
puts '--- Constant access ---'
|
|
38
|
+
enum_const_time = benchmark('Ruby::Enum constant (Colors::RED)', n) { Colors::RED }
|
|
39
|
+
plain_const_time = benchmark('plain Ruby constant (PlainConstants::RED)', n) { PlainConstants::RED }
|
|
40
|
+
|
|
41
|
+
puts "\n--- Key to value lookup ---"
|
|
42
|
+
enum_value_time = benchmark('Ruby::Enum (Colors.value(:RED))', n) { Colors.value(:RED) }
|
|
43
|
+
hash_value_time = benchmark('plain Hash (PLAIN_HASH[:RED])', n) { PLAIN_HASH[:RED] }
|
|
44
|
+
|
|
45
|
+
puts "\n--- Value to key lookup ---"
|
|
46
|
+
benchmark('Ruby::Enum (Colors.key(\'red\'))', n) { Colors.key('red') }
|
|
47
|
+
benchmark('plain Hash (PLAIN_HASH.key(\'red\'))', n) { PLAIN_HASH.key('red') }
|
|
48
|
+
|
|
49
|
+
puts "\n--- Existence checks ---"
|
|
50
|
+
benchmark('Ruby::Enum (Colors.key?(:RED))', n) { Colors.key?(:RED) }
|
|
51
|
+
benchmark('plain Hash (PLAIN_HASH.key?(:RED))', n) { PLAIN_HASH.key?(:RED) }
|
|
52
|
+
|
|
53
|
+
puts "\n--- Enumerating all keys/values ---"
|
|
54
|
+
benchmark('Ruby::Enum (Colors.keys)', n) { Colors.keys }
|
|
55
|
+
benchmark('plain Hash (PLAIN_HASH.keys)', n) { PLAIN_HASH.keys }
|
|
56
|
+
benchmark('Ruby::Enum (Colors.values)', n) { Colors.values }
|
|
57
|
+
benchmark('plain Hash (PLAIN_HASH.values)', n) { PLAIN_HASH.values }
|
|
58
|
+
|
|
59
|
+
puts "\n--- Comparison ---"
|
|
60
|
+
puts "Ruby::Enum constant access is #{(enum_const_time / plain_const_time).round(2)}x plain Ruby constant access"
|
|
61
|
+
puts "Ruby::Enum .value is #{(enum_value_time / hash_value_time).round(2)}x plain Hash#[]"
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
4
|
+
|
|
5
|
+
require 'benchmark'
|
|
6
|
+
require 'ruby-enum'
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
# Base enum, no inheritance.
|
|
10
|
+
class Colors
|
|
11
|
+
include Ruby::Enum
|
|
12
|
+
|
|
13
|
+
define :RED, 'red'
|
|
14
|
+
define :GREEN, 'green'
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
##
|
|
18
|
+
# Subclass, adds its own enum on top of an inherited one (1 level).
|
|
19
|
+
class SubColors < Colors
|
|
20
|
+
define :BLUE, 'blue'
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
##
|
|
24
|
+
# Sub-subclass, adds its own enum on top of 2 inherited levels.
|
|
25
|
+
class SubSubColors < SubColors
|
|
26
|
+
define :YELLOW, 'yellow'
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
n = 1_000_000
|
|
30
|
+
|
|
31
|
+
def benchmark(label, iterations, &block)
|
|
32
|
+
time = Benchmark.realtime { iterations.times(&block) }
|
|
33
|
+
puts "#{label}: #{time.round(4)}"
|
|
34
|
+
time
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
puts "Running #{n} iterations of each scenario below\n\n"
|
|
38
|
+
|
|
39
|
+
puts '--- .value lookups, by depth of inheritance ---'
|
|
40
|
+
base_value = benchmark('base class (no inheritance)', n) { Colors.value(:RED) }
|
|
41
|
+
sub_value = benchmark('subclass (1 level)', n) { SubColors.value(:RED) }
|
|
42
|
+
sub_sub_value = benchmark('sub-subclass (2 levels)', n) { SubSubColors.value(:RED) }
|
|
43
|
+
|
|
44
|
+
puts "\n--- Other methods on a subclass (1 level) ---"
|
|
45
|
+
benchmark('.keys', n) { SubColors.keys }
|
|
46
|
+
benchmark('.key?', n) { SubColors.key?(:RED) }
|
|
47
|
+
benchmark('.to_h', n) { SubColors.to_h }
|
|
48
|
+
count = 0
|
|
49
|
+
benchmark('.each', n) { SubColors.each { |_k, _v| count += 1 } }
|
|
50
|
+
|
|
51
|
+
puts "\n--- Comparison ---"
|
|
52
|
+
puts "subclass value is #{(sub_value / base_value).round(2)}x base class value"
|
|
53
|
+
puts "sub-subclass value is #{(sub_sub_value / base_value).round(2)}x base class value"
|
data/lib/ruby-enum/enum.rb
CHANGED
|
@@ -20,8 +20,8 @@ module Ruby
|
|
|
20
20
|
|
|
21
21
|
base.private_class_method(:new)
|
|
22
22
|
|
|
23
|
-
base.instance_variable_set(:@
|
|
24
|
-
base.instance_variable_set(:@
|
|
23
|
+
base.instance_variable_set(:@_own_enum_hash, {})
|
|
24
|
+
base.instance_variable_set(:@_own_enums_by_value, {})
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
module ClassMethods
|
|
@@ -31,9 +31,6 @@ module Ruby
|
|
|
31
31
|
# [key] Enumerator key.
|
|
32
32
|
# [value] Enumerator value.
|
|
33
33
|
def define(key, value = key)
|
|
34
|
-
@_enum_hash ||= {}
|
|
35
|
-
@_enums_by_value ||= {}
|
|
36
|
-
|
|
37
34
|
validate_key!(key)
|
|
38
35
|
validate_value!(value)
|
|
39
36
|
|
|
@@ -48,18 +45,21 @@ module Ruby
|
|
|
48
45
|
|
|
49
46
|
def store_new_instance(key, value)
|
|
50
47
|
new_instance = new(key, value)
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
_own_enum_hash[key] = new_instance
|
|
49
|
+
_own_enums_by_value[value] = new_instance
|
|
50
|
+
|
|
51
|
+
# Invalidate memoized, merged hashes since this class' own enums changed.
|
|
52
|
+
@_enum_hash = @_enums_by_value = nil
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def const_missing(key)
|
|
56
56
|
raise Ruby::Enum::Errors::UninitializedConstantError, name: name, key: key
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
-
# Iterate over all enumerated values.
|
|
59
|
+
# Iterate over all enumerated values, including those defined in a superclass.
|
|
60
60
|
# Required for Enumerable mixin
|
|
61
61
|
def each(&block)
|
|
62
|
-
|
|
62
|
+
_enum_hash.each(&block)
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
# Attempt to parse an enum key and return the
|
|
@@ -77,56 +77,56 @@ module Ruby
|
|
|
77
77
|
nil
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
-
# Whether the specified key exists in this enum.
|
|
80
|
+
# Whether the specified key exists in this enum, including those defined in a superclass.
|
|
81
81
|
#
|
|
82
82
|
# === Parameters
|
|
83
83
|
# [k] The string key to check.
|
|
84
84
|
#
|
|
85
85
|
# Returns true if the key exists, false otherwise.
|
|
86
86
|
def key?(k)
|
|
87
|
-
|
|
87
|
+
_enum_hash.key?(k)
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
-
# Gets the string value for the specified key.
|
|
90
|
+
# Gets the string value for the specified key, including those defined in a superclass.
|
|
91
91
|
#
|
|
92
92
|
# === Parameters
|
|
93
93
|
# [k] The key symbol to get the value for.
|
|
94
94
|
#
|
|
95
95
|
# Returns the corresponding enum instance or nil.
|
|
96
96
|
def value(k)
|
|
97
|
-
enum =
|
|
97
|
+
enum = _enum_hash[k]
|
|
98
98
|
enum&.value
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
-
# Whether the specified value exists in this enum.
|
|
101
|
+
# Whether the specified value exists in this enum, including those defined in a superclass.
|
|
102
102
|
#
|
|
103
103
|
# === Parameters
|
|
104
104
|
# [k] The string value to check.
|
|
105
105
|
#
|
|
106
106
|
# Returns true if the value exists, false otherwise.
|
|
107
107
|
def value?(v)
|
|
108
|
-
|
|
108
|
+
_enums_by_value.key?(v)
|
|
109
109
|
end
|
|
110
110
|
|
|
111
|
-
# Gets the key symbol for the specified value.
|
|
111
|
+
# Gets the key symbol for the specified value, including those defined in a superclass.
|
|
112
112
|
#
|
|
113
113
|
# === Parameters
|
|
114
114
|
# [v] The string value to parse.
|
|
115
115
|
#
|
|
116
116
|
# Returns the corresponding key symbol or nil.
|
|
117
117
|
def key(v)
|
|
118
|
-
enum =
|
|
118
|
+
enum = _enums_by_value[v]
|
|
119
119
|
enum&.key
|
|
120
120
|
end
|
|
121
121
|
|
|
122
|
-
# Returns all enum keys.
|
|
122
|
+
# Returns all enum keys, including those defined in a superclass.
|
|
123
123
|
def keys
|
|
124
|
-
|
|
124
|
+
_enum_hash.values.map(&:key)
|
|
125
125
|
end
|
|
126
126
|
|
|
127
127
|
# Returns all enum values.
|
|
128
128
|
def values
|
|
129
|
-
result =
|
|
129
|
+
result = _own_enum_hash.values.map(&:value)
|
|
130
130
|
|
|
131
131
|
if superclass < Ruby::Enum
|
|
132
132
|
superclass.values + result
|
|
@@ -135,40 +135,77 @@ module Ruby
|
|
|
135
135
|
end
|
|
136
136
|
end
|
|
137
137
|
|
|
138
|
-
# Iterate over all enumerated values.
|
|
138
|
+
# Iterate over all enumerated values, including those defined in a superclass.
|
|
139
139
|
# Required for Enumerable mixin
|
|
140
140
|
def each_value(&_block)
|
|
141
|
-
|
|
141
|
+
_enum_hash.each_value do |v|
|
|
142
142
|
yield v.value
|
|
143
143
|
end
|
|
144
144
|
end
|
|
145
145
|
|
|
146
|
-
# Iterate over all enumerated keys.
|
|
146
|
+
# Iterate over all enumerated keys, including those defined in a superclass.
|
|
147
147
|
# Required for Enumerable mixin
|
|
148
148
|
def each_key(&_block)
|
|
149
|
-
|
|
149
|
+
_enum_hash.each_value do |v|
|
|
150
150
|
yield v.key
|
|
151
151
|
end
|
|
152
152
|
end
|
|
153
153
|
|
|
154
|
+
# Returns a hash of key:values, including those defined in a superclass.
|
|
154
155
|
def to_h
|
|
155
|
-
|
|
156
|
+
_enum_hash.transform_values(&:value)
|
|
156
157
|
end
|
|
157
158
|
|
|
158
159
|
private
|
|
159
160
|
|
|
161
|
+
# Returns this class' own enum hash, defaulting to an empty hash.
|
|
162
|
+
#
|
|
163
|
+
# A subclass that does not `define` any of its own enums does not have
|
|
164
|
+
# its `@_own_enum_hash` instance variable set, since it's only initialized
|
|
165
|
+
# in `define` and in the `included` hook.
|
|
166
|
+
def _own_enum_hash
|
|
167
|
+
@_own_enum_hash ||= {}
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Returns this class' own enums-by-value hash, defaulting to an empty hash.
|
|
171
|
+
def _own_enums_by_value
|
|
172
|
+
@_own_enums_by_value ||= {}
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Returns the enum hash for this class merged with all of its superclasses,
|
|
176
|
+
# with keys defined in this class taking precedence over those inherited
|
|
177
|
+
# from a superclass.
|
|
178
|
+
def _enum_hash
|
|
179
|
+
@_enum_hash ||= if superclass < Ruby::Enum
|
|
180
|
+
superclass.send(:_enum_hash).merge(_own_enum_hash)
|
|
181
|
+
else
|
|
182
|
+
_own_enum_hash
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Returns the enums-by-value hash for this class merged with all of its
|
|
187
|
+
# superclasses, with values defined in this class taking precedence over
|
|
188
|
+
# those inherited from a superclass.
|
|
189
|
+
def _enums_by_value
|
|
190
|
+
@_enums_by_value ||= if superclass < Ruby::Enum
|
|
191
|
+
superclass.send(:_enums_by_value).merge(_own_enums_by_value)
|
|
192
|
+
else
|
|
193
|
+
_own_enums_by_value
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
160
197
|
def upper?(s)
|
|
161
198
|
!/[[:upper:]]/.match(s).nil?
|
|
162
199
|
end
|
|
163
200
|
|
|
164
201
|
def validate_key!(key)
|
|
165
|
-
return unless
|
|
202
|
+
return unless _own_enum_hash.key?(key)
|
|
166
203
|
|
|
167
204
|
raise Ruby::Enum::Errors::DuplicateKeyError, name: name, key: key
|
|
168
205
|
end
|
|
169
206
|
|
|
170
207
|
def validate_value!(value)
|
|
171
|
-
return unless
|
|
208
|
+
return unless _own_enums_by_value.key?(value)
|
|
172
209
|
|
|
173
210
|
raise Ruby::Enum::Errors::DuplicateValueError, name: name, value: value
|
|
174
211
|
end
|
data/lib/ruby-enum/version.rb
CHANGED
|
Binary file
|