varia_model 0.1.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.
@@ -0,0 +1,610 @@
1
+ require 'spec_helper'
2
+
3
+ describe VariaModel do
4
+ describe "ClassMethods" do
5
+ subject do
6
+ Class.new do
7
+ include VariaModel
8
+ end
9
+ end
10
+
11
+ describe "::attributes" do
12
+ it "returns a VariaModel::Attributes" do
13
+ expect(subject.attributes).to be_a(described_class::Attributes)
14
+ end
15
+
16
+ it "is empty by default" do
17
+ expect(subject.attributes).to be_empty
18
+ end
19
+ end
20
+
21
+ describe "::attribute" do
22
+ it "adds an attribute to the attributes hash for each attribute function call" do
23
+ subject.attribute 'jamie.winsor'
24
+ subject.attribute 'brooke.winsor'
25
+
26
+ expect(subject.attributes).to have(2).items
27
+ end
28
+
29
+ it "adds a validation if :required option is true" do
30
+ subject.attribute 'brooke.winsor', required: true
31
+
32
+ expect(subject.validations).to have(1).item
33
+ end
34
+
35
+ it "adds a validation if the :type option is provided" do
36
+ subject.attribute 'brooke.winsor', type: :string
37
+
38
+ expect(subject.validations).to have(1).item
39
+ end
40
+
41
+ it "sets a default value if :default option is provided" do
42
+ subject.attribute 'brooke.winsor', default: 'rhode island'
43
+
44
+ expect(subject.attributes.dig('brooke.winsor')).to eql('rhode island')
45
+ end
46
+
47
+ it "allows an attribute called 'attributes'" do
48
+ subject.attribute 'attributes', default: 'bag of junk'
49
+
50
+ expect(subject.attributes.dig('attributes')).to eql('bag of junk')
51
+ end
52
+
53
+ it "allows an attribute called 'attribute'" do
54
+ subject.attribute 'attribute', default: 'some value'
55
+
56
+ expect(subject.attributes.dig('attribute')).to eql('some value')
57
+ end
58
+ end
59
+
60
+ describe "::validations" do
61
+ it "returns a Hashie::Mash" do
62
+ expect(subject.validations).to be_a(Hashie::Mash)
63
+ end
64
+
65
+ it "is empty by default" do
66
+ expect(subject.validations).to be_empty
67
+ end
68
+ end
69
+
70
+ describe "::validations_for" do
71
+ context "when an attribute is registered and has validations" do
72
+ before(:each) do
73
+ subject.attribute("nested.attribute", required: true, type: String)
74
+ end
75
+
76
+ it "returns an array of procs" do
77
+ validations = subject.validations_for("nested.attribute")
78
+
79
+ expect(validations).to be_a(Array)
80
+ expect(validations).to each be_a(Proc)
81
+ end
82
+ end
83
+
84
+ context "when an attribute is registered but has no validations" do
85
+ before(:each) do
86
+ subject.attribute("nested.attribute")
87
+ end
88
+
89
+ it "returns an empty array" do
90
+ validations = subject.validations_for("nested.attribute")
91
+
92
+ expect(validations).to be_a(Array)
93
+ expect(validations).to be_empty
94
+ end
95
+ end
96
+
97
+ context "when an attribute is not registered" do
98
+ it "returns an empty array" do
99
+ validations = subject.validations_for("not_existing.attribute")
100
+
101
+ expect(validations).to be_a(Array)
102
+ expect(validations).to be_empty
103
+ end
104
+ end
105
+
106
+ describe "#assignment_mode" do
107
+ it "returns the default assignment mode :whitelist" do
108
+ expect(subject.assignment_mode).to eql(:whitelist)
109
+ end
110
+ end
111
+
112
+ describe "#set_assignment_mode" do
113
+ it "sets the assignment_mode to whitelist" do
114
+ subject.set_assignment_mode(:whitelist)
115
+
116
+ expect(subject.assignment_mode).to eql(:whitelist)
117
+ end
118
+
119
+ it "sets the assignment_mode to carefree" do
120
+ subject.set_assignment_mode(:carefree)
121
+
122
+ expect(subject.assignment_mode).to eql(:carefree)
123
+ end
124
+
125
+ it "raises if given an invalid assignment mode" do
126
+ expect { subject.set_assignment_mode(:not_a_real_mode) }.to raise_error(ArgumentError)
127
+ end
128
+ end
129
+ end
130
+
131
+ describe "::validate_kind_of" do
132
+ let(:types) { [ String, Boolean ] }
133
+ let(:key) { 'nested.one' }
134
+
135
+ subject do
136
+ Class.new do
137
+ include VariaModel
138
+
139
+ attribute 'nested.one', types: [String, Boolean]
140
+ end
141
+ end
142
+
143
+ let(:model) { subject.new }
144
+
145
+ it "returns an array" do
146
+ expect(subject.validate_kind_of(types, model, key)).to be_a(Array)
147
+ end
148
+
149
+ context "failure" do
150
+ before(:each) { model.nested.one = nil }
151
+
152
+ it "returns an array where the first element is ':error'" do
153
+ expect(subject.validate_kind_of(types, model, key).first).to eql(:error)
154
+ end
155
+
156
+ it "returns an array where the second element is an error message containing the attribute and types" do
157
+ types.each do |type|
158
+ expect(subject.validate_kind_of(types, model, key)[1]).to match(/#{type}/)
159
+ end
160
+ expect(subject.validate_kind_of(types, model, key)[1]).to match(/#{key}/)
161
+ end
162
+ end
163
+
164
+ context "success" do
165
+ before(:each) { model.nested.one = true }
166
+
167
+ it "returns an array where the first element is ':ok'" do
168
+ expect(subject.validate_kind_of(types, model, key).first).to eql(:ok)
169
+ end
170
+
171
+ it "returns an array where the second element is a blank string" do
172
+ expect(subject.validate_kind_of(types, model, key)[1]).to be_blank
173
+ end
174
+ end
175
+
176
+ context "when given two types of the same kind" do
177
+ let(:types) { [ String, String ] }
178
+ let(:key) { 'nested.one' }
179
+
180
+ subject do
181
+ Class.new do
182
+ include VariaModel
183
+
184
+ attribute 'nested.one', types: [String, Boolean]
185
+ end
186
+ end
187
+
188
+ let(:model) { subject.new }
189
+ before(:each) { model.nested.one = nil }
190
+
191
+ it "returns a error message that contains the type error only once" do
192
+ error_message = "Expected attribute: 'nested.one' to be a type of: 'String'"
193
+ expect(subject.validate_kind_of(types, model, key)[1]).to eql(error_message)
194
+ end
195
+ end
196
+ end
197
+
198
+ describe "::validate_required" do
199
+ subject do
200
+ Class.new do
201
+ include VariaModel
202
+
203
+ attribute 'nested.one', required: true
204
+ end
205
+ end
206
+
207
+ let(:key) { 'nested.one' }
208
+ let(:model) { subject.new }
209
+
210
+ it "returns an array" do
211
+ expect(subject.validate_required(model, key)).to be_a(Array)
212
+ end
213
+
214
+ it "fails validation if the value of the attribute is nil" do
215
+ model.set_attribute(key, nil)
216
+
217
+ expect(subject.validate_required(model, key).first).to eql(:error)
218
+ end
219
+
220
+ it "passes validation if the value of the attribute is false" do
221
+ model.set_attribute(key, false)
222
+
223
+ expect(subject.validate_required(model, key).first).to eql(:ok)
224
+ end
225
+
226
+ it "passes validation if the value of the attribute is not nil" do
227
+ model.set_attribute(key, 'some_value')
228
+
229
+ expect(subject.validate_required(model, key).first).to eql(:ok)
230
+ end
231
+
232
+ context "failure" do
233
+ before(:each) { model.nested.one = nil }
234
+
235
+ it "returns an array where the first element is ':error'" do
236
+ expect(subject.validate_required(model, key).first).to eql(:error)
237
+ end
238
+
239
+ it "returns an array where the second element is an error message containing the attribute name" do
240
+ expect(subject.validate_required(model, key)[1]).to match(/#{key}/)
241
+ end
242
+ end
243
+
244
+ context "success" do
245
+ before(:each) { model.nested.one = "hello" }
246
+
247
+ it "returns an array where the first element is ':ok'" do
248
+ expect(subject.validate_required(model, key).first).to eql(:ok)
249
+ end
250
+
251
+ it "returns an array where the second element is a blank string" do
252
+ expect(subject.validate_required(model, key)[1]).to be_blank
253
+ end
254
+ end
255
+ end
256
+ end
257
+
258
+ subject do
259
+ Class.new do
260
+ include VariaModel
261
+
262
+ attribute 'nested.not_coerced', default: 'hello'
263
+ attribute 'nested.no_default'
264
+ attribute 'nested.coerced', coerce: lambda { |m| m.to_s }
265
+ attribute 'toplevel', default: 'hello'
266
+ attribute 'no_default'
267
+ attribute 'coerced', coerce: lambda { |m| m.to_s }
268
+ end.new
269
+ end
270
+
271
+ describe "GeneratedAccessors" do
272
+ describe "nested getter" do
273
+ it "returns the default value" do
274
+ expect(subject.nested.not_coerced).to eql('hello')
275
+ end
276
+
277
+ it "returns nil if there is no default value" do
278
+ expect(subject.nested.no_default).to be_nil
279
+ end
280
+ end
281
+
282
+ describe "toplevel getter" do
283
+ it "returns the default value" do
284
+ expect(subject.toplevel).to eql('hello')
285
+ end
286
+
287
+ it "returns nil if there is no default value" do
288
+ expect(subject.no_default).to be_nil
289
+ end
290
+ end
291
+
292
+ describe "nested setter" do
293
+ it "sets the value of the nested attribute" do
294
+ subject.nested.not_coerced = 'world'
295
+
296
+ expect(subject.nested.not_coerced).to eql('world')
297
+ end
298
+ end
299
+
300
+ describe "toplevel setter" do
301
+ it "sets the value of the top level attribute" do
302
+ subject.toplevel = 'world'
303
+
304
+ expect(subject.toplevel).to eql('world')
305
+ end
306
+ end
307
+
308
+ describe "nested coerced setter" do
309
+ it "sets the value of the nested coerced attribute" do
310
+ subject.nested.coerced = 1
311
+
312
+ expect(subject.nested.coerced).to eql("1")
313
+ end
314
+ end
315
+
316
+ describe "toplevel coerced setter" do
317
+ it "sets the value of the top level coerced attribute" do
318
+ subject.coerced = 1
319
+
320
+ expect(subject.coerced).to eql('1')
321
+ end
322
+ end
323
+
324
+ context "given two nested attributes with a common parent and default values" do
325
+ subject do
326
+ Class.new do
327
+ include VariaModel
328
+
329
+ attribute 'nested.one', default: 'val_one'
330
+ attribute 'nested.two', default: 'val_two'
331
+ end.new
332
+ end
333
+
334
+ it "sets a default value for each nested attribute" do
335
+ expect(subject.nested.one).to eql('val_one')
336
+ expect(subject.nested.two).to eql('val_two')
337
+ end
338
+ end
339
+
340
+ context "given two nested attributes with a common parent and coercions" do
341
+ subject do
342
+ Class.new do
343
+ include VariaModel
344
+
345
+ attribute 'nested.one', coerce: lambda { |m| m.to_s }
346
+ attribute 'nested.two', coerce: lambda { |m| m.to_s }
347
+ end.new
348
+ end
349
+
350
+ it "coerces each value if both have a coercion" do
351
+ subject.nested.one = 1
352
+ subject.nested.two = 2
353
+
354
+ expect(subject.nested.one).to eql("1")
355
+ expect(subject.nested.two).to eql("2")
356
+ end
357
+ end
358
+
359
+ context "given an attribute called 'attributes'" do
360
+ subject do
361
+ Class.new do
362
+ include VariaModel
363
+
364
+ attribute 'attributes', default: Hash.new
365
+ end.new
366
+ end
367
+
368
+ it "allows the setting and getting of the 'attributes' mimic methods" do
369
+ expect(subject.attributes).to be_a(Hash)
370
+ expect(subject.attributes).to be_empty
371
+
372
+ new_hash = { something: "here" }
373
+ subject.attributes = new_hash
374
+ expect(subject.attributes[:something]).to eql("here")
375
+ end
376
+ end
377
+ end
378
+
379
+ describe "Validations" do
380
+ describe "validate required" do
381
+ subject do
382
+ Class.new do
383
+ include VariaModel
384
+
385
+ attribute 'brooke.winsor', required: true
386
+ end.new
387
+ end
388
+
389
+ it "is not valid if it fails validation" do
390
+ expect(subject).not_to be_valid
391
+ end
392
+
393
+ it "adds an error for each attribute that fails validations" do
394
+ subject.validate
395
+
396
+ expect(subject.errors).to have(1).item
397
+ end
398
+
399
+ it "adds a message for each failed validation" do
400
+ subject.validate
401
+
402
+ expect(subject.errors['brooke.winsor']).to have(1).item
403
+ expect(subject.errors['brooke.winsor'][0]).to eql("A value is required for attribute: 'brooke.winsor'")
404
+ end
405
+ end
406
+
407
+ describe "validate type" do
408
+ subject do
409
+ Class.new do
410
+ include VariaModel
411
+
412
+ attribute 'brooke.winsor', type: String
413
+ end.new
414
+ end
415
+
416
+ before(:each) { subject.brooke.winsor = false }
417
+
418
+ it "returns false if it fails validation" do
419
+ expect(subject).not_to be_valid
420
+ end
421
+
422
+ it "adds an error if it fails validation" do
423
+ subject.validate
424
+
425
+ expect(subject.errors).to have(1).item
426
+ expect(subject.errors['brooke.winsor']).to have(1).item
427
+ expect(subject.errors['brooke.winsor'][0]).to eql("Expected attribute: 'brooke.winsor' to be a type of: 'String', 'NilClass'")
428
+ end
429
+ end
430
+ end
431
+
432
+ describe "#set_attribute" do
433
+ subject do
434
+ Class.new do
435
+ include VariaModel
436
+
437
+ attribute 'brooke.winsor', type: String, default: 'sister'
438
+ attribute 'brooke.costantini', type: String, default: 'sister'
439
+ end.new
440
+ end
441
+
442
+ it "sets the value of the given attribute" do
443
+ subject.set_attribute('brooke.winsor', 'rhode island')
444
+
445
+ expect(subject.brooke.winsor).to eql('rhode island')
446
+ end
447
+
448
+ it "does not disturb the other attributes" do
449
+ subject.set_attribute('brooke.winsor', 'rhode island')
450
+
451
+ expect(subject.brooke.costantini).to eql('sister')
452
+ end
453
+ end
454
+
455
+ describe "#get_attribute" do
456
+ subject do
457
+ Class.new do
458
+ include VariaModel
459
+
460
+ attribute 'brooke.winsor', type: String, default: 'sister'
461
+ end.new
462
+ end
463
+
464
+ it "returns the value of the given dotted path" do
465
+ expect(subject.get_attribute('brooke.winsor')).to eql('sister')
466
+ end
467
+
468
+ it "returns nil if the dotted path matches no attributes" do
469
+ expect(subject.get_attribute('brooke.costantini')).to be_nil
470
+ end
471
+ end
472
+
473
+ describe "#mass_assign" do
474
+ subject do
475
+ Class.new do
476
+ include VariaModel
477
+
478
+ attribute 'brooke.winsor', type: String, default: 'sister'
479
+ attribute 'jamie.winsor', type: String, default: 'brother'
480
+ attribute 'gizmo', type: String, default: 'dog'
481
+ end.new
482
+ end
483
+
484
+ it "sets the values of all matching defined attributes" do
485
+ new_attrs = {
486
+ brooke: {
487
+ winsor: "other"
488
+ },
489
+ jamie: {
490
+ winsor: "other_two"
491
+ }
492
+ }
493
+
494
+ subject.mass_assign(new_attrs)
495
+ expect(subject.brooke.winsor).to eql("other")
496
+ expect(subject.jamie.winsor).to eql("other_two")
497
+ end
498
+
499
+ it "leaves the values of untouched attributes" do
500
+ new_attrs = {
501
+ brooke: {
502
+ winsor: "other"
503
+ },
504
+ jamie: {
505
+ winsor: "other_two"
506
+ }
507
+ }
508
+
509
+ subject.mass_assign(new_attrs)
510
+ expect(subject.gizmo).to eql("dog")
511
+ end
512
+
513
+ it "ignores values which are not defined attributes" do
514
+ new_attrs = {
515
+ undefined_attribute: "value"
516
+ }
517
+
518
+ subject.mass_assign(new_attrs)
519
+ expect(subject.get_attribute(:undefined_attribute)).to be_nil
520
+ expect(subject).not_to respond_to(:undefined_attribute)
521
+ end
522
+
523
+ context "when in carefree assignment mode" do
524
+ subject do
525
+ Class.new do
526
+ include VariaModel
527
+
528
+ set_assignment_mode :carefree
529
+ end.new
530
+ end
531
+
532
+ it "does not ignore values which are not defined" do
533
+ new_attrs = {
534
+ undefined_attribute: "value"
535
+ }
536
+
537
+ subject.mass_assign(new_attrs)
538
+ expect(subject.get_attribute(:undefined_attribute)).to eql("value")
539
+ end
540
+ end
541
+ end
542
+
543
+ describe "#from_json" do
544
+ subject do
545
+ Class.new do
546
+ include VariaModel
547
+
548
+ attribute 'first_name', type: String
549
+ attribute 'nick', type: String
550
+ end.new
551
+ end
552
+
553
+ it "returns self" do
554
+ expect(subject.from_json(JSON.dump(first_name: "jamie", nick: "reset"))).to be_a(described_class)
555
+ end
556
+
557
+ it "updates self from JSON data" do
558
+ subject.from_json(JSON.dump(first_name: "jamie", nick: "reset"))
559
+
560
+ expect(subject.first_name).to eql("jamie")
561
+ expect(subject.nick).to eql("reset")
562
+ end
563
+ end
564
+
565
+ describe "#from_hash" do
566
+ subject do
567
+ Class.new do
568
+ include VariaModel
569
+
570
+ attribute 'first_name', type: String
571
+ attribute 'nick', type: String
572
+ end.new
573
+ end
574
+
575
+ it "returns self" do
576
+ expect(subject.from_hash(first_name: "jamie", nick: "reset")).to be_a(described_class)
577
+ end
578
+
579
+ it "updates and returns self from a Hash" do
580
+ subject.from_hash(first_name: "jamie", nick: "reset")
581
+
582
+ expect(subject.first_name).to eql("jamie")
583
+ expect(subject.nick).to eql("reset")
584
+ end
585
+ end
586
+
587
+ describe "#to_json" do
588
+ subject do
589
+ Class.new do
590
+ include VariaModel
591
+
592
+ attribute 'first_name', type: String
593
+ attribute 'nick', type: String
594
+ end.new
595
+ end
596
+
597
+ it "returns a JSON string containin the serialized attributes" do
598
+ subject.first_name = "brooke"
599
+ subject.nick = "leblanc"
600
+
601
+ expect(subject.to_json).to eql(JSON.dump(first_name: "brooke", nick: "leblanc"))
602
+ end
603
+ end
604
+
605
+ describe "#to_hash" do
606
+ it "returns all of the varia dattributes" do
607
+ expect(subject.to_hash).to eql(subject.send(:_attributes_))
608
+ end
609
+ end
610
+ end
@@ -0,0 +1,33 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/varia_model/version', __FILE__)
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.authors = ["Jamie Winsor"]
6
+ spec.email = ["reset@riotgames.com"]
7
+ spec.description = %q{A mixin to provide objects with magic attribute reading and writing}
8
+ spec.summary = spec.description
9
+ spec.homepage = "https://github.com/RiotGames/varia_model"
10
+ spec.license = "Apache 2.0"
11
+
12
+ spec.files = `git ls-files`.split($\)
13
+ spec.executables = spec.files.grep(%r{^bin/}).map { |f| File.basename(f) }
14
+ spec.test_files = spec.files.grep(%r{^(spec)/})
15
+ spec.name = "varia_model"
16
+ spec.require_paths = ["lib"]
17
+ spec.version = VariaModel::VERSION
18
+ spec.required_ruby_version = ">= 1.9.1"
19
+
20
+ spec.add_dependency "hashie", ">= 2.0.2"
21
+ spec.add_dependency "buff-extensions", "~> 0.1"
22
+
23
+ spec.add_development_dependency "buff-ruby_engine", "~> 0.1"
24
+ spec.add_development_dependency "thor", "~> 0.18.0"
25
+ spec.add_development_dependency "bundler", "~> 1.3"
26
+ spec.add_development_dependency "rake"
27
+ spec.add_development_dependency "rspec"
28
+ spec.add_development_dependency "fuubar"
29
+ spec.add_development_dependency "guard"
30
+ spec.add_development_dependency "guard-rspec"
31
+ spec.add_development_dependency "guard-spork"
32
+ spec.add_development_dependency "spork"
33
+ end