type_is_enum 0.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 +7 -0
- data/.gitignore +45 -0
- data/.rubocop.yml +24 -0
- data/.ruby-version +1 -0
- data/.travis.yml +2 -0
- data/.yardopts +1 -0
- data/CHANGES.md +47 -0
- data/Gemfile +3 -0
- data/LICENSE.md +22 -0
- data/README.md +478 -0
- data/Rakefile +49 -0
- data/lib/type_is_enum.rb +3 -0
- data/lib/type_is_enum/enum.rb +137 -0
- data/lib/type_is_enum/module_info.rb +10 -0
- data/lib/type_is_enum/value_enum.rb +9 -0
- data/spec/.rubocop.yml +10 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/unit/type_is_enum/enum_spec.rb +367 -0
- data/type_is_enum.gemspec +34 -0
- metadata +166 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0404f23335ab5f97a1afd40dfd03b867fb2b820a
|
4
|
+
data.tar.gz: 7f7cc6cf0103fb2e158813f2c638b9757d3075dd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 99ab72b0c8fca5006bbe28e3f77b9615e3537ea7df28ffcaeb03df588b28368619394b5718545f373e1ac1cb3ccb53545df4cfb8564cfa69648b6e2565047fe0
|
7
|
+
data.tar.gz: b28d6949c5447d1135700a3e0acc1f9ac263023f79aa432155bf5afe787aebb58b333a93a8cc616b1aced7c8cf1dbb254a4f99baaa901089a6bde6b6e5a51571
|
data/.gitignore
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# Ruby defaults
|
2
|
+
|
3
|
+
/.bundle/
|
4
|
+
/.yardoc
|
5
|
+
/Gemfile.lock
|
6
|
+
/_yardoc/
|
7
|
+
/coverage/
|
8
|
+
/doc/
|
9
|
+
/pkg/
|
10
|
+
/spec/reports/
|
11
|
+
/tmp/
|
12
|
+
*.bundle
|
13
|
+
*.so
|
14
|
+
*.o
|
15
|
+
*.a
|
16
|
+
mkmf.log
|
17
|
+
|
18
|
+
# Build output
|
19
|
+
*.gem
|
20
|
+
|
21
|
+
# Database
|
22
|
+
|
23
|
+
db/*.sqlite3
|
24
|
+
|
25
|
+
# Logs
|
26
|
+
|
27
|
+
/log/
|
28
|
+
|
29
|
+
# IntellJ
|
30
|
+
|
31
|
+
*.iml
|
32
|
+
*.ipr
|
33
|
+
*.iws
|
34
|
+
*.ids
|
35
|
+
.rakeTasks
|
36
|
+
|
37
|
+
# Emacs
|
38
|
+
|
39
|
+
*~
|
40
|
+
\#*
|
41
|
+
.#*
|
42
|
+
|
43
|
+
# Mac OS
|
44
|
+
|
45
|
+
.DS_Store
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# Disable compact style check for example.rb
|
2
|
+
Style/ClassAndModuleChildren:
|
3
|
+
Exclude:
|
4
|
+
- 'example.rb'
|
5
|
+
|
6
|
+
# Disable line-length check; it's too easy for the cure to be worse than the disease
|
7
|
+
Metrics/LineLength:
|
8
|
+
Enabled: False
|
9
|
+
|
10
|
+
# Disable problematic module documentation check (see https://github.com/bbatsov/rubocop/issues/947)
|
11
|
+
Style/Documentation:
|
12
|
+
Enabled: false
|
13
|
+
|
14
|
+
# Allow one line around class body (Style/EmptyLines will still disallow two or more)
|
15
|
+
Style/EmptyLinesAroundClassBody:
|
16
|
+
Enabled: false
|
17
|
+
|
18
|
+
# Allow one line around module body (Style/EmptyLines will still disallow two or more)
|
19
|
+
Style/EmptyLinesAroundModuleBody:
|
20
|
+
Enabled: false
|
21
|
+
|
22
|
+
# Allow one line around block body (Style/EmptyLines will still disallow two or more)
|
23
|
+
Style/EmptyLinesAroundBlockBody:
|
24
|
+
Enabled: false
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.2.3
|
data/.travis.yml
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--markup markdown
|
data/CHANGES.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
## 0.1.7 (28 April 2016)
|
2
|
+
|
3
|
+
- The default `to_s` for `TypesafeEnum::Enum` now includes the enum's class, key, value,
|
4
|
+
and ordinal, e.g.
|
5
|
+
|
6
|
+
Suit::DIAMONDS.to_s
|
7
|
+
# => "Suit::DIAMONDS [1] -> diamonds"
|
8
|
+
|
9
|
+
(Fixes [#5](https://github.com/dmolesUC3/typesafe_enum/issues/5).)
|
10
|
+
- `::find_by_value_str` now uses a hash lookup like the other `::find_by` methods.
|
11
|
+
- Improved method documentation.
|
12
|
+
|
13
|
+
## 0.1.6 (15 Mar 2016)
|
14
|
+
|
15
|
+
- [#3](https://github.com/dmolesUC3/typesafe_enum/pull/3) - No need for `instance_eval`
|
16
|
+
when creating new enum instance methods - [@dblock](https://github.com/dblock).
|
17
|
+
|
18
|
+
## 0.1.5 (27 Jan 2016)
|
19
|
+
|
20
|
+
- Include the original call site in the warning message for duplicate instances to help
|
21
|
+
with debugging.
|
22
|
+
- Modified gemspec to take into account SSH checkouts when determining the homepage URL.
|
23
|
+
|
24
|
+
## 0.1.4 (18 Dec 2015))
|
25
|
+
|
26
|
+
- Exact duplicate instances (e.g. due to multiple `requires`) are now ignored with a warning,
|
27
|
+
instead of causing a `NameError`. Duplicate keys with different values, and duplicate values
|
28
|
+
with different keys, still raise a `NameError`.
|
29
|
+
- `NameErrors` due to invalid keys or values no longer cause the enum class to be undefined.
|
30
|
+
However, the invalid instances will still not be registered and no constants created for them.
|
31
|
+
|
32
|
+
## 0.1.3 (17 Dec 2015)
|
33
|
+
|
34
|
+
- Fixed issue where invalid classes weren't properly removed after duplicate name declarations,
|
35
|
+
polluting the namespace and causing duplicate declration error messages to be lost.
|
36
|
+
|
37
|
+
## 0.1.2 (19 Nov 2015)
|
38
|
+
|
39
|
+
- Fixed issue where `::find_by_value_str` failed to return `nil` for bad values
|
40
|
+
|
41
|
+
## 0.1.1 (19 Nov 2015)
|
42
|
+
|
43
|
+
- Added `::find_by_value_str`
|
44
|
+
|
45
|
+
## 0.1.0 (18 Nov 2015)
|
46
|
+
|
47
|
+
- Initial release
|
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 The Regents of the University of California
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,478 @@
|
|
1
|
+
# TypeIsEnum
|
2
|
+
|
3
|
+
[](https://travis-ci.org/dmolesUC3/typesafe_enum)
|
4
|
+
[](https://codeclimate.com/github/dmolesUC3/typesafe_enum)
|
5
|
+
[](http://inch-ci.org/github/dmolesUC3/typesafe_enum)
|
6
|
+
[](https://github.com/dmolesUC3/typesafe_enum/releases)
|
7
|
+
|
8
|
+
A Ruby implementation of Joshua Bloch's
|
9
|
+
[typesafe enum pattern](http://www.oracle.com/technetwork/java/page1-139488.html#replaceenums),
|
10
|
+
with syntax loosely inspired by [Ruby::Enum](https://github.com/dblock/ruby-enum).
|
11
|
+
|
12
|
+
## Table of contents
|
13
|
+
|
14
|
+
- [Basic usage](#basic-usage)
|
15
|
+
- [Ordering](#ordering)
|
16
|
+
- [String representations](#string-representations)
|
17
|
+
- [Convenience methods on enum classes](#convenience-methods-on-enum-classes)
|
18
|
+
- [::to\_a](#to_a)
|
19
|
+
- [::size](#size)
|
20
|
+
- [::each, ::each\_with\_index, and ::map](#each-each_with_index-and-map)
|
21
|
+
- [::find\_by\_key, ::find\_by\_value, ::find\_by\_ord](#find_by_key-find_by_value-find_by_ord)
|
22
|
+
- [::find\_by\_value\_str](#find_by_value_str)
|
23
|
+
- [Enum classes with methods](#enum-classes-with-methods)
|
24
|
+
- [Enum instances with methods](#enum-instances-with-methods)
|
25
|
+
- [How is this different from Ruby::Enum?](#how-is-this-different-from-rubyenum)
|
26
|
+
- [How is this different from java.lang.Enum?](#how-is-this-different-from-javalangenum)
|
27
|
+
- [Clunkier syntax](#clunkier-syntax)
|
28
|
+
- [No special switch/case support](#no-special-switchcase-support)
|
29
|
+
- [No serialization support](#no-serialization-support)
|
30
|
+
- [No support classes](#no-support-classes)
|
31
|
+
- [Enum classes are not closed](#enum-classes-are-not-closed)
|
32
|
+
- [Contributing](#contributing)
|
33
|
+
|
34
|
+
## Basic usage
|
35
|
+
|
36
|
+
Create a new enum class and a set of instances:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
require 'typesafe_enum'
|
40
|
+
|
41
|
+
class Suit < TypeIsEnum::Enum
|
42
|
+
new :CLUBS
|
43
|
+
new :DIAMONDS
|
44
|
+
new :HEARTS
|
45
|
+
new :SPADES
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
A constant is declared for each instance, with an instance of the new
|
50
|
+
class as the value of that constant:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
Suit::CLUBS
|
54
|
+
# => #<Suit:0x007fe9b3ba2698 @key=:CLUBS, @value="clubs", @ord=0>
|
55
|
+
```
|
56
|
+
|
57
|
+
By default, the `value` of an instance is its `key` symbol, lowercased:
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
Suit::CLUBS.key
|
61
|
+
# => :CLUBS
|
62
|
+
Suit::CLUBS.value
|
63
|
+
# => 'clubs'
|
64
|
+
```
|
65
|
+
|
66
|
+
But you can also declare an explicit `value`:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
class Tarot < TypeIsEnum::ValueEnum
|
70
|
+
new :CUPS, 'Cups'
|
71
|
+
new :COINS, 'Coins'
|
72
|
+
new :WANDS, 'Wands'
|
73
|
+
new :SWORDS, 'Swords'
|
74
|
+
end
|
75
|
+
|
76
|
+
Tarot::CUPS.value
|
77
|
+
# => 'Cups'
|
78
|
+
```
|
79
|
+
|
80
|
+
And `values` need not be strings:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
class Scale < TypeIsEnum::ValueEnum
|
84
|
+
new :DECA, 10
|
85
|
+
new :HECTO, 100
|
86
|
+
new :KILO, 1_000
|
87
|
+
new :MEGA, 1_000_000
|
88
|
+
end
|
89
|
+
|
90
|
+
Scale::KILO.value
|
91
|
+
# => 1000
|
92
|
+
```
|
93
|
+
|
94
|
+
Declaring two instances with the same key will produce an error:
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
class Suit < TypeIsEnum::Enum
|
98
|
+
new :CLUBS
|
99
|
+
new :DIAMONDS
|
100
|
+
new :HEARTS
|
101
|
+
new :SPADES
|
102
|
+
new :SPADES, '♠'
|
103
|
+
end
|
104
|
+
```
|
105
|
+
|
106
|
+
```
|
107
|
+
typesafe_enum/lib/typesafe_enum/base.rb:88:in `valid_key_and_value': Suit::SPADES already exists (NameError)
|
108
|
+
from /Users/dmoles/Work/Stash/typesafe_enum/lib/typesafe_enum/base.rb:98:in `register'
|
109
|
+
from /Users/dmoles/Work/Stash/typesafe_enum/lib/typesafe_enum/base.rb:138:in `block in initialize'
|
110
|
+
from /Users/dmoles/Work/Stash/typesafe_enum/lib/typesafe_enum/base.rb:137:in `class_exec'
|
111
|
+
from /Users/dmoles/Work/Stash/typesafe_enum/lib/typesafe_enum/base.rb:137:in `initialize'
|
112
|
+
from ./scratch.rb:11:in `new'
|
113
|
+
from ./scratch.rb:11:in `<class:Suit>'
|
114
|
+
from ./scratch.rb:6:in `<main>'
|
115
|
+
```
|
116
|
+
|
117
|
+
Likewise two instances with the same value but different keys:
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
class Tarot < TypeIsEnum::ValueEnum
|
121
|
+
new :CUPS, 'Cups'
|
122
|
+
new :COINS, 'Coins'
|
123
|
+
new :WANDS, 'Wands'
|
124
|
+
new :SWORDS, 'Swords'
|
125
|
+
new :STAVES, 'Wands'
|
126
|
+
end
|
127
|
+
```
|
128
|
+
|
129
|
+
```
|
130
|
+
/typesafe_enum/lib/typesafe_enum/base.rb:92:in `valid_key_and_value': A Tarot instance with value 'Wands' already exists (NameError)
|
131
|
+
from /Users/dmoles/Work/Stash/typesafe_enum/lib/typesafe_enum/base.rb:98:in `register'
|
132
|
+
from /Users/dmoles/Work/Stash/typesafe_enum/lib/typesafe_enum/base.rb:138:in `block in initialize'
|
133
|
+
from /Users/dmoles/Work/Stash/typesafe_enum/lib/typesafe_enum/base.rb:137:in `class_exec'
|
134
|
+
from /Users/dmoles/Work/Stash/typesafe_enum/lib/typesafe_enum/base.rb:137:in `initialize'
|
135
|
+
from ./scratch.rb:11:in `new'
|
136
|
+
from ./scratch.rb:11:in `<class:Tarot>'
|
137
|
+
from ./scratch.rb:6:in `<main>'
|
138
|
+
```
|
139
|
+
|
140
|
+
However, declaring an identical key/value pair will be ignored with a warning, to avoid unnecessary errors
|
141
|
+
when, e.g., a declaration file is accidentally loaded twice.
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
class Tarot < TypeIsEnum::ValueEnum
|
145
|
+
new :CUPS, 'Cups'
|
146
|
+
new :COINS, 'Coins'
|
147
|
+
new :WANDS, 'Wands'
|
148
|
+
new :SWORDS, 'Swords'
|
149
|
+
end
|
150
|
+
|
151
|
+
class Tarot < TypeIsEnum::ValueEnum
|
152
|
+
new :CUPS, 'Cups'
|
153
|
+
new :COINS, 'Coins'
|
154
|
+
new :WANDS, 'Wands'
|
155
|
+
new :SWORDS, 'Swords'
|
156
|
+
end
|
157
|
+
|
158
|
+
# => ignoring redeclaration of Tarot::CUPS with value Cups (source: /tmp/duplicate_enum.rb:13:in `new')
|
159
|
+
# => ignoring redeclaration of Tarot::COINS with value Coins (source: /tmp/duplicate_enum.rb:14:in `new')
|
160
|
+
# => ignoring redeclaration of Tarot::WANDS with value Wands (source: /tmp/duplicate_enum.rb:15:in `new')
|
161
|
+
# => ignoring redeclaration of Tarot::SWORDS with value Swords (source: /tmp/duplicate_enum.rb:16:in `new')
|
162
|
+
```
|
163
|
+
|
164
|
+
**Note:** If do you see these warnings, it probably means there's something wrong with your `$LOAD_PATH` (e.g.,
|
165
|
+
the same directory present both via its real path and via a symlink). This can cause all sorts of problems,
|
166
|
+
and Ruby's `require` statement is [known to be not smart enough to deal with it](https://bugs.ruby-lang.org/issues/4403),
|
167
|
+
so it's worth tracking down and fixing the root cause.
|
168
|
+
|
169
|
+
## Ordering
|
170
|
+
|
171
|
+
Enum instances have an ordinal value corresponding to their declaration
|
172
|
+
order:
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
Suit::SPADES.ord
|
176
|
+
# => 3
|
177
|
+
```
|
178
|
+
|
179
|
+
And enum instances are comparable (within a type) based on that order:
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
Suit::SPADES.is_a?(Comparable)
|
183
|
+
# => true
|
184
|
+
Suit::SPADES > Suit::DIAMONDS
|
185
|
+
# => true
|
186
|
+
Suit::SPADES > Tarot::CUPS
|
187
|
+
# ArgumentError: comparison of Suit with Tarot failed
|
188
|
+
```
|
189
|
+
|
190
|
+
## String representations
|
191
|
+
|
192
|
+
The default `to_s` implementation provides the enum's class, key, value,
|
193
|
+
and ordinal, e.g.
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
Suit::DIAMONDS.to_s
|
197
|
+
# => "Suit::DIAMONDS [1] -> diamonds"
|
198
|
+
```
|
199
|
+
|
200
|
+
It can of course be overridden.
|
201
|
+
|
202
|
+
## Convenience methods on enum classes
|
203
|
+
|
204
|
+
### `::to_a`
|
205
|
+
|
206
|
+
Returns an array of the enum instances in declaration order:
|
207
|
+
|
208
|
+
```ruby
|
209
|
+
Tarot.to_a
|
210
|
+
# => [#<Tarot:0x007fd4db30eca8 @key=:CUPS, @value="Cups", @ord=0>, #<Tarot:0x007fd4db30ebe0 @key=:COINS, @value="Coins", @ord=1>, #<Tarot:0x007fd4db30eaf0 @key=:WANDS, @value="Wands", @ord=2>, #<Tarot:0x007fd4db30e9b0 @key=:SWORDS, @value="Swords", @ord=3>]
|
211
|
+
```
|
212
|
+
|
213
|
+
### `::size`
|
214
|
+
|
215
|
+
Returns the number of enum instances:
|
216
|
+
|
217
|
+
```ruby
|
218
|
+
Suit.size
|
219
|
+
# => 4
|
220
|
+
```
|
221
|
+
|
222
|
+
### `::each`, `::each_with_index`, and `::map`
|
223
|
+
|
224
|
+
Iterate over the set of enum instances:
|
225
|
+
|
226
|
+
```ruby
|
227
|
+
Suit.each { |s| puts s.value }
|
228
|
+
# clubs
|
229
|
+
# diamonds
|
230
|
+
# hearts
|
231
|
+
# spades
|
232
|
+
|
233
|
+
Suit.each_with_index { |s, i| puts "#{i}: #{s.key}" }
|
234
|
+
# 0: CLUBS
|
235
|
+
# 1: DIAMONDS
|
236
|
+
# 2: HEARTS
|
237
|
+
# 3: SPADES
|
238
|
+
|
239
|
+
Suit.map(&:value)
|
240
|
+
# => ["clubs", "diamonds", "hearts", "spades"]
|
241
|
+
```
|
242
|
+
|
243
|
+
### `::find_by_key`, `::find_by_value`, `::find_by_ord`
|
244
|
+
|
245
|
+
Look up an enum instance based on its key, value, or ordinal:
|
246
|
+
|
247
|
+
```ruby
|
248
|
+
Tarot.find_by_key(:CUPS)
|
249
|
+
# => #<Tarot:0x007faab19fda40 @key=:CUPS, @value="Cups", @ord=0>
|
250
|
+
Tarot.find_by_value('Wands')
|
251
|
+
# => #<Tarot:0x007faab19fd8b0 @key=:WANDS, @value="Wands", @ord=2>
|
252
|
+
Tarot.find_by_ord(3)
|
253
|
+
# => #<Tarot:0x007faab19fd810 @key=:SWORDS, @value="Swords", @ord=3>
|
254
|
+
```
|
255
|
+
|
256
|
+
### `::find_by_value_str`
|
257
|
+
|
258
|
+
Look up an enum instance based on the string form of its value (as returned by `to_s`) --
|
259
|
+
useful for, e.g., XML or JSON mapping of enums with non-string values:
|
260
|
+
|
261
|
+
```ruby
|
262
|
+
Scale.find_by_value_str('1000000')
|
263
|
+
# => #<Scale:0x007f8513a93810 @key=:MEGA, @value=1000000, @ord=3>
|
264
|
+
```
|
265
|
+
|
266
|
+
## Enum classes with methods
|
267
|
+
|
268
|
+
Enum classes are just classes. They can have methods, and other non-enum constants.
|
269
|
+
(The `:initialize` method for each class, though, is declared programmatically by
|
270
|
+
the base class. If you need to redefine it, be sure to alias and call the original.)
|
271
|
+
|
272
|
+
```ruby
|
273
|
+
class Suit < TypeIsEnum::Enum
|
274
|
+
new :CLUBS
|
275
|
+
new :DIAMONDS
|
276
|
+
new :HEARTS
|
277
|
+
new :SPADES
|
278
|
+
|
279
|
+
ALL_PIPS = %w(♣ ♦ ♥ ♠)
|
280
|
+
|
281
|
+
def pip
|
282
|
+
ALL_PIPS[self.ord]
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
Suit::ALL_PIPS
|
287
|
+
# => ["♣", "♦", "♥", "♠"]
|
288
|
+
|
289
|
+
Suit::CLUBS.pip
|
290
|
+
# => "♣"
|
291
|
+
|
292
|
+
Suit.map(&:pip)
|
293
|
+
# => ["♣", "♦", "♥", "♠"]
|
294
|
+
```
|
295
|
+
|
296
|
+
## Enum instances with methods
|
297
|
+
|
298
|
+
Enum instances can declare their own methods:
|
299
|
+
|
300
|
+
```ruby
|
301
|
+
class Operation < TypeIsEnum::Enum
|
302
|
+
new(:PLUS, '+') do
|
303
|
+
def eval(x, y)
|
304
|
+
x + y
|
305
|
+
end
|
306
|
+
end
|
307
|
+
new(:MINUS, '-') do
|
308
|
+
def eval(x, y)
|
309
|
+
x - y
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
Operation::PLUS.eval(11, 17)
|
315
|
+
# => 28
|
316
|
+
|
317
|
+
Operation::MINUS.eval(28, 11)
|
318
|
+
# => 17
|
319
|
+
|
320
|
+
Operation.map { |op| op.eval(39, 23) }
|
321
|
+
# => [62, 16]
|
322
|
+
```
|
323
|
+
|
324
|
+
## How is this different from [Ruby::Enum](https://github.com/dblock/ruby-enum)?
|
325
|
+
|
326
|
+
[Ruby::Enum](https://github.com/dblock/ruby-enum) is much closer to the classic
|
327
|
+
[C enumeration](https://www.gnu.org/software/gnu-c-manual/gnu-c-manual.html#Enumerations)
|
328
|
+
as seen in C, [C++](https://msdn.microsoft.com/en-us/library/2dzy4k6e.aspx),
|
329
|
+
[C#](https://msdn.microsoft.com/en-us/library/sbbt4032.aspx), and
|
330
|
+
[Objective-C](https://developer.apple.com/library/ios/releasenotes/ObjectiveC/ModernizationObjC/AdoptingModernObjective-C/AdoptingModernObjective-C.html#//apple_ref/doc/uid/TP40014150-CH1-SW6).
|
331
|
+
In C and most C-like languages, an `enum` is simply a named set of `int` values
|
332
|
+
(though C++ and others require an explicit cast to assign an `enum` value to
|
333
|
+
an `int` variable).
|
334
|
+
|
335
|
+
Similarly, a `Ruby::Enum` class is simply a named set of values of any type,
|
336
|
+
with convenience methods for iterating over the set. Usually the values are
|
337
|
+
strings, but they can be of any type.
|
338
|
+
|
339
|
+
```ruby
|
340
|
+
# String enum
|
341
|
+
class Foo
|
342
|
+
include Ruby::Enum
|
343
|
+
|
344
|
+
new :BAR, 'bar'
|
345
|
+
new :BAZ, 'baz'
|
346
|
+
end
|
347
|
+
|
348
|
+
Foo::BAR
|
349
|
+
# => "bar"
|
350
|
+
Foo::BAR == 'bar'
|
351
|
+
# => true
|
352
|
+
|
353
|
+
# Integer enum
|
354
|
+
class Bar
|
355
|
+
include Ruby::Enum
|
356
|
+
|
357
|
+
new :BAR, 1
|
358
|
+
new :BAZ, 2
|
359
|
+
end
|
360
|
+
|
361
|
+
Bar::BAR
|
362
|
+
# => "bar"
|
363
|
+
Bar::BAR == 1
|
364
|
+
# => true
|
365
|
+
```
|
366
|
+
|
367
|
+
Java introduced the concept of "typesafe enums", first as a
|
368
|
+
[design pattern](http://www.oracle.com/technetwork/java/page1-139488.html#replaceenums)
|
369
|
+
and later as a
|
370
|
+
[first-class language construct](https://docs.oracle.com/javase/1.5.0/docs/guide/language/enums.html).
|
371
|
+
In Java, an `Enum` class defines a closed, valued set of _instances of that class,_ rather than
|
372
|
+
of a primitive type such as an `int`, and those instances have all the features of other objects,
|
373
|
+
such as methods, fields, and type membership. Likewise, a `TypeIsEnum` class defines a valued set
|
374
|
+
of instances of that class, rather than of a set of some other type.
|
375
|
+
|
376
|
+
```ruby
|
377
|
+
Suit::CLUBS.is_a?(Suit)
|
378
|
+
# => true
|
379
|
+
Tarot::CUPS == 'Cups'
|
380
|
+
# => false
|
381
|
+
```
|
382
|
+
|
383
|
+
## How is this different from `java.lang.Enum`?
|
384
|
+
|
385
|
+
### Clunkier syntax
|
386
|
+
|
387
|
+
In Java 5+, you can define an enum in one line and instance-specific methods with a pair of braces.
|
388
|
+
|
389
|
+
```java
|
390
|
+
enum CMYKColor {
|
391
|
+
CYAN, MAGENTA, YELLOW, BLACK
|
392
|
+
}
|
393
|
+
|
394
|
+
enum Suit {
|
395
|
+
CLUBS { char pip() { return '♣'; } },
|
396
|
+
DIAMONDS { char pip() { return '♦'; } },
|
397
|
+
HEARTS { char pip() { return '♥'; } },
|
398
|
+
SPADES { char pip() { return '♠'; } };
|
399
|
+
|
400
|
+
abstract char pip();
|
401
|
+
}
|
402
|
+
```
|
403
|
+
|
404
|
+
With `TypeIsEnum`, instance-specific methods require extra parentheses,
|
405
|
+
as shown above, and about the best you can do even for simple enums is something like:
|
406
|
+
|
407
|
+
```ruby
|
408
|
+
class CMYKColor < TypeIsEnum::Enum
|
409
|
+
[:CYAN, :MAGENTA, :YELLOW, :BLACK].each { |c| new c }
|
410
|
+
end
|
411
|
+
```
|
412
|
+
|
413
|
+
### No special `switch`/`case` support
|
414
|
+
|
415
|
+
The Java compiler will warn you if a `switch` statement doesn't include all instances of a Java enum.
|
416
|
+
Ruby doesn't care whether you cover all instances of a `TypeIsEnum`, and in fact it doesn't care if
|
417
|
+
your `when` statements include a mix of enum instances of different classes, or of enum instances and
|
418
|
+
other things. (In some respects this latter is a feature, of course.)
|
419
|
+
|
420
|
+
### No serialization support
|
421
|
+
|
422
|
+
The Java `Enum` class has special code to ensure that enum instances are deserialized to the existing
|
423
|
+
singleton constants. This can be done with Ruby [`Marshal`](http://ruby-doc.org/core-2.2.3/Marshal.html)
|
424
|
+
(by defining `marshal_load`) but it didn't seem worth the trouble, so a deserialized `TypeIsEnum` will
|
425
|
+
not be identical to the original:
|
426
|
+
|
427
|
+
```ruby
|
428
|
+
clubs2 = Marshal.load(Marshal.dump(Suit::CLUBS))
|
429
|
+
Suit::CLUBS.equal?(clubs2)
|
430
|
+
# => false
|
431
|
+
```
|
432
|
+
|
433
|
+
However, `#==`, `#hash`, etc. are `Marshal`-safe:
|
434
|
+
|
435
|
+
```ruby
|
436
|
+
Suit::CLUBS == clubs2
|
437
|
+
# => true
|
438
|
+
clubs2 == Suit::CLUBS
|
439
|
+
# => true
|
440
|
+
Suit::CLUBS.hash == clubs2.hash
|
441
|
+
# => true
|
442
|
+
```
|
443
|
+
|
444
|
+
If this isn't enough, and the lack of object identity across marshalling is a problem, it could be added
|
445
|
+
in a later version. (Pull requests welcome!)
|
446
|
+
|
447
|
+
### No support classes
|
448
|
+
|
449
|
+
Java has `Enum`-specific classes like
|
450
|
+
[`EnumSet`](http://docs.oracle.com/javase/8/docs/api/java/util/EnumSet.html) and
|
451
|
+
[`EnumMap`](http://docs.oracle.com/javase/8/docs/api/java/util/EnumMap.html) that provide special
|
452
|
+
high-performance, optimized versions of its collection interfaces. `TypeIsEnum` doesn't.
|
453
|
+
|
454
|
+
### Enum classes are not closed
|
455
|
+
|
456
|
+
It's Ruby, so even though `:new` is private to each enum class, you
|
457
|
+
can work around that in various ways:
|
458
|
+
|
459
|
+
```ruby
|
460
|
+
Suit.send(:new, :JOKERS)
|
461
|
+
# => #<Suit:0x007fc9e44e4778 @key=:JOKERS, @value="jokers", @ord=4>
|
462
|
+
|
463
|
+
class Tarot
|
464
|
+
new :MAJOR_ARCANA, 'Major Arcana'
|
465
|
+
end
|
466
|
+
# => #<Tarot:0x007f8513b39b20 @key=:MAJOR_ARCANA, @value="Major Arcana", @ord=4>
|
467
|
+
|
468
|
+
Suit.map(&:key)
|
469
|
+
# => [:CLUBS, :DIAMONDS, :HEARTS, :SPADES, :JOKERS]
|
470
|
+
|
471
|
+
Tarot.map(&:key)
|
472
|
+
# => [:CUPS, :COINS, :WANDS, :SWORDS, :MAJOR_ARCANA]
|
473
|
+
```
|
474
|
+
|
475
|
+
## Contributing
|
476
|
+
|
477
|
+
Pull requests are welcome, but please make sure the tests pass, the code has 100% coverage, and the
|
478
|
+
code style passes Rubocop. (The default rake task should check all of these for you.)
|