attributor 5.5 → 6.1

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.
@@ -11,7 +11,7 @@ describe Attributor::Attribute do
11
11
 
12
12
  context 'initialize' do
13
13
  its(:type) { should be type }
14
- its(:options) { should be attribute_options }
14
+ its(:options) { should eq attribute_options }
15
15
 
16
16
  it 'calls check_options!' do
17
17
  expect_any_instance_of(Attributor::Attribute).to receive(:check_options!)
@@ -62,6 +62,7 @@ describe Attributor::Attribute do
62
62
  it 'as well as the type-specific ones' do
63
63
  expect(js[:type]).to eq(:integer)
64
64
  end
65
+
65
66
  end
66
67
 
67
68
  end
@@ -71,8 +72,8 @@ describe Attributor::Attribute do
71
72
  let(:expected) do
72
73
  h = {type: {name: 'String', id: type.id, family: type.family}}
73
74
  common = attribute_options.select{|k,v| Attributor::Attribute::TOP_LEVEL_OPTIONS.include? k }
74
- h.merge!( common )
75
- h[:options] = {:min => 0 }
75
+ h.merge!(common)
76
+ h[:options] = {min: 0}
76
77
  h
77
78
  end
78
79
 
@@ -194,6 +195,49 @@ describe Attributor::Attribute do
194
195
  end.not_to raise_error
195
196
  end
196
197
  end
198
+
199
+ context 'custom_options' do
200
+ let(:option_name) { :foo }
201
+ let(:custom_option_args) { [option_name, String] }
202
+
203
+ around do |example|
204
+ Attributor::Attribute.custom_option *custom_option_args
205
+ example.run
206
+ Attributor::Attribute.custom_options.delete option_name
207
+ end
208
+
209
+ it 'raises ArgumentError if given an existing option' do
210
+ expect {
211
+ Attributor::Attribute.custom_option :default, Object
212
+ }.to raise_error(ArgumentError)
213
+ end
214
+
215
+ it 'accepts custom options' do
216
+ expect do
217
+ Attributor::Attribute.new(Integer, foo: 'unvalidated')
218
+ end.not_to raise_error
219
+ end
220
+
221
+ context 'can validate the custom option value' do
222
+ let(:custom_option_args) { [option_name, String, values: ['valid']] }
223
+ it 'does not raise with a valid option value' do
224
+ expect do
225
+ Attributor::Attribute.new(Integer, foo: 'valid')
226
+ end.not_to raise_error
227
+ end
228
+ it 'raises with an invalid option value' do
229
+ expect do
230
+ Attributor::Attribute.new(Integer, foo: 'invalid')
231
+ end.to raise_error(Attributor::AttributorException)
232
+ end
233
+ end
234
+
235
+ it 'appear in as_json_schema' do
236
+ attribute = Attributor::Attribute.new(Integer, foo: 'valid')
237
+ json_schema = attribute.as_json_schema
238
+ expect(json_schema[:'x-foo']).to eq 'valid'
239
+ end
240
+ end
197
241
  end
198
242
 
199
243
  context 'example' do
@@ -414,10 +458,49 @@ describe Attributor::Attribute do
414
458
  context 'applying attribute options' do
415
459
  context ':required' do
416
460
  let(:attribute_options) { { required: true } }
461
+ context 'has no effect on a bare attribute' do
462
+ let(:value) { 'val' }
463
+ it 'it does not error, as we do not know if the parent attribute key was passed in (done at the Hash level)' do
464
+ expect(attribute.validate(value, context)).to be_empty
465
+ end
466
+ end
467
+ end
468
+ context ':null false (non-nullable)' do
469
+ let(:attribute_options) { { null: false } }
470
+ context 'with a nil value' do
471
+ let(:value) { nil }
472
+ it 'returns an error' do
473
+ expect(attribute.validate(value, context).first).to eq 'Attribute context is not nullable'
474
+ end
475
+ end
476
+ end
477
+ context ':null true (nullable)' do
478
+ let(:attribute_options) { { null: true } }
479
+ context 'with a nil value' do
480
+ let(:value) { nil }
481
+ it 'does not error' do
482
+ expect(attribute.validate(value, context)).to be_empty
483
+ end
484
+ end
485
+ end
486
+ context 'defaults to non-nullable if null not defined' do
487
+ let(:attribute_options) { { } }
417
488
  context 'with a nil value' do
418
489
  let(:value) { nil }
419
490
  it 'returns an error' do
420
- expect(attribute.validate(value, context).first).to eq 'Attribute context is required'
491
+ expect(Attributor::Attribute.default_for_null).to be(false)
492
+ expect(attribute.validate(value, context).first).to eq 'Attribute context is not nullable'
493
+ end
494
+ end
495
+ end
496
+
497
+ context 'default can be overrideable with true' do
498
+ let(:attribute_options) { { } }
499
+ context 'with a nil value' do
500
+ let(:value) { nil }
501
+ it 'suceeds' do
502
+ expect(Attributor::Attribute).to receive(:default_for_null).and_return(true)
503
+ expect(attribute.validate(value, context)).to be_empty
421
504
  end
422
505
  end
423
506
  end
@@ -463,6 +546,13 @@ describe Attributor::Attribute do
463
546
  end
464
547
  end
465
548
 
549
+ context 'with a nil value' do
550
+ let(:value) { nil }
551
+ it 'returns no errors' do
552
+ expect(errors).to be_empty
553
+ end
554
+ end
555
+
466
556
  context 'with a value of a value different than the native_type' do
467
557
  let(:value) { 1 }
468
558
 
@@ -472,58 +562,6 @@ describe Attributor::Attribute do
472
562
  end
473
563
  end
474
564
  end
475
-
476
- context '#validate_missing_value' do
477
- let(:key) { '$.instance.ssh_key.name' }
478
- let(:value) { /\w+/.gen }
479
-
480
- let(:attribute_options) { { required_if: key } }
481
-
482
- let(:ssh_key) { double('ssh_key', name: value) }
483
- let(:instance) { double('instance', ssh_key: ssh_key) }
484
-
485
- before { Attributor::AttributeResolver.current.register('instance', instance) }
486
-
487
- let(:attribute_context) { ['$', 'params', 'key_material'] }
488
- subject(:errors) { attribute.validate_missing_value(attribute_context) }
489
-
490
- context 'for a simple dependency without a predicate' do
491
- context 'that is satisfied' do
492
- it { should_not be_empty }
493
- end
494
-
495
- context 'that is missing' do
496
- let(:value) { nil }
497
- it { should be_empty }
498
- end
499
- end
500
-
501
- context 'with a dependency that has a predicate' do
502
- let(:value) { 'default_ssh_key_name' }
503
- # subject(:errors) { attribute.validate_missing_value('') }
504
-
505
- context 'where the target attribute exists, and matches the predicate' do
506
- let(:attribute_options) { { required_if: { key => /default/ } } }
507
-
508
- it { should_not be_empty }
509
-
510
- its(:first) { should match(/Attribute #{Regexp.quote(Attributor.humanize_context(attribute_context))} is required when #{Regexp.quote(key)} matches/) }
511
- end
512
-
513
- context 'where the target attribute exists, but does not match the predicate' do
514
- let(:attribute_options) { { required_if: { key => /other/ } } }
515
-
516
- it { should be_empty }
517
- end
518
-
519
- context 'where the target attribute does not exist' do
520
- let(:attribute_options) { { required_if: { key => /default/ } } }
521
- let(:ssh_key) { double('ssh_key', name: nil) }
522
-
523
- it { should be_empty }
524
- end
525
- end
526
- end
527
565
  end
528
566
 
529
567
  context 'for an attribute for a subclass of Model' do
@@ -592,71 +630,6 @@ describe Attributor::Attribute do
592
630
  end
593
631
  end
594
632
  end
595
-
596
- context '#validate_missing_value' do
597
- let(:type) { Duck }
598
- let(:attribute_name) { nil }
599
- let(:attribute) { Duck.attributes[attribute_name] }
600
-
601
- let(:attribute_context) { ['$', 'duck', attribute_name.to_s] }
602
- subject(:errors) { attribute.validate_missing_value(attribute_context) }
603
-
604
- before do
605
- Attributor::AttributeResolver.current.register('duck', duck)
606
- end
607
-
608
- context 'for a dependency with no predicate' do
609
- let(:attribute_name) { :email }
610
-
611
- let(:duck) do
612
- d = Duck.new
613
- d.age = 1
614
- d.name = 'Donald'
615
- d
616
- end
617
-
618
- context 'where the target attribute exists, and matches the predicate' do
619
- it { should_not be_empty }
620
- its(:first) { should eq 'Attribute $.duck.email is required when name (for $.duck) is present.' }
621
- end
622
- context 'where the target attribute does not exist' do
623
- before do
624
- duck.name = nil
625
- end
626
- it { should be_empty }
627
- end
628
- end
629
-
630
- context 'for a dependency with a predicate' do
631
- let(:attribute_name) { :age }
632
-
633
- let(:duck) do
634
- d = Duck.new
635
- d.name = 'Daffy'
636
- d.email = 'daffy@darkwing.uoregon.edu' # he's a duck,get it?
637
- d
638
- end
639
-
640
- context 'where the target attribute exists, and matches the predicate' do
641
- it { should_not be_empty }
642
- its(:first) { should match(/Attribute #{Regexp.quote('$.duck.age')} is required when name #{Regexp.quote('(for $.duck)')} matches/) }
643
- end
644
-
645
- context 'where the target attribute exists, and does not match the predicate' do
646
- before do
647
- duck.name = 'Donald'
648
- end
649
- it { should be_empty }
650
- end
651
-
652
- context 'where the target attribute does not exist' do
653
- before do
654
- duck.name = nil
655
- end
656
- it { should be_empty }
657
- end
658
- end
659
- end
660
633
  end
661
634
  end
662
635
 
@@ -713,4 +686,24 @@ describe Attributor::Attribute do
713
686
  end
714
687
  end
715
688
  end
689
+
690
+ context '.nullable_attribute?' do
691
+ subject { described_class.nullable_attribute?(options) }
692
+ context 'with null: true option' do
693
+ let(:options) { { null: true } }
694
+ it { should be_truthy }
695
+ end
696
+ context 'with null: false option' do
697
+ let(:options) { { null: false } }
698
+ it { should be_falsey }
699
+ end
700
+ context 'defaults to false without any null option' do
701
+ let(:options) { { } }
702
+ it { should be_falsey }
703
+ end
704
+ context 'defaults to false if null: nil' do
705
+ let(:options) { { null: nil } }
706
+ it { should be_falsey }
707
+ end
708
+ end
716
709
  end
@@ -109,31 +109,31 @@ describe Attributor::HashDSLCompiler do
109
109
  context 'for :all' do
110
110
  let(:arguments) { { all: [:one, :two, :three] } }
111
111
  let(:value) { [:one] }
112
- let(:validation_error) { ['Key two is required for $.', 'Key three is required for $.'] }
112
+ let(:validation_error) { ["Attribute $.key(:two) is required.", "Attribute $.key(:three) is required."] }
113
113
  it { expect(subject).to include(*validation_error) }
114
114
  end
115
115
  context 'for :exactly' do
116
116
  let(:requirement) { req_class.new(exactly: 1).of(:one, :two) }
117
117
  let(:value) { [:one, :two] }
118
- let(:validation_error) { 'Exactly 1 of the following keys [:one, :two] are required for $. Found 2 instead: [:one, :two]' }
118
+ let(:validation_error) { 'Exactly 1 of the following attributes [:one, :two] are required for $. Found 2 instead: [:one, :two]' }
119
119
  it { expect(subject).to include(validation_error) }
120
120
  end
121
121
  context 'for :at_least' do
122
122
  let(:requirement) { req_class.new(at_least: 2).of(:one, :two, :three) }
123
123
  let(:value) { [:one] }
124
- let(:validation_error) { 'At least 2 keys out of [:one, :two, :three] are required to be passed in for $. Found [:one]' }
124
+ let(:validation_error) { 'At least 2 attributes out of [:one, :two, :three] are required to be passed in for $. Found [:one]' }
125
125
  it { expect(subject).to include(validation_error) }
126
126
  end
127
127
  context 'for :at_most' do
128
128
  let(:requirement) { req_class.new(at_most: 1).of(:one, :two, :three) }
129
129
  let(:value) { [:one, :two] }
130
- let(:validation_error) { 'At most 1 keys out of [:one, :two, :three] can be passed in for $. Found [:one, :two]' }
130
+ let(:validation_error) { 'At most 1 attributes out of [:one, :two, :three] can be passed in for $. Found [:one, :two]' }
131
131
  it { expect(subject).to include(validation_error) }
132
132
  end
133
133
  context 'for :exclusive' do
134
134
  let(:arguments) { { exclusive: [:one, :two] } }
135
135
  let(:value) { [:one, :two] }
136
- let(:validation_error) { 'keys [:one, :two] are mutually exclusive for $.' }
136
+ let(:validation_error) { 'Attributes [:one, :two] are mutually exclusive for $.' }
137
137
  it { expect(subject).to include(validation_error) }
138
138
  end
139
139
  end
data/spec/spec_helper.rb CHANGED
@@ -23,9 +23,7 @@ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
23
23
 
24
24
  RSpec.configure do |config|
25
25
  config.around(:each) do |example|
26
- Attributor::AttributeResolver.current = Attributor::AttributeResolver.new
27
26
  example.run
28
- Attributor::AttributeResolver.current = nil
29
27
  end
30
28
  end
31
29
 
@@ -11,9 +11,9 @@ end
11
11
 
12
12
  class Duck < Attributor::Model
13
13
  attributes do
14
- attribute :age, Attributor::Integer, required_if: { 'name' => 'Daffy' }
14
+ attribute :age, Attributor::Integer
15
15
  attribute :name, Attributor::String
16
- attribute :email, Attributor::String, required_if: 'name'
16
+ attribute :email, Attributor::String
17
17
  attribute :angry, Attributor::Boolean, default: true, example: /true|false/, description: 'Angry bird?'
18
18
  attribute :weight, Attributor::Float, example: /\d{1,2}\.\d/, description: 'The weight of the duck'
19
19
  attribute :type, Attributor::Symbol, values: [:duck]
@@ -50,15 +50,15 @@ class Cormorant < Attributor::Model
50
50
  end
51
51
 
52
52
  # This will be a collection of arbitrary Ruby Objects
53
- attribute :fish, Attributor::Collection, description: 'All kinds of fish for feeding the babies'
53
+ attribute :all_the_fish, Attributor::Collection, description: 'All kinds of fish for feeding the babies'
54
54
 
55
55
  # This will be a collection of Cormorants (note, this relationship is circular)
56
- attribute :neighbors, Attributor::Collection.of(Cormorant), description: 'Neighbor cormorants'
56
+ attribute :neighbors, Attributor::Collection.of(Cormorant), member_options: {null: false}, description: 'Neighbor cormorants', null: false
57
57
 
58
58
  # This will be a collection of instances of an anonymous Struct class, each having two well-defined attributes
59
59
 
60
60
  attribute :babies, Attributor::Collection.of(Attributor::Struct), description: 'All the babies', member_options: { identity: :name } do
61
- attribute :name, Attributor::String, example: /[:name]/, description: 'The name of the baby cormorant'
61
+ attribute :name, Attributor::String, example: /[:name]/, description: 'The name of the baby cormorant', required: true
62
62
  attribute :months, Attributor::Integer, default: 0, min: 0, description: 'The age in months of the baby cormorant'
63
63
  attribute :weight, Attributor::Float, example: /\d{1,2}\.\d{3}/, description: 'The weight in kg of the baby cormorant'
64
64
  end
@@ -76,8 +76,8 @@ end
76
76
 
77
77
  class Address < Attributor::Model
78
78
  attributes do
79
- attribute :name, String, example: /\w+/
80
- attribute :state, String, values: %w(OR CA)
79
+ attribute :name, String, example: /\w+/, null: true
80
+ attribute :state, String, values: %w(OR CA), null: false
81
81
  attribute :person, Person, example: proc { |address, context| Person.example(context, address: address) }
82
82
  requires :name
83
83
  end
@@ -514,6 +514,7 @@ describe Attributor::Hash do
514
514
  end
515
515
  end
516
516
  end
517
+
517
518
  context '#validate' do
518
519
  context 'for a key and value typed hash' do
519
520
  let(:key_type) { Integer }
@@ -534,45 +535,85 @@ describe Attributor::Hash do
534
535
  context 'for a hash with defined keys' do
535
536
  let(:block) do
536
537
  proc do
537
- key 'integer', Integer
538
+ key 'integer', Integer, null: false # non-nullable, but not required
538
539
  key 'datetime', DateTime
539
- key 'not-optional', String, required: true
540
+ key 'not-optional', String, required: true, null: false # required AND non-nullable
541
+ key 'required-but-nullable', String, required: true, null: true
540
542
  end
541
543
  end
542
544
 
543
545
  let(:type) { Attributor::Hash.construct(block) }
544
-
545
- let(:values) { { 'integer' => 'one', 'datetime' => 'now' } }
546
546
  subject(:hash) { type.new(values) }
547
547
 
548
- it 'validates the keys' do
549
- errors = hash.validate
550
- expect(errors).to have(3).items
551
- expect(errors).to include('Attribute $.key("not-optional") is required')
548
+ context 'validates it all' do
549
+ let(:values) { { 'integer' => 'one', 'datetime' => 'now', 'required-but-nullable' => nil} }
550
+ it 'validates the keys' do
551
+ errors = hash.validate
552
+ expect(errors).to have(3).items
553
+ [
554
+ 'Attribute $.key("integer") received value: "one" is of the wrong type (got: String, expected: Attributor::Integer)',
555
+ 'Attribute $.key("datetime") received value: "now" is of the wrong type (got: String, expected: Attributor::DateTime)',
556
+ 'Attribute $.key("not-optional") is required'
557
+ ].each do |msg|
558
+ regexp = Regexp.new(Regexp.escape(msg))
559
+ expect(errors.any?{|err| err =~ regexp}).to be_truthy
560
+ end
561
+ end
562
+ end
563
+
564
+ context 'still validates requiredness even if values are nullable' do
565
+ let(:values) { {} }
566
+ it 'complains if not provided' do
567
+ errors = hash.validate
568
+ expect(errors).to have(2).items
569
+ [
570
+ 'Attribute $.key("not-optional") is required',
571
+ 'Attribute $.key("required-but-nullable") is required'
572
+ ].each do |msg|
573
+ regexp = Regexp.new(Regexp.escape(msg))
574
+ expect(errors.any?{|err| err =~ regexp}).to be_truthy
575
+ end
576
+ end
552
577
  end
578
+
579
+ context 'validates nullability regardless of requiredness' do
580
+ let(:values) { {'integer' => nil, 'not-optional' => nil, 'required-but-nullable' => nil} }
581
+ it 'complains if null' do
582
+ errors = hash.validate
583
+ expect(errors).to have(2).items
584
+ [
585
+ 'Attribute $.key("integer") is not nullable',
586
+ 'Attribute $.key("not-optional") is not nullable',
587
+ ].each do |msg|
588
+ regexp = Regexp.new(Regexp.escape(msg))
589
+ expect(errors.any?{|err| err =~ regexp}).to be_truthy
590
+ end
591
+ end
592
+ end
593
+
553
594
  end
554
595
 
555
596
  context 'with requirements defined' do
556
597
  let(:type) { Attributor::Hash.construct(block) }
557
598
 
558
- context 'using requires' do
599
+ context 'using the requires DSL' do
559
600
  let(:block) do
560
601
  proc do
561
602
  key 'name', String
603
+ key 'nevernull', String, null: false
562
604
  key 'consistency', Attributor::Boolean
563
- key 'availability', Attributor::Boolean
564
- key 'partitioning', Attributor::Boolean
605
+ key 'availability', Attributor::Boolean, null: true
606
+ key 'partitioning', Attributor::Boolean, null: true
565
607
  requires 'consistency', 'availability'
566
- requires.all 'name' # Just to show that it is equivalent to 'requires'
608
+ requires.all 'name'
567
609
  end
568
610
  end
569
611
 
570
612
  it 'complains not all the listed elements are set (false or true)' do
571
- errors = type.new('name' => 'CAP').validate
613
+ errors = type.new('name' => 'CAP', 'consistency' => true, 'nevernull' => nil).validate
572
614
  expect(errors).to have(2).items
573
- %w(consistency availability).each do |name|
574
- expect(errors).to include("Key #{name} is required for $.")
575
- end
615
+ expect(errors).to include('Attribute $.key("nevernull") is not nullable.')
616
+ expect(errors).to include('Attribute $.key("availability") is required.')
576
617
  end
577
618
  end
578
619
 
@@ -591,7 +632,7 @@ describe Attributor::Hash do
591
632
  errors = type.new('name' => 'CAP', 'consistency' => false).validate
592
633
  expect(errors).to have(1).items
593
634
  expect(errors).to include(
594
- 'At least 2 keys out of ["consistency", "availability", "partitioning"] are required to be passed in for $. Found ["consistency"]'
635
+ 'At least 2 attributes out of ["consistency", "availability", "partitioning"] are required to be passed in for $. Found ["consistency"]'
595
636
  )
596
637
  end
597
638
  end
@@ -610,7 +651,7 @@ describe Attributor::Hash do
610
651
  it 'complains if more than 2 in the group are set (false or true)' do
611
652
  errors = type.new('name' => 'CAP', 'consistency' => false, 'availability' => true, 'partitioning' => false).validate
612
653
  expect(errors).to have(1).items
613
- expect(errors).to include('At most 2 keys out of ["consistency", "availability", "partitioning"] can be passed in for $. Found ["consistency", "availability", "partitioning"]')
654
+ expect(errors).to include('At most 2 attributes out of ["consistency", "availability", "partitioning"] can be passed in for $. Found ["consistency", "availability", "partitioning"]')
614
655
  end
615
656
  end
616
657
 
@@ -628,12 +669,12 @@ describe Attributor::Hash do
628
669
  it 'complains if less than 1 in the group are set (false or true)' do
629
670
  errors = type.new('name' => 'CAP').validate
630
671
  expect(errors).to have(1).items
631
- expect(errors).to include('Exactly 1 of the following keys ["consistency", "availability", "partitioning"] are required for $. Found 0 instead: []')
672
+ expect(errors).to include('Exactly 1 of the following attributes ["consistency", "availability", "partitioning"] are required for $. Found 0 instead: []')
632
673
  end
633
674
  it 'complains if more than 1 in the group are set (false or true)' do
634
675
  errors = type.new('name' => 'CAP', 'consistency' => false, 'availability' => true).validate
635
676
  expect(errors).to have(1).items
636
- expect(errors).to include('Exactly 1 of the following keys ["consistency", "availability", "partitioning"] are required for $. Found 2 instead: ["consistency", "availability"]')
677
+ expect(errors).to include('Exactly 1 of the following attributes ["consistency", "availability", "partitioning"] are required for $. Found 2 instead: ["consistency", "availability"]')
637
678
  end
638
679
  end
639
680
 
@@ -651,7 +692,7 @@ describe Attributor::Hash do
651
692
  it 'complains if two or more in the group are set (false or true)' do
652
693
  errors = type.new('name' => 'CAP', 'consistency' => false, 'availability' => true).validate
653
694
  expect(errors).to have(1).items
654
- expect(errors).to include('keys ["consistency", "availability"] are mutually exclusive for $.')
695
+ expect(errors).to include('Attributes ["consistency", "availability"] are mutually exclusive for $.')
655
696
  end
656
697
  end
657
698
 
@@ -676,7 +717,7 @@ describe Attributor::Hash do
676
717
  errors = type.new('name' => 'CAP').validate
677
718
  expect(errors).to have(1).items
678
719
  expect(errors).to include(
679
- 'At least 1 keys out of ["consistency", "availability", "partitioning"] are required to be passed in for $. Found none'
720
+ 'At least 1 attributes out of ["consistency", "availability", "partitioning"] are required to be passed in for $. Found none'
680
721
  )
681
722
  end
682
723
  end
@@ -371,7 +371,12 @@ describe Attributor::Model do
371
371
  context 'for models using the "requires" DSL' do
372
372
  subject(:address) { Address.load({state: 'CA'}) }
373
373
  its(:validate) { should_not be_empty }
374
- its(:validate) { should include 'Key name is required for $.' }
374
+ its(:validate) { should include 'Attribute $.key(:name) is required.' }
375
+ end
376
+ context 'for models with non-nullable attributes' do
377
+ subject(:address) { Address.load({name: nil, state: nil}) }
378
+ its(:validate) { should_not be_empty }
379
+ its(:validate) { should include 'Attribute $.state is not nullable.' } # name is nullable
375
380
  end
376
381
  context 'for models with circular sub-attributes' do
377
382
  context 'that are valid' do
@@ -478,6 +483,7 @@ describe Attributor::Model do
478
483
 
479
484
  it 'supports defining sub-attributes using the proper reference' do
480
485
  expect(struct.attributes[:neighbors].options[:required]).to be true
486
+ expect(struct.attributes[:neighbors].options[:null]).to be false
481
487
  expect(struct.attributes[:neighbors].type.member_attribute.type.attributes.keys).to match_array [:name, :age]
482
488
 
483
489
  name_options = struct.attributes[:neighbors].type.member_attribute.type.attributes[:name].options
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: attributor
3
3
  version: !ruby/object:Gem::Version
4
- version: '5.5'
4
+ version: '6.1'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josep M. Blanquer
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-08-21 00:00:00.000000000 Z
12
+ date: 2022-02-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: hashie
@@ -312,7 +312,6 @@ files:
312
312
  - attributor.gemspec
313
313
  - lib/attributor.rb
314
314
  - lib/attributor/attribute.rb
315
- - lib/attributor/attribute_resolver.rb
316
315
  - lib/attributor/dsl_compiler.rb
317
316
  - lib/attributor/dumpable.rb
318
317
  - lib/attributor/example_mixin.rb
@@ -350,7 +349,6 @@ files:
350
349
  - lib/attributor/types/time.rb
351
350
  - lib/attributor/types/uri.rb
352
351
  - lib/attributor/version.rb
353
- - spec/attribute_resolver_spec.rb
354
352
  - spec/attribute_spec.rb
355
353
  - spec/attributor_spec.rb
356
354
  - spec/dsl_compiler_spec.rb
@@ -407,13 +405,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
407
405
  - !ruby/object:Gem::Version
408
406
  version: '0'
409
407
  requirements: []
410
- rubyforge_project:
411
- rubygems_version: 2.6.14
408
+ rubygems_version: 3.1.2
412
409
  signing_key:
413
410
  specification_version: 4
414
411
  summary: A powerful attribute and type management library for Ruby
415
412
  test_files:
416
- - spec/attribute_resolver_spec.rb
417
413
  - spec/attribute_spec.rb
418
414
  - spec/attributor_spec.rb
419
415
  - spec/dsl_compiler_spec.rb