enum-x 1.0.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 +29 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +42 -0
- data/LICENSE.txt +22 -0
- data/README.md +74 -0
- data/Rakefile +1 -0
- data/enum-x.gemspec +26 -0
- data/lib/enum-x.rb +2 -0
- data/lib/enum_x/dsl.rb +389 -0
- data/lib/enum_x/monkey.rb +51 -0
- data/lib/enum_x/railtie.rb +14 -0
- data/lib/enum_x/value.rb +164 -0
- data/lib/enum_x/value_list.rb +69 -0
- data/lib/enum_x/version.rb +3 -0
- data/lib/enum_x.rb +336 -0
- data/spec/enum_x/dsl_spec.rb +345 -0
- data/spec/enum_x_spec.rb +365 -0
- data/spec/spec_helper.rb +19 -0
- metadata +136 -0
@@ -0,0 +1,345 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'active_model'
|
3
|
+
|
4
|
+
describe EnumX::DSL do
|
5
|
+
|
6
|
+
let(:klass) do
|
7
|
+
Class.new do
|
8
|
+
include ActiveModel::Validations; include EnumX::DSL
|
9
|
+
def self.model_name; ActiveModel::Name.new(self, nil, 'TestClass') end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
let(:object) { klass.new }
|
13
|
+
let(:statuses) { EnumX.new(:statuses, %w[ new sent ]) }
|
14
|
+
let(:alternate_statuses) { EnumX.new(:statuses, %w[ new sent delivered ]) }
|
15
|
+
let(:roles) { EnumX.new(:roles, %w[ admin user ]) }
|
16
|
+
|
17
|
+
before do
|
18
|
+
allow(EnumX).to receive(:[]) { |name| case name.to_s when 'statuses' then statuses when 'alternate_statuses' then alternate_statuses else roles end }
|
19
|
+
allow(EnumX).to receive(:statuses) { statuses }
|
20
|
+
allow(EnumX).to receive(:alternate_statuses) { alternate_statuses }
|
21
|
+
allow(EnumX).to receive(:roles) { roles }
|
22
|
+
end
|
23
|
+
|
24
|
+
describe 'convenience accessors' do
|
25
|
+
|
26
|
+
it "should create a convenience accessor for the enum (single)" do
|
27
|
+
klass.class_eval { attr_accessor :status; enum :status }
|
28
|
+
expect(klass.statuses).to be(statuses)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should create a convenience accessor to an alternate enum if provided" do
|
32
|
+
klass.class_eval { attr_accessor :status; enum :status, :alternate_statuses }
|
33
|
+
expect(klass.statuses).to be(alternate_statuses)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should create a convience accessor to an ad-hoc enum" do
|
37
|
+
use_this_enum = EnumX.new(:alternative, %w[ one two three ])
|
38
|
+
klass.class_eval { attr_accessor :status; enum :status, use_this_enum }
|
39
|
+
expect(klass.statuses).to be(use_this_enum)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should create a convenience accessor for the enum flags" do
|
43
|
+
klass.class_eval { attr_accessor :roles; enum :roles, :flags => true }
|
44
|
+
expect(klass.roles).to be(roles)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should be accessible on a subclass as well" do
|
48
|
+
subclass = Class.new(klass)
|
49
|
+
klass.class_eval { attr_accessor :status; enum :status }
|
50
|
+
expect(subclass.statuses).to be(statuses)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "reader registration" do
|
56
|
+
context "when the class has an existing attribute_reader" do
|
57
|
+
before { klass.class_eval { attr_accessor :status; enum :status } }
|
58
|
+
|
59
|
+
it "should use the original reader upon reading" do
|
60
|
+
expect(object).to receive(:status_without_enums).and_return(:new)
|
61
|
+
expect(object.status).to be(EnumX.statuses[:new])
|
62
|
+
end
|
63
|
+
it "should call the original writer upon writing" do
|
64
|
+
expect(object).to receive(:status_without_enums=).with(EnumX.statuses[:new])
|
65
|
+
object.status = :new
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "when the class has a read_attribute and write_attribute method (ActiveRecord)" do
|
70
|
+
before do
|
71
|
+
klass.class_eval do
|
72
|
+
# These will be stubbed later.
|
73
|
+
def read_attribute(attribute) end
|
74
|
+
def write_attribute(attribute, value) end
|
75
|
+
enum :status
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should use read_attribute upon reading" do
|
80
|
+
expect(object).to receive(:read_attribute).with(:status).and_return(:new)
|
81
|
+
expect(object.status).to be(EnumX.statuses[:new])
|
82
|
+
end
|
83
|
+
it "should call the original writer upon writing" do
|
84
|
+
expect(object).to receive(:write_attribute).with(:status, EnumX.statuses[:new])
|
85
|
+
object.status = :new
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context "when the class has neither" do
|
90
|
+
it "should raise an error" do
|
91
|
+
expect{ klass.class_eval{ enum :status } }.to raise_error(/cannot overwrite/)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe "single enum" do
|
97
|
+
|
98
|
+
before do
|
99
|
+
klass.class_eval { attr_accessor :status; enum :status }
|
100
|
+
end
|
101
|
+
|
102
|
+
context "validation" do
|
103
|
+
|
104
|
+
it "should allow any value which is any of the enum values" do
|
105
|
+
object.status = :new
|
106
|
+
expect(object).to be_valid
|
107
|
+
end
|
108
|
+
|
109
|
+
it "shoult not allow any other value" do
|
110
|
+
object.status = :doesnotexist
|
111
|
+
expect(object).to_not be_valid
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should allow a blank value by default" do
|
115
|
+
object.status = nil
|
116
|
+
expect(object).to be_valid
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should not allow a blank value if :allow_blank => false" do
|
120
|
+
klass.class_eval { attr_accessor :status2; enum :status2, :status, :allow_blank => false }
|
121
|
+
object.status2 = nil
|
122
|
+
expect(object).to_not be_valid
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
context "reading" do
|
128
|
+
|
129
|
+
it "should convert a Symbol into an enum value" do
|
130
|
+
object.instance_variable_set('@status', :new)
|
131
|
+
expect(object.status).to be(EnumX.statuses[:new])
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should convert a String into an enum value" do
|
135
|
+
object.instance_variable_set('@status', 'new')
|
136
|
+
expect(object.status).to be(EnumX.statuses[:new])
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should keep any EnumX::Value instance" do
|
140
|
+
object.instance_variable_set('@status', EnumX.statuses[:new])
|
141
|
+
expect(object.status).to be(EnumX.statuses[:new])
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should pass the original value if no corresponding enum value was found" do
|
145
|
+
object.instance_variable_set('@status', :doesnotexist)
|
146
|
+
expect(object.status).to be_a(Symbol)
|
147
|
+
expect(object.status).to be(:doesnotexist)
|
148
|
+
end
|
149
|
+
|
150
|
+
it "should pass a nil value through" do
|
151
|
+
object.instance_variable_set('@status', nil)
|
152
|
+
expect(object.status).to be_nil
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
|
157
|
+
context "writing" do
|
158
|
+
|
159
|
+
it "should convert a Symbol into an enum value" do
|
160
|
+
object.status = :new
|
161
|
+
expect(object.instance_variable_get('@status')).to be(EnumX.statuses[:new])
|
162
|
+
end
|
163
|
+
|
164
|
+
it "should convert a String into an enum value" do
|
165
|
+
object.status = 'new'
|
166
|
+
expect(object.instance_variable_get('@status')).to be(EnumX.statuses[:new])
|
167
|
+
end
|
168
|
+
|
169
|
+
it "should keep any EnumX::Value instance" do
|
170
|
+
object.status = EnumX.statuses[:new]
|
171
|
+
expect(object.instance_variable_get('@status')).to be(EnumX.statuses[:new])
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should pass the original value if no corresponding enum value was found" do
|
175
|
+
object.status = :doesnotexist
|
176
|
+
expect(object.instance_variable_get('@status')).to be_a(Symbol)
|
177
|
+
expect(object.instance_variable_get('@status')).to be(:doesnotexist)
|
178
|
+
end
|
179
|
+
|
180
|
+
it "should pass a nil value through" do
|
181
|
+
object.status = nil
|
182
|
+
expect(object.instance_variable_get('@status')).to be_nil
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
describe "enum flags (multiple enum)" do
|
190
|
+
|
191
|
+
before do
|
192
|
+
klass.class_eval { attr_accessor :roles; enum :roles, :flags => true }
|
193
|
+
end
|
194
|
+
|
195
|
+
context "validation" do
|
196
|
+
|
197
|
+
it "should allow a single value" do
|
198
|
+
object.roles = :admin
|
199
|
+
expect(object).to be_valid
|
200
|
+
end
|
201
|
+
|
202
|
+
it "should allow an array with only valid values" do
|
203
|
+
object.roles = [ :admin, :user ]
|
204
|
+
expect(object).to be_valid
|
205
|
+
end
|
206
|
+
|
207
|
+
it "shoult not allow any invalid value to exist" do
|
208
|
+
object.roles = [ :admin, :doesnotexist ]
|
209
|
+
expect(object).to_not be_valid
|
210
|
+
end
|
211
|
+
|
212
|
+
it "should allow a nil value by default" do
|
213
|
+
object.roles = nil
|
214
|
+
expect(object).to be_valid
|
215
|
+
end
|
216
|
+
|
217
|
+
it "should allow an empty array value by default" do
|
218
|
+
object.roles = []
|
219
|
+
expect(object).to be_valid
|
220
|
+
end
|
221
|
+
|
222
|
+
it "should not allow a nil value or empty array if :allow_blank => false" do
|
223
|
+
klass.class_eval { attr_accessor :roles2; enum :roles2, :roles, :allow_blank => false }
|
224
|
+
object.roles2 = nil
|
225
|
+
expect(object).to_not be_valid
|
226
|
+
|
227
|
+
object.roles2 = []
|
228
|
+
expect(object).to_not be_valid
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
232
|
+
|
233
|
+
context "reading" do
|
234
|
+
|
235
|
+
it "should convert an array of Symbols and Strings into a value list" do
|
236
|
+
object.instance_variable_set('@roles', [ :admin, 'user' ])
|
237
|
+
expect(object.roles).to be_a(EnumX::ValueList)
|
238
|
+
expect(object.roles).to eq([ EnumX.roles[:admin], EnumX.roles[:user] ])
|
239
|
+
end
|
240
|
+
|
241
|
+
it "should convert a single Symbol into a value list" do
|
242
|
+
object.instance_variable_set('@roles', :admin)
|
243
|
+
expect(object.roles).to be_a(EnumX::ValueList)
|
244
|
+
expect(object.roles).to eq([ EnumX.roles[:admin] ])
|
245
|
+
end
|
246
|
+
|
247
|
+
it "should convert a single String into a value list" do
|
248
|
+
object.instance_variable_set('@roles', 'admin')
|
249
|
+
expect(object.roles).to be_a(EnumX::ValueList)
|
250
|
+
expect(object.roles).to eq([ EnumX.roles[:admin] ])
|
251
|
+
end
|
252
|
+
|
253
|
+
it "should convert a single EnumX::Value into a value list" do
|
254
|
+
object.instance_variable_set('@roles', EnumX.roles[:admin])
|
255
|
+
expect(object.roles).to be_a(EnumX::ValueList)
|
256
|
+
expect(object.roles).to eq([ EnumX.roles[:admin] ])
|
257
|
+
end
|
258
|
+
|
259
|
+
it "should keep any EnumX::ValueList instance" do
|
260
|
+
list = EnumX::ValueList.new(EnumX.roles, [ :admin, :user ])
|
261
|
+
object.instance_variable_set('@roles', list)
|
262
|
+
expect(object.roles).to be(list)
|
263
|
+
end
|
264
|
+
|
265
|
+
it "should keep invalid enum values as their originals" do
|
266
|
+
object.instance_variable_set('@roles', [ :admin, :doesnotexist ])
|
267
|
+
expect(object.roles).to be_a(EnumX::ValueList)
|
268
|
+
expect(object.roles).to eq([ EnumX.roles[:admin], :doesnotexist ])
|
269
|
+
end
|
270
|
+
|
271
|
+
it "should pass a nil value through" do
|
272
|
+
object.instance_variable_set('@roles', nil)
|
273
|
+
expect(object.roles).to be_nil
|
274
|
+
end
|
275
|
+
|
276
|
+
end
|
277
|
+
|
278
|
+
describe 'sifters' do
|
279
|
+
|
280
|
+
# Note - this doesn't check the syntactical correctness of the sifter. This is hard to test without
|
281
|
+
# depending on the correct workings of Squeel. They have been manually tested.
|
282
|
+
#
|
283
|
+
# Usage:
|
284
|
+
#
|
285
|
+
# klass.where{ sift(:roles2_include, 'admin') }.to_sql
|
286
|
+
# # => SELECT * FROM `<table>` WHERE `<table>`.`roles2` LIKE '%|admin|%'
|
287
|
+
it "should create a sifter <enum>_include" do
|
288
|
+
expect(klass).to receive(:sifter).with(:roles2_include)
|
289
|
+
expect(klass).to receive(:sifter).with(:roles2_exclude)
|
290
|
+
klass.class_eval { attr_accessor :roles2; enum :roles2, :flags => true }
|
291
|
+
end
|
292
|
+
|
293
|
+
end
|
294
|
+
|
295
|
+
end
|
296
|
+
|
297
|
+
describe "mnemonics" do
|
298
|
+
|
299
|
+
it "should create an mnemonic for each enum value (single enum)" do
|
300
|
+
klass.class_eval { attr_accessor :status; enum :status, :mnemonics => true }
|
301
|
+
|
302
|
+
object.status = :new
|
303
|
+
expect(object.new?).to eql(true)
|
304
|
+
expect(object.sent?).to eql(false)
|
305
|
+
|
306
|
+
object.status = :sent
|
307
|
+
expect(object.new?).to eql(false)
|
308
|
+
expect(object.sent?).to eql(true)
|
309
|
+
end
|
310
|
+
|
311
|
+
it "should create an mnemonic for each enum value (flags enum)" do
|
312
|
+
klass.class_eval { attr_accessor :roles; enum :roles, :flags => true, :mnemonics => true }
|
313
|
+
|
314
|
+
object.roles = []
|
315
|
+
expect(object.admin?).to eql(false)
|
316
|
+
expect(object.user?).to eql(false)
|
317
|
+
|
318
|
+
object.roles = [ :admin ]
|
319
|
+
expect(object.admin?).to eql(true)
|
320
|
+
expect(object.user?).to eql(false)
|
321
|
+
|
322
|
+
object.roles = [ :admin, :user ]
|
323
|
+
expect(object.admin?).to eql(true)
|
324
|
+
expect(object.user?).to eql(true)
|
325
|
+
end
|
326
|
+
|
327
|
+
end
|
328
|
+
|
329
|
+
describe EnumX::DSL::FlagsSerializer do
|
330
|
+
let(:serializer) { EnumX::DSL::FlagsSerializer.new(EnumX.roles) }
|
331
|
+
|
332
|
+
it "should load a pipe-separated string" do
|
333
|
+
list = serializer.load("|admin|user|doesnotexist|")
|
334
|
+
expect(list).to be_a(EnumX::ValueList)
|
335
|
+
expect(list).to eq([ EnumX.roles[:admin], EnumX.roles[:user], 'doesnotexist' ])
|
336
|
+
end
|
337
|
+
|
338
|
+
it "should dump a list to a pipe-separated string" do
|
339
|
+
list = EnumX::ValueList.new(EnumX.roles, [ :admin, :user ])
|
340
|
+
expect(serializer.dump(list)).to eql("|admin|user|")
|
341
|
+
expect(serializer.dump("|admin|user|")).to eql("|admin|user|")
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
end
|