yaks 0.4.4 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/CHANGELOG.md +44 -3
  4. data/README.md +90 -33
  5. data/Rakefile +10 -0
  6. data/bench/bench.rb +0 -1
  7. data/bench/bench_1000.rb +60 -0
  8. data/lib/yaks/breaking_changes.rb +22 -0
  9. data/lib/yaks/config/dsl.rb +114 -27
  10. data/lib/yaks/config.rb +39 -54
  11. data/lib/yaks/default_policy.rb +32 -14
  12. data/lib/yaks/format/collection_json.rb +4 -4
  13. data/lib/yaks/format/hal.rb +20 -3
  14. data/lib/yaks/format/json_api.rb +3 -3
  15. data/lib/yaks/format.rb +54 -9
  16. data/lib/yaks/fp/callable.rb +9 -0
  17. data/lib/yaks/fp/hash_updatable.rb +2 -0
  18. data/lib/yaks/fp/updatable.rb +2 -0
  19. data/lib/yaks/fp.rb +8 -0
  20. data/lib/yaks/mapper/link.rb +2 -2
  21. data/lib/yaks/mapper.rb +6 -6
  22. data/lib/yaks/primitivize.rb +2 -2
  23. data/lib/yaks/resource/link.rb +0 -4
  24. data/lib/yaks/runner.rb +90 -0
  25. data/lib/yaks/util.rb +4 -0
  26. data/lib/yaks/version.rb +1 -1
  27. data/lib/yaks.rb +3 -0
  28. data/spec/acceptance/acceptance_spec.rb +6 -1
  29. data/spec/json/confucius.collection.json +5 -16
  30. data/spec/json/plant_collection.collection.json +32 -0
  31. data/spec/spec_helper.rb +2 -1
  32. data/spec/support/deep_eql.rb +14 -7
  33. data/spec/support/pet_mapper.rb +0 -2
  34. data/spec/unit/yaks/collection_mapper_spec.rb +24 -2
  35. data/spec/unit/yaks/config/dsl_spec.rb +6 -10
  36. data/spec/unit/yaks/config_spec.rb +40 -99
  37. data/spec/unit/yaks/default_policy_spec.rb +20 -0
  38. data/spec/unit/yaks/format/collection_json_spec.rb +41 -0
  39. data/spec/unit/yaks/format/hal_spec.rb +38 -3
  40. data/spec/unit/yaks/format/json_api_spec.rb +2 -2
  41. data/spec/unit/yaks/format_spec.rb +28 -3
  42. data/spec/unit/yaks/fp/callable_spec.rb +13 -0
  43. data/spec/unit/yaks/mapper_spec.rb +226 -126
  44. data/spec/unit/yaks/resource/link_spec.rb +2 -3
  45. data/spec/unit/yaks/resource_spec.rb +15 -0
  46. data/spec/unit/yaks/runner_spec.rb +260 -0
  47. data/spec/unit/yaks/util_spec.rb +7 -1
  48. data/yaks.gemspec +4 -1
  49. metadata +72 -15
  50. /data/spec/json/{hal_plant_collection.json → plant_collection.hal.json} +0 -0
@@ -12,192 +12,292 @@ RSpec.describe Yaks::Mapper do
12
12
 
13
13
  its(:env) { should equal rack_env }
14
14
 
15
- context 'with attributes' do
16
- before do
17
- mapper_class.attributes :foo, :bar
18
- end
19
-
20
- it 'should make the configured attributes available on the instance' do
21
- expect(mapper.attributes).to eq [
22
- Yaks::Mapper::Attribute.new(:foo),
23
- Yaks::Mapper::Attribute.new(:bar)
24
- ]
25
- end
26
-
27
- it 'should load them from the model' do
28
- expect(resource.attributes).to eq(foo: 'hello', bar: 'world')
29
- end
30
-
31
- context 'with attribute filtering' do
15
+ describe '#call' do
16
+ context 'with attributes' do
32
17
  before do
33
- mapper_class.class_eval do
34
- def attributes
35
- super.reject {|attr| attr.name == :foo}
36
- end
37
- end
18
+ mapper_class.attributes :foo, :bar
38
19
  end
39
20
 
40
- it 'should only map the non-filtered attributes' do
41
- expect(resource.attributes).to eq(:bar => 'world')
21
+ it 'should make the configured attributes available on the instance' do
22
+ expect(mapper.attributes).to eq [
23
+ Yaks::Mapper::Attribute.new(:foo),
24
+ Yaks::Mapper::Attribute.new(:bar)
25
+ ]
42
26
  end
43
- end
44
- end
45
27
 
46
- context 'with links' do
47
- before do
48
- mapper_class.link :profile, 'http://foo/bar'
49
- end
28
+ it 'should load them from the model' do
29
+ expect(resource.attributes).to eq(foo: 'hello', bar: 'world')
30
+ end
50
31
 
51
- it 'should map the link' do
52
- expect(resource.links).to eq [
53
- Yaks::Resource::Link.new(:profile, 'http://foo/bar', {})
54
- ]
55
- end
32
+ context 'with attribute filtering' do
33
+ before do
34
+ mapper_class.class_eval do
35
+ def attributes
36
+ super.reject {|attr| attr.name == :foo}
37
+ end
38
+ end
39
+ end
56
40
 
57
- it 'should use the link in the resource' do
58
- expect(resource.links).to include Yaks::Resource::Link.new(:profile, 'http://foo/bar', {})
41
+ it 'should only map the non-filtered attributes' do
42
+ expect(resource.attributes).to eq(:bar => 'world')
43
+ end
44
+ end
59
45
  end
60
46
 
61
- context 'with the same link rel defined multiple times' do
47
+ context 'with links' do
62
48
  before do
63
- mapper_class.class_eval do
64
- link(:self, 'http://foo/bam')
65
- link(:self, 'http://foo/baz')
66
- link(:self, 'http://foo/baq')
67
- end
49
+ mapper_class.link :profile, 'http://foo/bar'
68
50
  end
69
51
 
70
- it 'should map all the links' do
52
+ it 'should map the link' do
71
53
  expect(resource.links).to eq [
72
- Yaks::Resource::Link.new(:profile, 'http://foo/bar', {}),
73
- Yaks::Resource::Link.new(:self, 'http://foo/bam', {}),
74
- Yaks::Resource::Link.new(:self, 'http://foo/baz', {}),
75
- Yaks::Resource::Link.new(:self, 'http://foo/baq', {})
54
+ Yaks::Resource::Link.new(:profile, 'http://foo/bar', {})
76
55
  ]
77
56
  end
78
- end
79
- end
80
57
 
81
- context 'with subresources' do
82
- let(:widget) { fake(type: 'super_widget') }
83
- let(:instance) { fake(widget: widget) }
84
- let(:widget_mapper) { Class.new(Yaks::Mapper) { type 'widget' } }
85
- fake(:policy) { Yaks::DefaultPolicy }
86
-
87
- describe 'has_one' do
88
- let(:has_one_opts) do
89
- { mapper: widget_mapper,
90
- rel: 'http://foo.bar/rels/widgets' }
58
+ it 'should use the link in the resource' do
59
+ expect(resource.links).to include Yaks::Resource::Link.new(:profile, 'http://foo/bar', {})
91
60
  end
92
61
 
93
- before do
94
- widget_mapper.attributes :type
95
- mapper_class.has_one(:widget, has_one_opts)
96
- end
97
-
98
-
99
- it 'should have the subresource in the resource' do
100
- expect(resource.subresources).to eq("http://foo.bar/rels/widgets" => Yaks::Resource.new(type: 'widget', attributes: {:type => "super_widget"}))
101
- end
62
+ context 'with the same link rel defined multiple times' do
63
+ before do
64
+ mapper_class.class_eval do
65
+ link(:self, 'http://foo/bam')
66
+ link(:self, 'http://foo/baz')
67
+ link(:self, 'http://foo/baq')
68
+ end
69
+ end
102
70
 
103
- context 'with explicit mapper and rel' do
104
- it 'should delegate to the given mapper' do
105
- expect(resource.subresources).to eq(
106
- "http://foo.bar/rels/widgets" => Yaks::Resource.new(type: 'widget', attributes: {:type => "super_widget"})
107
- )
71
+ it 'should map all the links' do
72
+ expect(resource.links).to eq [
73
+ Yaks::Resource::Link.new(:profile, 'http://foo/bar', {}),
74
+ Yaks::Resource::Link.new(:self, 'http://foo/bam', {}),
75
+ Yaks::Resource::Link.new(:self, 'http://foo/baz', {}),
76
+ Yaks::Resource::Link.new(:self, 'http://foo/baq', {})
77
+ ]
108
78
  end
109
79
  end
80
+ end
81
+
82
+ context 'with subresources' do
83
+ let(:widget) { fake(type: 'super_widget') }
84
+ let(:instance) { fake(widget: widget) }
85
+ let(:widget_mapper) { Class.new(Yaks::Mapper) { type 'widget' } }
86
+ fake(:policy) { Yaks::DefaultPolicy }
110
87
 
111
- context 'with unspecified mapper' do
88
+ describe 'has_one' do
112
89
  let(:has_one_opts) do
113
- { rel: 'http://foo.bar/rels/widgets' }
90
+ { mapper: widget_mapper,
91
+ rel: 'http://foo.bar/rels/widgets' }
114
92
  end
115
93
 
116
94
  before do
117
- stub(policy).derive_mapper_from_association(mapper.associations.first) do
118
- widget_mapper
119
- end
95
+ widget_mapper.attributes :type
96
+ mapper_class.has_one(:widget, has_one_opts)
120
97
  end
121
98
 
122
- it 'should derive the mapper based on policy' do
123
- expect(resource.subresources).to eq(
124
- "http://foo.bar/rels/widgets" => Yaks::Resource.new(type: 'widget', attributes: {:type => "super_widget"})
125
- )
126
- end
127
- end
128
99
 
129
- context 'with unspecified rel' do
130
- let(:has_one_opts) do
131
- { mapper: widget_mapper }
100
+ it 'should have the subresource in the resource' do
101
+ expect(resource.subresources).to eq("http://foo.bar/rels/widgets" => Yaks::Resource.new(type: 'widget', attributes: {:type => "super_widget"}))
132
102
  end
133
103
 
134
- before do
135
- stub(policy).derive_rel_from_association(mapper.associations.first) do
136
- 'http://rel/rel'
104
+ context 'with explicit mapper and rel' do
105
+ it 'should delegate to the given mapper' do
106
+ expect(resource.subresources).to eq(
107
+ "http://foo.bar/rels/widgets" => Yaks::Resource.new(type: 'widget', attributes: {:type => "super_widget"})
108
+ )
137
109
  end
138
110
  end
139
111
 
140
- it 'should derive the rel based on policy' do
141
- expect(resource.subresources).to eq(
142
- "http://rel/rel" => Yaks::Resource.new(type: 'widget', attributes: {:type => "super_widget"})
143
- )
112
+ context 'with unspecified mapper' do
113
+ let(:has_one_opts) do
114
+ { rel: 'http://foo.bar/rels/widgets' }
115
+ end
116
+
117
+ before do
118
+ stub(policy).derive_mapper_from_association(mapper.associations.first) do
119
+ widget_mapper
120
+ end
121
+ end
122
+
123
+ it 'should derive the mapper based on policy' do
124
+ expect(resource.subresources).to eq(
125
+ "http://foo.bar/rels/widgets" => Yaks::Resource.new(type: 'widget', attributes: {:type => "super_widget"})
126
+ )
127
+ end
144
128
  end
145
- end
146
129
 
147
- context 'with the association filtered out' do
148
- before do
149
- mapper_class.class_eval do
150
- def associations
151
- []
130
+ context 'with unspecified rel' do
131
+ let(:has_one_opts) do
132
+ { mapper: widget_mapper }
133
+ end
134
+
135
+ before do
136
+ stub(policy).derive_rel_from_association(mapper.associations.first) do
137
+ 'http://rel/rel'
152
138
  end
153
139
  end
140
+
141
+ it 'should derive the rel based on policy' do
142
+ expect(resource.subresources).to eq(
143
+ "http://rel/rel" => Yaks::Resource.new(type: 'widget', attributes: {:type => "super_widget"})
144
+ )
145
+ end
154
146
  end
155
147
 
156
- it 'should not map the resource' do
157
- expect(resource.subresources).to eq({})
148
+ context 'with the association filtered out' do
149
+ before do
150
+ mapper_class.class_eval do
151
+ def associations
152
+ []
153
+ end
154
+ end
155
+ end
156
+
157
+ it 'should not map the resource' do
158
+ expect(resource.subresources).to eq({})
159
+ end
158
160
  end
159
161
  end
160
162
  end
161
- end
162
163
 
163
- context 'when the mapper implements a method with the attribute name' do
164
- before do
165
- mapper_class.class_eval do
166
- attributes :fooattr, :bar
164
+ context 'when the mapper implements a method with the attribute name' do
165
+ before do
166
+ mapper_class.class_eval do
167
+ attributes :fooattr, :bar
167
168
 
168
- def fooattr
169
- "#{object.foo} my friend"
169
+ def fooattr
170
+ "#{object.foo} my friend"
171
+ end
170
172
  end
171
173
  end
174
+
175
+ it 'should get the attribute from the mapper' do
176
+ expect(resource.attributes).to eq(fooattr: 'hello my friend', bar: 'world')
177
+ end
172
178
  end
173
179
 
174
- it 'should get the attribute from the mapper' do
175
- expect(resource.attributes).to eq(fooattr: 'hello my friend', bar: 'world')
180
+ context 'with a nil subject' do
181
+ it 'should return a NullResource when the subject is nil' do
182
+ expect(mapper.call(nil)).to be_a Yaks::NullResource
183
+ end
184
+ end
185
+
186
+ context 'with a link generated by a method that returns nil' do
187
+ before do
188
+ mapper_class.class_eval do
189
+ attributes :id
190
+ link :bar_link, :link_generating_method
191
+
192
+ def link_generating_method
193
+ end
194
+ end
195
+ end
196
+
197
+ it 'should not render the link' do
198
+ expect(mapper.call(fake(id: 123))).to eql Yaks::Resource.new(
199
+ type: 'foo',
200
+ attributes: {id: 123}
201
+ )
202
+ end
176
203
  end
177
204
  end
178
205
 
179
- context 'with a nil subject' do
180
- it 'should return a NullResource when the subject is nil' do
181
- expect(mapper.call(nil)).to be_a Yaks::NullResource
206
+ describe '.mapper_name' do
207
+ context 'with a type configured' do
208
+ it 'should use the type' do
209
+ expect(mapper_class.mapper_name(policy)).to eql 'foo'
210
+ end
211
+ end
212
+
213
+ context 'without a type configured' do
214
+ let(:mapper_class) { PetMapper }
215
+
216
+ it 'should infer the type from the name' do
217
+ expect(mapper_class.mapper_name(policy)).to eql 'pet'
218
+ end
182
219
  end
183
220
  end
184
221
 
185
- context 'with a link generated by a method that returns nil' do
186
- before do
187
- mapper_class.class_eval do
188
- attributes :id
189
- link :bar_link, :link_generating_method
222
+ describe '#mapper_name' do
223
+ context 'with a type configured' do
224
+ it 'should use the type' do
225
+ expect(mapper.mapper_name).to eql 'foo'
226
+ end
227
+ end
228
+
229
+ context 'without a type configured' do
230
+ let(:mapper_class) { PetMapper }
190
231
 
191
- def link_generating_method
232
+ it 'should infer the type from the name' do
233
+ expect(mapper.mapper_name).to eql 'pet'
234
+ end
235
+ end
236
+ end
237
+
238
+ describe '#load_attribute' do
239
+ context 'with the attribute defined as a method on the mapper' do
240
+ it 'should call the local implementation' do
241
+ def mapper.foo
242
+ 14
192
243
  end
244
+
245
+ expect(mapper.load_attribute(:foo)).to eql 14
246
+ end
247
+ end
248
+
249
+ context 'without a mapper method with the attribute name' do
250
+ it 'should call the method on the object being mapped' do
251
+ mapper.call(instance) # set @object
252
+ expect(mapper.load_attribute(:foo)).to eql 'hello'
193
253
  end
194
254
  end
255
+ end
256
+
257
+ describe '#map_attributes' do
258
+ let(:attribute) { fake('Attribute') }
259
+
260
+ it 'should receive a context' do
261
+ stub(attribute).add_to_resource(any_args) {|r,_,_| Yaks::Resource.new}
262
+
263
+ mapper.config.attributes[0..-1] = [attribute]
264
+ mapper.call(instance)
265
+
266
+ expect(attribute).to have_received.add_to_resource(any(Yaks::Resource), mapper, yaks_context)
267
+ end
268
+ end
269
+
270
+ describe '#map_links' do
271
+ let(:link) { fake('Link') }
272
+
273
+ it 'should receive a context' do
274
+ stub(link).add_to_resource(any_args) {|r,_,_| Yaks::Resource.new}
275
+
276
+ mapper.config.links[0..-1] = [link]
277
+ mapper.call(instance)
278
+
279
+ expect(link).to have_received.add_to_resource(any(Yaks::Resource), mapper, yaks_context)
280
+ end
281
+ end
282
+
283
+ describe '#map_subresources' do
284
+ let(:association) { fake('Association') }
285
+
286
+ it 'should receive a context' do
287
+ stub(association).add_to_resource(any_args) {|r,_,_| Yaks::Resource.new}
288
+
289
+ mapper.config.associations[0..-1] = [association]
290
+ mapper.call(instance)
291
+
292
+ expect(association).to have_received.add_to_resource(any(Yaks::Resource), mapper, yaks_context)
293
+ end
294
+ end
295
+
296
+ describe '#mapper_stack' do
297
+ let(:yaks_context) { super().merge(mapper_stack: [:foo]) }
195
298
 
196
- it 'should not render the link' do
197
- expect(mapper.call(fake(id: 123))).to eql Yaks::Resource.new(
198
- type: 'foo',
199
- attributes: {id: 123}
200
- )
299
+ it 'should delegate to context' do
300
+ expect(mapper.mapper_stack).to eql [:foo]
201
301
  end
202
302
  end
203
303
  end
@@ -4,13 +4,12 @@ RSpec.describe Yaks::Resource::Link do
4
4
  subject(:link) { described_class.new(rel, uri, options) }
5
5
  let(:rel) { :foo_rel }
6
6
  let(:uri) { 'http://api.example.org/rel/foo' }
7
- let(:options) { {name: 'jimmy', title: 'mr. spectacular' } }
7
+ let(:options) { { title: 'mr. spectacular' } }
8
8
 
9
9
  its(:rel) { should eql :foo_rel }
10
10
  its(:uri) { should eql 'http://api.example.org/rel/foo' }
11
- its(:options) { should eql(name: 'jimmy', title: 'mr. spectacular') }
11
+ its(:options) { should eql(title: 'mr. spectacular') }
12
12
 
13
- its(:name) { should eql('jimmy') }
14
13
  its(:title) { should eql('mr. spectacular') }
15
14
  its(:templated?) { should be false }
16
15
 
@@ -90,4 +90,19 @@ RSpec.describe Yaks::Resource do
90
90
  )
91
91
  end
92
92
  end
93
+
94
+ describe '#self_link' do
95
+ let(:init_opts) {
96
+ { links:
97
+ [
98
+ Yaks::Resource::Link.new(:self, 'foo', {}),
99
+ Yaks::Resource::Link.new(:self, 'bar', {}),
100
+ Yaks::Resource::Link.new(:profile, 'baz', {})
101
+ ]
102
+ }
103
+ }
104
+ it 'should return the last self link' do
105
+ expect(resource.self_link).to eql Yaks::Resource::Link.new(:self, 'bar', {})
106
+ end
107
+ end
93
108
  end