Skip to content

Commit 28d00d7

Browse files
authored
Merge pull request #59 from dblock/subclass-inherited-values
Inherit keys, key?, value?, key, value, to_h, parse and each from superclass
2 parents 19b3249 + e11b310 commit 28d00d7

5 files changed

Lines changed: 196 additions & 29 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
### 1.2.0 (Next)
22

33
* [#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).
45
* Your contribution here.
56

67
### 1.1.0 (2026/6/20)

‎README.md‎

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ The `DuplicateValueError` exception is raised to be consistent with the unique k
232232

233233
### Inheritance
234234

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.
235+
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.
236236

237237
``` ruby
238238
class OrderState
@@ -262,6 +262,26 @@ OrderState.values # ['CREATED', 'PAID']
262262
ShippedOrderState.values # ['CREATED', 'PAID', 'PREPARED', SHIPPED']
263263
```
264264

265+
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.
266+
267+
``` ruby
268+
ShippedOrderState.keys # [:CREATED, :PAID, :PREPARED, :SHIPPED]
269+
ShippedOrderState.key?(:CREATED) # true
270+
ShippedOrderState.value(:CREATED) # 'CREATED'
271+
```
272+
273+
A subclass may redefine a key or value already used by a parent class without raising `DuplicateKeyError` or `DuplicateValueError`; its own definition takes precedence.
274+
275+
``` ruby
276+
class ShippedOrderState < OrderState
277+
define :CREATED, 'RECREATED' # does not raise, overrides the parent class' definition
278+
end
279+
280+
ShippedOrderState::CREATED # 'RECREATED'
281+
ShippedOrderState.value(:CREATED) # 'RECREATED'
282+
OrderState.value(:CREATED) # 'CREATED', unaffected
283+
```
284+
265285
### Exhaustive case matcher
266286

267287
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.

‎UPGRADING.md‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,44 @@
11
# Upgrading Ruby::Enum
22

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+
342
## Upgrading to >= 0.9.0
443

544
### Inheritance & `Ruby::Enum.values`

‎lib/ruby-enum/enum.rb‎

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ def self.included(base)
2020

2121
base.private_class_method(:new)
2222

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, {})
2525
end
2626

2727
module ClassMethods
@@ -45,15 +45,15 @@ def define(key, value = key)
4545

4646
def store_new_instance(key, value)
4747
new_instance = new(key, value)
48-
_enum_hash[key] = new_instance
49-
_enums_by_value[value] = new_instance
48+
_own_enum_hash[key] = new_instance
49+
_own_enums_by_value[value] = new_instance
5050
end
5151

5252
def const_missing(key)
5353
raise Ruby::Enum::Errors::UninitializedConstantError, name: name, key: key
5454
end
5555

56-
# Iterate over all enumerated values.
56+
# Iterate over all enumerated values, including those defined in a superclass.
5757
# Required for Enumerable mixin
5858
def each(&block)
5959
_enum_hash.each(&block)
@@ -74,7 +74,7 @@ def parse(k)
7474
nil
7575
end
7676

77-
# Whether the specified key exists in this enum.
77+
# Whether the specified key exists in this enum, including those defined in a superclass.
7878
#
7979
# === Parameters
8080
# [k] The string key to check.
@@ -84,7 +84,7 @@ def key?(k)
8484
_enum_hash.key?(k)
8585
end
8686

87-
# Gets the string value for the specified key.
87+
# Gets the string value for the specified key, including those defined in a superclass.
8888
#
8989
# === Parameters
9090
# [k] The key symbol to get the value for.
@@ -95,7 +95,7 @@ def value(k)
9595
enum&.value
9696
end
9797

98-
# Whether the specified value exists in this enum.
98+
# Whether the specified value exists in this enum, including those defined in a superclass.
9999
#
100100
# === Parameters
101101
# [k] The string value to check.
@@ -105,7 +105,7 @@ def value?(v)
105105
_enums_by_value.key?(v)
106106
end
107107

108-
# Gets the key symbol for the specified value.
108+
# Gets the key symbol for the specified value, including those defined in a superclass.
109109
#
110110
# === Parameters
111111
# [v] The string value to parse.
@@ -116,14 +116,14 @@ def key(v)
116116
enum&.key
117117
end
118118

119-
# Returns all enum keys.
119+
# Returns all enum keys, including those defined in a superclass.
120120
def keys
121121
_enum_hash.values.map(&:key)
122122
end
123123

124124
# Returns all enum values.
125125
def values
126-
result = _enum_hash.values.map(&:value)
126+
result = _own_enum_hash.values.map(&:value)
127127

128128
if superclass < Ruby::Enum
129129
superclass.values + result
@@ -132,22 +132,23 @@ def values
132132
end
133133
end
134134

135-
# Iterate over all enumerated values.
135+
# Iterate over all enumerated values, including those defined in a superclass.
136136
# Required for Enumerable mixin
137137
def each_value(&_block)
138138
_enum_hash.each_value do |v|
139139
yield v.value
140140
end
141141
end
142142

143-
# Iterate over all enumerated keys.
143+
# Iterate over all enumerated keys, including those defined in a superclass.
144144
# Required for Enumerable mixin
145145
def each_key(&_block)
146146
_enum_hash.each_value do |v|
147147
yield v.key
148148
end
149149
end
150150

151+
# Returns a hash of key:values, including those defined in a superclass.
151152
def to_h
152153
_enum_hash.transform_values(&:value)
153154
end
@@ -157,29 +158,51 @@ def to_h
157158
# Returns this class' own enum hash, defaulting to an empty hash.
158159
#
159160
# A subclass that does not `define` any of its own enums does not have
160-
# its `@_enum_hash` instance variable set, since it's only initialized
161+
# its `@_own_enum_hash` instance variable set, since it's only initialized
161162
# in `define` and in the `included` hook.
162-
def _enum_hash
163-
@_enum_hash ||= {}
163+
def _own_enum_hash
164+
@_own_enum_hash ||= {}
164165
end
165166

166167
# Returns this class' own enums-by-value hash, defaulting to an empty hash.
168+
def _own_enums_by_value
169+
@_own_enums_by_value ||= {}
170+
end
171+
172+
# Returns the enum hash for this class merged with all of its superclasses,
173+
# with keys defined in this class taking precedence over those inherited
174+
# from a superclass.
175+
def _enum_hash
176+
if superclass < Ruby::Enum
177+
superclass.send(:_enum_hash).merge(_own_enum_hash)
178+
else
179+
_own_enum_hash
180+
end
181+
end
182+
183+
# Returns the enums-by-value hash for this class merged with all of its
184+
# superclasses, with values defined in this class taking precedence over
185+
# those inherited from a superclass.
167186
def _enums_by_value
168-
@_enums_by_value ||= {}
187+
if superclass < Ruby::Enum
188+
superclass.send(:_enums_by_value).merge(_own_enums_by_value)
189+
else
190+
_own_enums_by_value
191+
end
169192
end
170193

171194
def upper?(s)
172195
!/[[:upper:]]/.match(s).nil?
173196
end
174197

175198
def validate_key!(key)
176-
return unless _enum_hash.key?(key)
199+
return unless _own_enum_hash.key?(key)
177200

178201
raise Ruby::Enum::Errors::DuplicateKeyError, name: name, key: key
179202
end
180203

181204
def validate_value!(value)
182-
return unless _enums_by_value.key?(value)
205+
return unless _own_enums_by_value.key?(value)
183206

184207
raise Ruby::Enum::Errors::DuplicateValueError, name: name, value: value
185208
end

0 commit comments

Comments
 (0)