attributor 5.0.2 → 5.1.0

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