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
data/Rakefile
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# ------------------------------------------------------------
|
2
|
+
# RSpec
|
3
|
+
|
4
|
+
require 'rspec/core'
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
|
7
|
+
namespace :spec do
|
8
|
+
|
9
|
+
desc 'Run all unit tests'
|
10
|
+
RSpec::Core::RakeTask.new(:unit) do |task|
|
11
|
+
task.rspec_opts = %w(--color --format documentation --order default)
|
12
|
+
task.pattern = 'unit/**/*_spec.rb'
|
13
|
+
end
|
14
|
+
|
15
|
+
task all: [:unit]
|
16
|
+
end
|
17
|
+
|
18
|
+
desc 'Run all tests'
|
19
|
+
task spec: 'spec:all'
|
20
|
+
|
21
|
+
# ------------------------------------------------------------
|
22
|
+
# Coverage
|
23
|
+
|
24
|
+
desc 'Run all unit tests with coverage'
|
25
|
+
task :coverage do
|
26
|
+
ENV['COVERAGE'] = 'true'
|
27
|
+
Rake::Task['spec:unit'].execute
|
28
|
+
end
|
29
|
+
|
30
|
+
# ------------------------------------------------------------
|
31
|
+
# RuboCop
|
32
|
+
|
33
|
+
require 'rubocop/rake_task'
|
34
|
+
RuboCop::RakeTask.new
|
35
|
+
|
36
|
+
# ------------------------------------------------------------
|
37
|
+
# TODOs
|
38
|
+
|
39
|
+
desc 'List TODOs (from spec/todo.rb)'
|
40
|
+
RSpec::Core::RakeTask.new(:todo) do |task|
|
41
|
+
task.rspec_opts = %w(--color --format documentation --order default)
|
42
|
+
task.pattern = 'todo.rb'
|
43
|
+
end
|
44
|
+
|
45
|
+
# ------------------------------------------------------------
|
46
|
+
# Defaults
|
47
|
+
|
48
|
+
desc 'Run unit tests, check test coverage, run acceptance tests, check code style'
|
49
|
+
task default: [:coverage, :rubocop]
|
data/lib/type_is_enum.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
# A Ruby implementation of Joshua Bloch's
|
2
|
+
# [typesafe enum pattern](http://www.oracle.com/technetwork/java/page1-139488.html#replaceenums)
|
3
|
+
module TypeIsEnum
|
4
|
+
# Base class for typesafe enum classes.
|
5
|
+
class Enum
|
6
|
+
include Comparable
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
# Returns an array of the enum instances in declaration order
|
11
|
+
# @return [Array<self>] All instances of this enum, in declaration order
|
12
|
+
def to_a
|
13
|
+
as_array.dup
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the number of enum instances
|
17
|
+
# @return [Integer] the number of instances
|
18
|
+
def size
|
19
|
+
as_array ? as_array.length : 0
|
20
|
+
end
|
21
|
+
|
22
|
+
# Iterates over the set of enum instances
|
23
|
+
# @yield [self] Each instance of this enum, in declaration order
|
24
|
+
# @return [Array<self>] All instances of this enum, in declaration order
|
25
|
+
def each(&block)
|
26
|
+
to_a.each(&block)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Iterates over the set of enum instances
|
30
|
+
# @yield [self, Integer] Each instance of this enum, in declaration order,
|
31
|
+
# with its ordinal index
|
32
|
+
# @return [Array<self>] All instances of this enum, in declaration order
|
33
|
+
def each_with_index(&block)
|
34
|
+
to_a.each_with_index(&block)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Iterates over the set of enum instances
|
38
|
+
# @yield [self] Each instance of this enum, in declaration order
|
39
|
+
# @return [Array] An array containing the result of applying `&block`
|
40
|
+
# to each instance of this enum, in instance declaration order
|
41
|
+
def map(&block)
|
42
|
+
to_a.map(&block)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Looks up an enum instance based on its key
|
46
|
+
# @param key [Symbol] the key to look up
|
47
|
+
# @return [self, nil] the corresponding enum instance, or nil
|
48
|
+
def find_by_key(key)
|
49
|
+
by_key[key]
|
50
|
+
end
|
51
|
+
|
52
|
+
# Looks up an enum instance based on its ordinal
|
53
|
+
# @param ord [Integer] the ordinal to look up
|
54
|
+
# @return [self, nil] the corresponding enum instance, or nil
|
55
|
+
def find_by_ord(ord)
|
56
|
+
return nil if ord < 0 || ord > size
|
57
|
+
as_array[ord]
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def add(key, *args, &block)
|
63
|
+
fail TypeError, "#{key} is not a symbol" unless key.is_a?(Symbol)
|
64
|
+
obj = new(*args)
|
65
|
+
obj.instance_variable_set :@key, key
|
66
|
+
obj.instance_variable_set :@ord, size
|
67
|
+
class_exec(obj) do |instance|
|
68
|
+
register(instance)
|
69
|
+
instance.instance_eval(&block) if block_given?
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def by_key
|
74
|
+
@by_key ||= {}
|
75
|
+
end
|
76
|
+
|
77
|
+
def as_array
|
78
|
+
@as_array ||= []
|
79
|
+
end
|
80
|
+
|
81
|
+
def valid_key(instance)
|
82
|
+
key = instance.key
|
83
|
+
if find_by_key(key)
|
84
|
+
warn("ignoring redeclaration of #{name}::#{key} (source: #{caller[4]})")
|
85
|
+
nil
|
86
|
+
else
|
87
|
+
key
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def register(instance)
|
92
|
+
key = valid_key(instance)
|
93
|
+
return unless key
|
94
|
+
|
95
|
+
const_set(key.to_s, instance)
|
96
|
+
by_key[key] = instance
|
97
|
+
as_array << instance
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# The symbol key for the enum instance
|
102
|
+
# @return [Symbol] the key
|
103
|
+
attr_reader :key
|
104
|
+
|
105
|
+
# The ordinal of the enum instance, in declaration order
|
106
|
+
# @return [Integer] the ordinal
|
107
|
+
attr_reader :ord
|
108
|
+
|
109
|
+
# Compares two instances of the same enum class based on their declaration order
|
110
|
+
# @param other [self] the enum instance to compare
|
111
|
+
# @return [Integer, nil] -1 if this value precedes `other`; 0 if the two are
|
112
|
+
# the same enum instance; 1 if this value follows `other`; `nil` if `other`
|
113
|
+
# is not an instance of this enum class
|
114
|
+
def <=>(other)
|
115
|
+
ord <=> other.ord if self.class == other.class
|
116
|
+
end
|
117
|
+
|
118
|
+
# Generates a Fixnum hash value for this enum instance
|
119
|
+
# @return [Fixnum] the hash value
|
120
|
+
def hash
|
121
|
+
@hash ||= begin
|
122
|
+
result = 17
|
123
|
+
result = 31 * result + self.class.hash
|
124
|
+
result = 31 * result + ord
|
125
|
+
result.is_a?(Fixnum) ? result : result.hash
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def name
|
130
|
+
key.to_s
|
131
|
+
end
|
132
|
+
|
133
|
+
def to_s
|
134
|
+
"#{self.class}::#{key} [#{ord}]"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
data/spec/.rubocop.yml
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# ------------------------------------------------------------
|
2
|
+
# SimpleCov setup
|
3
|
+
|
4
|
+
if ENV['COVERAGE']
|
5
|
+
require 'simplecov'
|
6
|
+
require 'simplecov-console'
|
7
|
+
|
8
|
+
SimpleCov.minimum_coverage 100
|
9
|
+
SimpleCov.start do
|
10
|
+
add_filter '/spec/'
|
11
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
12
|
+
SimpleCov::Formatter::HTMLFormatter,
|
13
|
+
SimpleCov::Formatter::Console,
|
14
|
+
]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# ------------------------------------------------------------
|
19
|
+
# Rspec configuration
|
20
|
+
|
21
|
+
require 'rspec'
|
22
|
+
|
23
|
+
RSpec.configure do |config|
|
24
|
+
config.raise_errors_for_deprecations!
|
25
|
+
config.mock_with :rspec
|
26
|
+
end
|
27
|
+
|
28
|
+
# ------------------------------------------------------------
|
29
|
+
# TypesafeEnum
|
30
|
+
|
31
|
+
require 'type_is_enum'
|
@@ -0,0 +1,367 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
class Suit < TypeIsEnum::Enum
|
5
|
+
add :CLUBS
|
6
|
+
add :DIAMONDS
|
7
|
+
add :HEARTS
|
8
|
+
add :SPADES
|
9
|
+
end
|
10
|
+
|
11
|
+
class Tarot < TypeIsEnum::ValueEnum
|
12
|
+
add :CUPS, 'Cups'
|
13
|
+
add :COINS, 'Coins'
|
14
|
+
add :WANDS, 'Wands'
|
15
|
+
add :SWORDS, 'Swords'
|
16
|
+
end
|
17
|
+
|
18
|
+
class RGBColor < TypeIsEnum::ValueEnum
|
19
|
+
add :RED, :red
|
20
|
+
add :GREEN, :green
|
21
|
+
add :BLUE, :blue
|
22
|
+
end
|
23
|
+
|
24
|
+
class Scale < TypeIsEnum::ValueEnum
|
25
|
+
add :DECA, 10
|
26
|
+
add :HECTO, 100
|
27
|
+
add :KILO, 1_000
|
28
|
+
add :MEGA, 1_000_000
|
29
|
+
end
|
30
|
+
|
31
|
+
class Car < TypeIsEnum::Enum
|
32
|
+
def initialize(price, coolness)
|
33
|
+
@price = price
|
34
|
+
@coolness = coolness
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_reader :price, :coolness
|
38
|
+
|
39
|
+
add :Audi, 25_000, 4
|
40
|
+
add :Mercedes, 30_000, 6
|
41
|
+
add :Toyota, 10_000, 2
|
42
|
+
end
|
43
|
+
|
44
|
+
module TypeIsEnum
|
45
|
+
describe Enum do
|
46
|
+
|
47
|
+
it 'allows custom constructors with multiple arguments' do
|
48
|
+
expect(Car.to_a).to contain_exactly(
|
49
|
+
have_attributes(price: 25_000, coolness: 4),
|
50
|
+
have_attributes(price: 30_000, coolness: 6),
|
51
|
+
have_attributes(price: 10_000, coolness: 2)
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'has a name that is a key as string' do
|
56
|
+
expect(Car.to_a.map(&:name)).to eq(['Audi', 'Mercedes', 'Toyota'])
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '::add' do
|
60
|
+
it ' adds a constant enum value' do
|
61
|
+
enum = Suit::CLUBS
|
62
|
+
expect(enum).to be_a(Suit)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'insists symbols be symbols' do
|
66
|
+
expect do
|
67
|
+
class ::StringKeys < ValueEnum
|
68
|
+
add 'spades', 'spades'
|
69
|
+
end
|
70
|
+
end.to raise_error(TypeError)
|
71
|
+
expect(::StringKeys.to_a).to be_empty
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'insists symbols be uppercase' do
|
75
|
+
expect do
|
76
|
+
class ::LowerCaseKeys < ValueEnum
|
77
|
+
add :spades, 'spades'
|
78
|
+
end
|
79
|
+
end.to raise_error(NameError)
|
80
|
+
expect(::LowerCaseKeys.to_a).to be_empty
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'disallows nil keys' do
|
84
|
+
expect do
|
85
|
+
class ::NilKeys < ValueEnum
|
86
|
+
add nil, 'nil'
|
87
|
+
end
|
88
|
+
end.to raise_error(TypeError)
|
89
|
+
expect(::NilKeys.to_a).to be_empty
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'allows, but ignores redeclaration of identical instances' do
|
93
|
+
class ::IdenticalInstances < ValueEnum
|
94
|
+
add :SPADES, 'spades'
|
95
|
+
end
|
96
|
+
expect(::IdenticalInstances).to receive(:warn).with(a_string_matching(/ignoring redeclaration of IdenticalInstances::SPADES/))
|
97
|
+
class ::IdenticalInstances < ValueEnum
|
98
|
+
add :SPADES, 'spades'
|
99
|
+
end
|
100
|
+
expect(::IdenticalInstances.to_a).to eq([::IdenticalInstances::SPADES])
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'is private' do
|
104
|
+
expect { Tarot.add(:PENTACLES) }.to raise_error(NoMethodError)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe '::to_a' do
|
109
|
+
it 'returns the values as an array' do
|
110
|
+
expect(Suit.to_a).to eq([Suit::CLUBS, Suit::DIAMONDS, Suit::HEARTS, Suit::SPADES])
|
111
|
+
end
|
112
|
+
it 'returns a copy' do
|
113
|
+
array = Suit.to_a
|
114
|
+
array.clear
|
115
|
+
expect(array.empty?).to eq(true)
|
116
|
+
expect(Suit.to_a).to eq([Suit::CLUBS, Suit::DIAMONDS, Suit::HEARTS, Suit::SPADES])
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe '::size' do
|
121
|
+
it 'returns the number of enum instnaces' do
|
122
|
+
expect(Suit.size).to eq(4)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe '::each' do
|
127
|
+
it 'iterates the enum values' do
|
128
|
+
expected = [Suit::CLUBS, Suit::DIAMONDS, Suit::HEARTS, Suit::SPADES]
|
129
|
+
index = 0
|
130
|
+
Suit.each do |s|
|
131
|
+
expect(s).to be(expected[index])
|
132
|
+
index += 1
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
describe '::each_with_index' do
|
138
|
+
it 'iterates the enum values with indices' do
|
139
|
+
expected = [Suit::CLUBS, Suit::DIAMONDS, Suit::HEARTS, Suit::SPADES]
|
140
|
+
Suit.each_with_index do |s, index|
|
141
|
+
expect(s).to be(expected[index])
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
describe '::map' do
|
147
|
+
it 'maps enum values' do
|
148
|
+
all_keys = Suit.map(&:key)
|
149
|
+
expect(all_keys).to eq([:CLUBS, :DIAMONDS, :HEARTS, :SPADES])
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe '#<=>' do
|
154
|
+
it 'orders enum instances' do
|
155
|
+
Suit.each_with_index do |s1, i1|
|
156
|
+
Suit.each_with_index do |s2, i2|
|
157
|
+
expect(s1 <=> s2).to eq(i1 <=> i2)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
describe '#==' do
|
164
|
+
it 'returns true for identical instances, false otherwise' do
|
165
|
+
Suit.each do |s1|
|
166
|
+
Suit.each do |s2|
|
167
|
+
identical = s1.equal?(s2)
|
168
|
+
expect(s1 == s2).to eq(identical)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'reports instances as unequal to nil and other objects' do
|
174
|
+
a_nil_value = nil
|
175
|
+
Suit.each do |s|
|
176
|
+
expect(s.nil?).to eq(false)
|
177
|
+
expect(s == a_nil_value).to eq(false)
|
178
|
+
Tarot.each do |t|
|
179
|
+
expect(s == t).to eq(false)
|
180
|
+
expect(t == s).to eq(false)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'survives marshalling' do
|
186
|
+
Suit.each do |s1|
|
187
|
+
dump = Marshal.dump(s1)
|
188
|
+
s2 = Marshal.load(dump)
|
189
|
+
expect(s1 == s2).to eq(true)
|
190
|
+
expect(s2 == s1).to eq(true)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
describe '#!=' do
|
196
|
+
it 'returns false for identical instances, true otherwise' do
|
197
|
+
Suit.each do |s1|
|
198
|
+
Suit.each do |s2|
|
199
|
+
different = !s1.equal?(s2)
|
200
|
+
expect(s1 != s2).to eq(different)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
it 'reports instances as unequal to nil and other objects' do
|
206
|
+
a_nil_value = nil
|
207
|
+
Suit.each do |s|
|
208
|
+
expect(s != a_nil_value).to eq(true)
|
209
|
+
Tarot.each do |t|
|
210
|
+
expect(s != t).to eq(true)
|
211
|
+
expect(t != s).to eq(true)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
describe '#hash' do
|
218
|
+
it 'gives consistent values' do
|
219
|
+
Suit.each do |s1|
|
220
|
+
Suit.each do |s2|
|
221
|
+
expect(s1.hash == s2.hash).to eq(s1 == s2)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'returns different values for different types' do
|
227
|
+
class Suit2 < ValueEnum
|
228
|
+
add :CLUBS, 'Clubs'
|
229
|
+
add :DIAMONDS, 'Diamonds'
|
230
|
+
add :HEARTS, 'Hearts'
|
231
|
+
add :SPADES, 'Spades'
|
232
|
+
end
|
233
|
+
|
234
|
+
Suit.each do |s1|
|
235
|
+
s2 = Suit2.find_by_key(s1.key)
|
236
|
+
expect(s1.hash == s2.hash).to eq(false)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
it 'survives marshalling' do
|
241
|
+
Suit.each do |s1|
|
242
|
+
dump = Marshal.dump(s1)
|
243
|
+
s2 = Marshal.load(dump)
|
244
|
+
expect(s2.hash).to eq(s1.hash)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
it 'always returns a Fixnum' do
|
249
|
+
Suit.each do |s1|
|
250
|
+
expect(s1.hash).to be_a(Fixnum)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
describe '#eql?' do
|
256
|
+
it 'is consistent with #hash' do
|
257
|
+
Suit.each do |s1|
|
258
|
+
Suit.each do |s2|
|
259
|
+
expect(s1.eql?(s2)).to eq(s1.hash == s2.hash)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
describe '#key' do
|
266
|
+
it 'returns the symbol key of the enum instance' do
|
267
|
+
expected = [:CLUBS, :DIAMONDS, :HEARTS, :SPADES]
|
268
|
+
Suit.each_with_index do |s, index|
|
269
|
+
expect(s.key).to eq(expected[index])
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
describe '#ord' do
|
275
|
+
it 'returns the ord value of the enum instance' do
|
276
|
+
Suit.each_with_index do |s, index|
|
277
|
+
expect(s.ord).to eq(index)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
describe '#to_s' do
|
283
|
+
it 'provides an informative string' do
|
284
|
+
aggregate_failures 'informative string' do
|
285
|
+
[Suit, Tarot, RGBColor, Scale].each do |ec|
|
286
|
+
ec.each do |ev|
|
287
|
+
result = ev.to_s
|
288
|
+
[ec.to_s, ev.key, ev.ord].each do |info|
|
289
|
+
expect(result).to include("#{info}")
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
describe '::find_by_key' do
|
298
|
+
it 'maps symbol keys to enum instances' do
|
299
|
+
keys = [:CLUBS, :DIAMONDS, :HEARTS, :SPADES]
|
300
|
+
expected = Suit.to_a
|
301
|
+
keys.each_with_index do |k, index|
|
302
|
+
expect(Suit.find_by_key(k)).to be(expected[index])
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
it 'returns nil for invalid keys' do
|
307
|
+
expect(Suit.find_by_key(:WANDS)).to be_nil
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
describe '::find_by_ord' do
|
312
|
+
it 'maps ordinal indices to enum instances' do
|
313
|
+
Suit.each do |s|
|
314
|
+
expect(Suit.find_by_ord(s.ord)).to be(s)
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
it 'returns nil for negative indices' do
|
319
|
+
expect(Suit.find_by_ord(-1)).to be_nil
|
320
|
+
end
|
321
|
+
|
322
|
+
it 'returns nil for out-of-range indices' do
|
323
|
+
expect(Suit.find_by_ord(Suit.size)).to be_nil
|
324
|
+
end
|
325
|
+
|
326
|
+
it 'returns nil for invalid indices' do
|
327
|
+
expect(Suit.find_by_ord(100)).to be_nil
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
it 'supports case statements' do
|
332
|
+
def pip(suit)
|
333
|
+
case suit
|
334
|
+
when Suit::CLUBS
|
335
|
+
'♣'
|
336
|
+
when Suit::DIAMONDS
|
337
|
+
'♦'
|
338
|
+
when Suit::HEARTS
|
339
|
+
'♥'
|
340
|
+
when Suit::SPADES
|
341
|
+
'♠'
|
342
|
+
else
|
343
|
+
fail "unknown suit: #{self}"
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
expect(Suit.map { |s| pip(s) }).to eq(%w(♣ ♦ ♥ ♠))
|
348
|
+
end
|
349
|
+
|
350
|
+
it 'supports "inner class" methods' do
|
351
|
+
class Operation < ValueEnum
|
352
|
+
add(:PLUS, '+') do
|
353
|
+
def eval(x, y)
|
354
|
+
x + y
|
355
|
+
end
|
356
|
+
end
|
357
|
+
add(:MINUS, '-') do
|
358
|
+
def eval(x, y)
|
359
|
+
x - y
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
expect(Operation.map { |op| op.eval(39, 23) }).to eq([39 + 23, 39 - 23])
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|