attributor 5.0.2 → 5.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.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +30 -0
  3. data/.travis.yml +6 -4
  4. data/CHANGELOG.md +6 -1
  5. data/Gemfile +1 -1
  6. data/Guardfile +14 -8
  7. data/Rakefile +4 -5
  8. data/attributor.gemspec +34 -29
  9. data/lib/attributor.rb +23 -29
  10. data/lib/attributor/attribute.rb +108 -127
  11. data/lib/attributor/attribute_resolver.rb +12 -26
  12. data/lib/attributor/dsl_compiler.rb +17 -21
  13. data/lib/attributor/dumpable.rb +1 -2
  14. data/lib/attributor/example_mixin.rb +5 -8
  15. data/lib/attributor/exceptions.rb +5 -6
  16. data/lib/attributor/extensions/randexp.rb +3 -5
  17. data/lib/attributor/extras/field_selector.rb +4 -4
  18. data/lib/attributor/extras/field_selector/transformer.rb +6 -7
  19. data/lib/attributor/families/numeric.rb +0 -2
  20. data/lib/attributor/families/temporal.rb +1 -4
  21. data/lib/attributor/hash_dsl_compiler.rb +22 -25
  22. data/lib/attributor/type.rb +24 -32
  23. data/lib/attributor/types/bigdecimal.rb +7 -14
  24. data/lib/attributor/types/boolean.rb +5 -8
  25. data/lib/attributor/types/class.rb +9 -10
  26. data/lib/attributor/types/collection.rb +34 -44
  27. data/lib/attributor/types/container.rb +9 -15
  28. data/lib/attributor/types/csv.rb +7 -10
  29. data/lib/attributor/types/date.rb +20 -25
  30. data/lib/attributor/types/date_time.rb +7 -14
  31. data/lib/attributor/types/float.rb +4 -6
  32. data/lib/attributor/types/hash.rb +171 -196
  33. data/lib/attributor/types/ids.rb +2 -6
  34. data/lib/attributor/types/integer.rb +12 -17
  35. data/lib/attributor/types/model.rb +39 -48
  36. data/lib/attributor/types/object.rb +2 -4
  37. data/lib/attributor/types/polymorphic.rb +118 -0
  38. data/lib/attributor/types/regexp.rb +4 -5
  39. data/lib/attributor/types/string.rb +6 -7
  40. data/lib/attributor/types/struct.rb +8 -15
  41. data/lib/attributor/types/symbol.rb +3 -6
  42. data/lib/attributor/types/tempfile.rb +5 -6
  43. data/lib/attributor/types/time.rb +11 -11
  44. data/lib/attributor/types/uri.rb +9 -10
  45. data/lib/attributor/version.rb +1 -1
  46. data/spec/attribute_resolver_spec.rb +57 -78
  47. data/spec/attribute_spec.rb +174 -216
  48. data/spec/attributor_spec.rb +11 -15
  49. data/spec/dsl_compiler_spec.rb +19 -33
  50. data/spec/dumpable_spec.rb +6 -7
  51. data/spec/extras/field_selector/field_selector_spec.rb +1 -1
  52. data/spec/families_spec.rb +1 -3
  53. data/spec/hash_dsl_compiler_spec.rb +65 -74
  54. data/spec/spec_helper.rb +9 -3
  55. data/spec/support/hashes.rb +2 -3
  56. data/spec/support/models.rb +30 -36
  57. data/spec/support/polymorphics.rb +10 -0
  58. data/spec/type_spec.rb +38 -61
  59. data/spec/types/bigdecimal_spec.rb +11 -15
  60. data/spec/types/boolean_spec.rb +12 -39
  61. data/spec/types/class_spec.rb +10 -11
  62. data/spec/types/collection_spec.rb +72 -81
  63. data/spec/types/container_spec.rb +22 -26
  64. data/spec/types/csv_spec.rb +15 -16
  65. data/spec/types/date_spec.rb +16 -33
  66. data/spec/types/date_time_spec.rb +16 -33
  67. data/spec/types/file_upload_spec.rb +1 -2
  68. data/spec/types/float_spec.rb +7 -14
  69. data/spec/types/hash_spec.rb +285 -289
  70. data/spec/types/ids_spec.rb +5 -7
  71. data/spec/types/integer_spec.rb +37 -46
  72. data/spec/types/model_spec.rb +111 -128
  73. data/spec/types/polymorphic_spec.rb +134 -0
  74. data/spec/types/regexp_spec.rb +4 -7
  75. data/spec/types/string_spec.rb +17 -21
  76. data/spec/types/struct_spec.rb +40 -47
  77. data/spec/types/tempfile_spec.rb +1 -2
  78. data/spec/types/temporal_spec.rb +9 -0
  79. data/spec/types/time_spec.rb +16 -32
  80. data/spec/types/type_spec.rb +15 -0
  81. data/spec/types/uri_spec.rb +6 -7
  82. metadata +77 -25
@@ -0,0 +1,134 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
2
+
3
+ describe Attributor::Polymorphic do
4
+ subject(:type) do
5
+ Attributor::Polymorphic.on(:type)
6
+ end
7
+
8
+ before do
9
+ type.given :chicken, Chicken
10
+ type.given :duck, Duck
11
+ type.given :turkey, Turkey
12
+ end
13
+
14
+ its(:discriminator) { should be :type }
15
+ its(:types) { should eq(chicken: Chicken, duck: Duck, turkey: Turkey) }
16
+ its(:native_type) { should be type }
17
+
18
+ context '.load' do
19
+ let(:chicken) { Chicken.example }
20
+ let(:duck) { Duck.example }
21
+ let(:turkey) { Turkey.example }
22
+
23
+ it 'loads' do
24
+ expect(type.load(chicken.dump)).to be_kind_of(Chicken)
25
+ expect(type.load(duck.dump)).to be_kind_of(Duck)
26
+ expect(type.load(turkey.dump)).to be_kind_of(Turkey)
27
+ end
28
+
29
+ it 'loads a hash with string keys' do
30
+ data = { 'type' => :chicken }
31
+ expect(type.load(data)).to be_kind_of(Chicken)
32
+ end
33
+
34
+ it 'raises a LoadError if the discriminator value is unknown' do
35
+ data = { type: :turducken }
36
+ expect { type.load(data) }.to raise_error(Attributor::LoadError)
37
+ end
38
+
39
+ it 'raises a LoadError if the discriminator value is missing' do
40
+ data = { id: 1 }
41
+ expect { type.load(data) }.to raise_error(Attributor::LoadError)
42
+ end
43
+
44
+ context 'for a type with a string discriminator' do
45
+ subject(:string_type) do
46
+ Attributor::Polymorphic.on('type')
47
+ end
48
+ it 'loads a hash with symbol keys' do
49
+ data = { 'type' => :chicken }
50
+ expect(type.load(data)).to be_kind_of(Chicken)
51
+ end
52
+ end
53
+ end
54
+
55
+ context '.dump' do
56
+ context 'when used in a model' do
57
+ let(:example) { Sandwich.example }
58
+ subject(:dumped) { example.dump }
59
+
60
+ it 'properly dumps the attribute' do
61
+ expect(dumped[:meat]).to eq example.meat.dump
62
+ end
63
+ end
64
+ end
65
+
66
+ context '.valid_type?' do
67
+ it 'is true for instances of possible types' do
68
+ [Chicken, Duck, Turkey].each do |bird_type|
69
+ example = bird_type.example
70
+ expect(type.valid_type?(example)).to be_truthy
71
+ end
72
+ end
73
+ it 'is false for other model types' do
74
+ [Address, Person].each do |other_type|
75
+ example = other_type.example
76
+ expect(type.valid_type?(example)).to be_falsey
77
+ end
78
+ end
79
+ end
80
+
81
+ context '.example' do
82
+ subject(:example) { type.example }
83
+ it do
84
+ expect([Chicken, Duck, Turkey]).to include(type.example.class)
85
+ end
86
+ end
87
+
88
+ context '.describe' do
89
+ let(:example) { nil }
90
+ subject(:description) { type.describe(example: example) }
91
+
92
+ its([:discriminator]) { should eq :type }
93
+ context 'types' do
94
+ subject(:types) { description[:types] }
95
+ its(:keys) { should eq type.types.keys }
96
+ it do
97
+ expect(types[:chicken]).to eq(type: Chicken.describe(true))
98
+ expect(types[:turkey]).to eq(type: Turkey.describe(true))
99
+ expect(types[:duck]).to eq(type: Duck.describe(true))
100
+ end
101
+ end
102
+
103
+ context 'in a Model' do
104
+ subject(:description) { Sandwich.describe[:attributes][:meat][:type] }
105
+ its([:discriminator]) { should eq :type }
106
+ context 'types' do
107
+ subject(:types) { description[:types] }
108
+ its(:keys) { should match_array [:chicken, :turkey, :duck] }
109
+ it do
110
+ expect(types[:chicken]).to eq(type: Chicken.describe(true))
111
+ expect(types[:turkey]).to eq(type: Turkey.describe(true))
112
+ expect(types[:duck]).to eq(type: Duck.describe(true))
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ context 'as an attribute in a model' do
119
+ let(:model) { Sandwich }
120
+ subject(:example) { model.example }
121
+ it 'generates an example properly' do
122
+ expect([Chicken, Duck, Turkey]).to include(example.meat.class)
123
+ end
124
+
125
+ context 'loading' do
126
+ [Chicken, Duck, Turkey].each do |meat_class|
127
+ it "loads #{meat_class}" do
128
+ data = { meat: meat_class.example.dump }
129
+ expect(Sandwich.load(data).meat).to be_kind_of(meat_class)
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
@@ -1,32 +1,29 @@
1
1
  require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
2
2
 
3
3
  describe Attributor::Regexp do
4
-
5
4
  subject(:type) { Attributor::Regexp }
6
5
 
7
6
  it 'it is not Dumpable' do
8
- type.new.is_a?(Attributor::Dumpable).should_not be(true)
7
+ expect(type.new.is_a?(Attributor::Dumpable)).not_to be(true)
9
8
  end
10
9
 
11
10
  its(:native_type) { should be(::Regexp) }
12
11
  its(:example) { should be_a(::String) }
13
- its(:family) { should == 'string' }
12
+ its(:family) { should eq 'string' }
14
13
 
15
14
  context '.load' do
16
15
  let(:value) { nil }
17
16
 
18
17
  it 'returns nil for nil' do
19
- type.load(nil).should be(nil)
18
+ expect(type.load(nil)).to be(nil)
20
19
  end
21
20
 
22
21
  context 'for incoming String values' do
23
-
24
22
  { 'foo' => /foo/, '^pattern$' => /^pattern$/ }.each do |value, expected|
25
23
  it "loads '#{value}' as #{expected.inspect}" do
26
- type.load(value).should eq(expected)
24
+ expect(type.load(value)).to eq(expected)
27
25
  end
28
26
  end
29
-
30
27
  end
31
28
  end
32
29
  end
@@ -1,61 +1,57 @@
1
1
  require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
2
2
 
3
3
  describe Attributor::String do
4
-
5
4
  subject(:type) { Attributor::String }
6
5
 
7
6
  it 'it is not Dumpable' do
8
- type.new.is_a?(Attributor::Dumpable).should_not be(true)
7
+ expect(type.new.is_a?(Attributor::Dumpable)).not_to be(true)
9
8
  end
10
9
 
11
10
  context '.native_type' do
12
- it "returns String" do
13
- type.native_type.should be(::String)
11
+ it 'returns String' do
12
+ expect(type.native_type).to be(::String)
14
13
  end
15
14
  end
16
15
 
17
16
  context '.example' do
18
- it "should return a valid String" do
19
- type.example(options:{regexp: /\w\d{2,3}/}).should be_a(::String)
17
+ it 'should return a valid String' do
18
+ expect(type.example(options: { regexp: /\w\d{2,3}/ })).to be_a(::String)
20
19
  end
21
20
 
22
- it "should return a valid String" do
23
- type.example.should be_a(::String)
21
+ it 'should return a valid String' do
22
+ expect(type.example).to be_a(::String)
24
23
  end
25
24
 
26
25
  it 'handles regexps that Randexp can not (#72)' do
27
26
  regex = /\w+(,\w+)*/
28
- expect {
29
- val = Attributor::String.example(options:{regexp: regex})
30
- val.should be_a(::String)
31
- val.should =~ /Failed to generate.+is too vague/
32
- }.to_not raise_error
27
+ expect do
28
+ val = Attributor::String.example(options: { regexp: regex })
29
+ expect(val).to be_a(::String)
30
+ expect(val).to match(/Failed to generate.+is too vague/)
31
+ end.to_not raise_error
33
32
  end
34
-
35
33
  end
36
34
 
37
35
  context '.load' do
38
36
  let(:value) { nil }
39
37
 
40
38
  it 'returns nil for nil' do
41
- type.load(nil).should be(nil)
39
+ expect(type.load(nil)).to be(nil)
42
40
  end
43
41
 
44
42
  context 'for incoming String values' do
45
-
46
43
  it 'returns the incoming value' do
47
44
  ['', 'foo', '0.0', '-1.0', '1.0', '1e-10', 1].each do |value|
48
- type.load(value).should eq(String(value))
45
+ expect(type.load(value)).to eq(String(value))
49
46
  end
50
47
  end
51
48
  end
52
-
53
49
  end
54
50
 
55
51
  context 'for incoming Symbol values' do
56
52
  let(:value) { :something }
57
53
  it 'returns the stringified-value' do
58
- type.load(value).should == value.to_s
54
+ expect(type.load(value)).to eq value.to_s
59
55
  end
60
56
  end
61
57
 
@@ -63,9 +59,9 @@ describe Attributor::String do
63
59
  let(:value) { [1] }
64
60
 
65
61
  it 'raises IncompatibleTypeError' do
66
- expect {
62
+ expect do
67
63
  type.load(value)
68
- }.to raise_error(Attributor::IncompatibleTypeError)
64
+ end.to raise_error(Attributor::IncompatibleTypeError)
69
65
  end
70
66
  end
71
67
  end
@@ -1,36 +1,33 @@
1
1
  require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
2
2
 
3
3
  describe Attributor::Struct do
4
-
5
4
  context '.definition for a Struct with no sub-attributes' do
6
5
  subject { Attributor::Struct }
7
6
  it 'raises an error' do
8
- expect {
7
+ expect do
9
8
  subject.definition
10
- }.to raise_error(Attributor::AttributorException,"Can not use a pure Struct without defining sub-attributes")
9
+ end.to raise_error(Attributor::AttributorException, 'Can not use a pure Struct without defining sub-attributes')
11
10
  end
12
-
13
11
  end
14
12
  context '.construct' do
15
-
16
13
  context 'empty struct' do
17
14
  let(:attribute_definition) do
18
- Proc.new {}
15
+ proc {}
19
16
  end
20
17
 
21
18
  subject(:empty_struct) { Attributor::Struct.construct(attribute_definition) }
22
19
 
23
20
  it 'constructs a struct with no attributes' do
24
- empty_struct.should < Attributor::Struct
21
+ expect(empty_struct).to be_subclass_of Attributor::Struct
25
22
 
26
23
  attributes = empty_struct.attributes
27
- attributes.should be_empty
24
+ expect(attributes).to be_empty
28
25
  end
29
26
  end
30
27
 
31
28
  context 'simple struct' do
32
29
  let(:attribute_definition) do
33
- Proc.new do
30
+ proc do
34
31
  attribute :age, Attributor::Integer
35
32
  end
36
33
  end
@@ -38,16 +35,16 @@ describe Attributor::Struct do
38
35
  subject(:simple_struct) { Attributor::Struct.construct(attribute_definition) }
39
36
 
40
37
  it 'constructs a struct with one attribute' do
41
- simple_struct.should < Attributor::Struct
38
+ expect(simple_struct).to be_subclass_of Attributor::Struct
42
39
 
43
40
  attributes = simple_struct.attributes
44
- attributes.should have_key :age
41
+ expect(attributes).to have_key :age
45
42
  end
46
43
  end
47
44
 
48
45
  context 'less simple struct' do
49
46
  let(:attribute_definition) do
50
- Proc.new do
47
+ proc do
51
48
  attribute :age, Attributor::Integer
52
49
  attribute :name, Attributor::String
53
50
  attribute :employed?, Attributor::Boolean
@@ -59,20 +56,20 @@ describe Attributor::Struct do
59
56
  subject(:large_struct) { Attributor::Struct.construct(attribute_definition) }
60
57
 
61
58
  it 'constructs a struct with five attributes' do
62
- large_struct.should < Attributor::Struct
59
+ expect(large_struct).to be_subclass_of Attributor::Struct
63
60
 
64
61
  attributes = large_struct.attributes
65
- attributes.should have_key :age
66
- attributes.should have_key :name
67
- attributes.should have_key :employed?
68
- attributes.should have_key :salary
69
- attributes.should have_key :hired_at
62
+ expect(attributes).to have_key :age
63
+ expect(attributes).to have_key :name
64
+ expect(attributes).to have_key :employed?
65
+ expect(attributes).to have_key :salary
66
+ expect(attributes).to have_key :hired_at
70
67
  end
71
68
  end
72
69
 
73
70
  context 'complex struct containing model' do
74
71
  let(:attribute_definition) do
75
- Proc.new do
72
+ proc do
76
73
  attribute :pet, ::Chicken
77
74
  end
78
75
  end
@@ -80,16 +77,16 @@ describe Attributor::Struct do
80
77
  subject(:struct_of_models) { Attributor::Struct.construct(attribute_definition) }
81
78
 
82
79
  it 'constructs a struct with a model attribute' do
83
- struct_of_models.should < Attributor::Struct
80
+ expect(struct_of_models).to be_subclass_of Attributor::Struct
84
81
 
85
82
  attributes = struct_of_models.attributes
86
- attributes.should have_key :pet
83
+ expect(attributes).to have_key :pet
87
84
  end
88
85
  end
89
86
 
90
87
  context 'complex struct containing named struct' do
91
88
  let(:attribute_definition) do
92
- Proc.new do
89
+ proc do
93
90
  attribute :stats, Attributor::Struct do
94
91
  attribute :months, Attributor::Integer
95
92
  attribute :days, Attributor::Integer
@@ -100,20 +97,20 @@ describe Attributor::Struct do
100
97
  subject(:struct_of_structs) { Attributor::Struct.construct(attribute_definition) }
101
98
 
102
99
  it 'constructs a struct with a named struct attribute' do
103
- struct_of_structs.should < Attributor::Struct
100
+ expect(struct_of_structs).to be_subclass_of Attributor::Struct
104
101
 
105
102
  attributes = struct_of_structs.attributes
106
- attributes.should have_key :stats
103
+ expect(attributes).to have_key :stats
107
104
 
108
105
  stats = attributes[:stats].attributes
109
- stats.should have_key :months
110
- stats.should have_key :days
106
+ expect(stats).to have_key :months
107
+ expect(stats).to have_key :days
111
108
  end
112
109
  end
113
110
 
114
111
  context 'complex struct containing multi-level recursive structs' do
115
112
  let(:attribute_definition) do
116
- Proc.new do
113
+ proc do
117
114
  attribute :arthropods, Attributor::Struct do
118
115
  attribute :insects, Attributor::Struct do
119
116
  attribute :ants, Attributor::Struct do
@@ -129,21 +126,21 @@ describe Attributor::Struct do
129
126
  subject(:multi_level_struct_of_structs) { Attributor::Struct.construct(attribute_definition) }
130
127
 
131
128
  it 'constructs a struct with multiple levels of named struct attributes' do
132
- multi_level_struct_of_structs.should < Attributor::Struct
129
+ expect(multi_level_struct_of_structs).to be_subclass_of Attributor::Struct
133
130
 
134
131
  root = multi_level_struct_of_structs.attributes
135
- root.should have_key :arthropods
132
+ expect(root).to have_key :arthropods
136
133
 
137
134
  arthropods = root[:arthropods].attributes
138
- arthropods.should have_key :insects
135
+ expect(arthropods).to have_key :insects
139
136
 
140
137
  insects = arthropods[:insects].attributes
141
- insects.should have_key :ants
138
+ expect(insects).to have_key :ants
142
139
 
143
140
  ants = insects[:ants].attributes
144
- ants.should have_key :name
145
- ants.should have_key :age
146
- ants.should have_key :weight
141
+ expect(ants).to have_key :name
142
+ expect(ants).to have_key :age
143
+ expect(ants).to have_key :weight
147
144
  end
148
145
  end
149
146
 
@@ -153,37 +150,33 @@ describe Attributor::Struct do
153
150
  proc do
154
151
  end
155
152
  end
156
- subject(:struct) { Attributor::Struct.construct(attribute_definition, options)}
153
+ subject(:struct) { Attributor::Struct.construct(attribute_definition, options) }
157
154
 
158
155
  context 'with new type-level options' do
159
- let(:options) { {reference: reference} }
156
+ let(:options) { { reference: reference } }
160
157
  its(:options) { should have_key(:identity) }
161
158
  it 'inherits from the reference' do
162
- struct.options[:identity].should eq(reference.options[:identity])
159
+ expect(struct.options[:identity]).to eq(reference.options[:identity])
163
160
  end
164
161
  it 'does not raise an error when used in an attribute' do
165
- expect {
162
+ expect do
166
163
  Attributor::Attribute.new(struct)
167
- }.to_not raise_error
164
+ end.to_not raise_error
168
165
  end
169
166
  end
170
167
 
171
168
  context 'with existing type-level options' do
172
- let(:options) { {reference: reference, identity: :name} }
169
+ let(:options) { { reference: reference, identity: :name } }
173
170
  its(:options) { should have_key(:identity) }
174
171
  it 'does not override from the reference' do
175
- struct.options[:identity].should eq(:name)
172
+ expect(struct.options[:identity]).to eq(:name)
176
173
  end
177
174
  it 'does not raise an error when used in an attribute' do
178
- expect {
175
+ expect do
179
176
  Attributor::Attribute.new(struct)
180
- }.to_not raise_error
177
+ end.to_not raise_error
181
178
  end
182
-
183
179
  end
184
180
  end
185
-
186
-
187
181
  end
188
-
189
182
  end