varia_model 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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