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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +35 -2
  5. data/RELEASING.md +14 -5
  6. data/Rakefile +10 -0
  7. data/UPGRADING.md +39 -0
  8. data/benchmarks/basic.rb +61 -0
  9. data/benchmarks/inheritance.rb +53 -0
  10. data/lib/ruby-enum/enum.rb +64 -27
  11. data/lib/ruby-enum/version.rb +1 -1
  12. data/pkg/ruby-enum-1.1.0.gem +0 -0
  13. data/spec/ruby-enum/enum_spec.rb +115 -0
  14. data/spec_i18n/Gemfile.lock +45 -0
  15. metadata +6 -61
  16. data/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_asc.png +0 -0
  17. data/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_asc_disabled.png +0 -0
  18. data/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_both.png +0 -0
  19. data/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_desc.png +0 -0
  20. data/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_desc_disabled.png +0 -0
  21. data/coverage/assets/0.12.3/application.css +0 -1
  22. data/coverage/assets/0.12.3/application.js +0 -7
  23. data/coverage/assets/0.12.3/colorbox/border.png +0 -0
  24. data/coverage/assets/0.12.3/colorbox/controls.png +0 -0
  25. data/coverage/assets/0.12.3/colorbox/loading.gif +0 -0
  26. data/coverage/assets/0.12.3/colorbox/loading_background.png +0 -0
  27. data/coverage/assets/0.12.3/favicon_green.png +0 -0
  28. data/coverage/assets/0.12.3/favicon_red.png +0 -0
  29. data/coverage/assets/0.12.3/favicon_yellow.png +0 -0
  30. data/coverage/assets/0.12.3/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  31. data/coverage/assets/0.12.3/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  32. data/coverage/assets/0.12.3/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  33. data/coverage/assets/0.12.3/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  34. data/coverage/assets/0.12.3/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  35. data/coverage/assets/0.12.3/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  36. data/coverage/assets/0.12.3/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  37. data/coverage/assets/0.12.3/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  38. data/coverage/assets/0.12.3/images/ui-icons_222222_256x240.png +0 -0
  39. data/coverage/assets/0.12.3/images/ui-icons_2e83ff_256x240.png +0 -0
  40. data/coverage/assets/0.12.3/images/ui-icons_454545_256x240.png +0 -0
  41. data/coverage/assets/0.12.3/images/ui-icons_888888_256x240.png +0 -0
  42. data/coverage/assets/0.12.3/images/ui-icons_cd0a0a_256x240.png +0 -0
  43. data/coverage/assets/0.12.3/loading.gif +0 -0
  44. data/coverage/assets/0.12.3/magnify.png +0 -0
  45. data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_asc.png +0 -0
  46. data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_asc_disabled.png +0 -0
  47. data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_both.png +0 -0
  48. data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_desc.png +0 -0
  49. data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_desc_disabled.png +0 -0
  50. data/coverage/assets/0.13.2/application.css +0 -1
  51. data/coverage/assets/0.13.2/application.js +0 -7
  52. data/coverage/assets/0.13.2/colorbox/border.png +0 -0
  53. data/coverage/assets/0.13.2/colorbox/controls.png +0 -0
  54. data/coverage/assets/0.13.2/colorbox/loading.gif +0 -0
  55. data/coverage/assets/0.13.2/colorbox/loading_background.png +0 -0
  56. data/coverage/assets/0.13.2/favicon_green.png +0 -0
  57. data/coverage/assets/0.13.2/favicon_red.png +0 -0
  58. data/coverage/assets/0.13.2/favicon_yellow.png +0 -0
  59. data/coverage/assets/0.13.2/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  60. data/coverage/assets/0.13.2/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  61. data/coverage/assets/0.13.2/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  62. data/coverage/assets/0.13.2/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  63. data/coverage/assets/0.13.2/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  64. data/coverage/assets/0.13.2/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  65. data/coverage/assets/0.13.2/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  66. data/coverage/assets/0.13.2/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  67. data/coverage/assets/0.13.2/images/ui-icons_222222_256x240.png +0 -0
  68. data/coverage/assets/0.13.2/images/ui-icons_2e83ff_256x240.png +0 -0
  69. data/coverage/assets/0.13.2/images/ui-icons_454545_256x240.png +0 -0
  70. data/coverage/assets/0.13.2/images/ui-icons_888888_256x240.png +0 -0
  71. data/coverage/assets/0.13.2/images/ui-icons_cd0a0a_256x240.png +0 -0
  72. data/coverage/assets/0.13.2/loading.gif +0 -0
  73. data/coverage/assets/0.13.2/magnify.png +0 -0
  74. data/coverage/index.html +0 -8636
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ef6a193362634cd178978189b5e5a09fafe68ad2428c1d646abf33ef6f66d4a
4
- data.tar.gz: 182bdfaff04b1b241bac9614722a3010d2613e2ff8b0cb7a603053df6796b664
3
+ metadata.gz: 0424fcb26d8fc260165a669fa7be1d83e41a3a8e967ad606c5eb72abb656b349
4
+ data.tar.gz: 2ea8e9dc094a9612447679c7d5bebf88ac466b1c1b9a5f666d3ce5a8d0981965
5
5
  SHA512:
6
- metadata.gz: 40d0b2b8a93150ec1f21ef209e020dcb6a75142eaba2fd989b9f0ac6853953ec5a6ec0b9a356f8d1411e2ffb6ab955acb453cb576ba841cad3ec7eea6ffebac5
7
- data.tar.gz: 65fe31ba27ba5f9c76d95090d00d8024fb0ba1f7abb8bd5e5bff87ea7559f941ec67a35a655f5cfe39b1e68e89de043d0702bd572ed18465483c1c4bc36fe6dc
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ruby-enum (1.1.0)
4
+ ruby-enum (1.2.0)
5
5
 
6
6
  GEM
7
7
  remote: http://rubygems.org/
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 sub classes as well. Sub classes can also provide extra enums, as usual.
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 benchmarks:case
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 [Travis CI](https://travis-ci.org/dblock/ruby-enum) for all supported platforms.
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 0.2.2 built to pkg/ruby-enum-0.2.2.gem.
37
- Tagged v0.2.2.
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
- Pushed ruby-enum 0.2.2 to rubygems.org.
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`
@@ -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"
@@ -20,8 +20,8 @@ module Ruby
20
20
 
21
21
  base.private_class_method(:new)
22
22
 
23
- base.instance_variable_set(:@_enum_hash, {})
24
- base.instance_variable_set(:@_enums_by_value, {})
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
- @_enum_hash[key] = new_instance
52
- @_enums_by_value[value] = new_instance
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
- @_enum_hash.each(&block)
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
- @_enum_hash.key?(k)
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 = @_enum_hash[k]
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
- @_enums_by_value.key?(v)
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 = @_enums_by_value[v]
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
- @_enum_hash.values.map(&:key)
124
+ _enum_hash.values.map(&:key)
125
125
  end
126
126
 
127
127
  # Returns all enum values.
128
128
  def values
129
- result = @_enum_hash.values.map(&:value)
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
- @_enum_hash.each_value do |v|
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
- @_enum_hash.each_value do |v|
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
- @_enum_hash.transform_values(&:value)
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 @_enum_hash.key?(key)
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 @_enums_by_value.key?(value)
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ruby
4
4
  module Enum
5
- VERSION = '1.1.0'
5
+ VERSION = '1.2.0'
6
6
  end
7
7
  end
Binary file