yaks 0.4.4 → 0.5.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.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/CHANGELOG.md +44 -3
- data/README.md +90 -33
- data/Rakefile +10 -0
- data/bench/bench.rb +0 -1
- data/bench/bench_1000.rb +60 -0
- data/lib/yaks/breaking_changes.rb +22 -0
- data/lib/yaks/config/dsl.rb +114 -27
- data/lib/yaks/config.rb +39 -54
- data/lib/yaks/default_policy.rb +32 -14
- data/lib/yaks/format/collection_json.rb +4 -4
- data/lib/yaks/format/hal.rb +20 -3
- data/lib/yaks/format/json_api.rb +3 -3
- data/lib/yaks/format.rb +54 -9
- data/lib/yaks/fp/callable.rb +9 -0
- data/lib/yaks/fp/hash_updatable.rb +2 -0
- data/lib/yaks/fp/updatable.rb +2 -0
- data/lib/yaks/fp.rb +8 -0
- data/lib/yaks/mapper/link.rb +2 -2
- data/lib/yaks/mapper.rb +6 -6
- data/lib/yaks/primitivize.rb +2 -2
- data/lib/yaks/resource/link.rb +0 -4
- data/lib/yaks/runner.rb +90 -0
- data/lib/yaks/util.rb +4 -0
- data/lib/yaks/version.rb +1 -1
- data/lib/yaks.rb +3 -0
- data/spec/acceptance/acceptance_spec.rb +6 -1
- data/spec/json/confucius.collection.json +5 -16
- data/spec/json/plant_collection.collection.json +32 -0
- data/spec/spec_helper.rb +2 -1
- data/spec/support/deep_eql.rb +14 -7
- data/spec/support/pet_mapper.rb +0 -2
- data/spec/unit/yaks/collection_mapper_spec.rb +24 -2
- data/spec/unit/yaks/config/dsl_spec.rb +6 -10
- data/spec/unit/yaks/config_spec.rb +40 -99
- data/spec/unit/yaks/default_policy_spec.rb +20 -0
- data/spec/unit/yaks/format/collection_json_spec.rb +41 -0
- data/spec/unit/yaks/format/hal_spec.rb +38 -3
- data/spec/unit/yaks/format/json_api_spec.rb +2 -2
- data/spec/unit/yaks/format_spec.rb +28 -3
- data/spec/unit/yaks/fp/callable_spec.rb +13 -0
- data/spec/unit/yaks/mapper_spec.rb +226 -126
- data/spec/unit/yaks/resource/link_spec.rb +2 -3
- data/spec/unit/yaks/resource_spec.rb +15 -0
- data/spec/unit/yaks/runner_spec.rb +260 -0
- data/spec/unit/yaks/util_spec.rb +7 -1
- data/yaks.gemspec +4 -1
- metadata +72 -15
- /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
|
-
|
16
|
-
|
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.
|
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
|
41
|
-
expect(
|
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
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
58
|
-
|
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
|
47
|
+
context 'with links' do
|
62
48
|
before do
|
63
|
-
mapper_class.
|
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
|
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
|
-
|
82
|
-
|
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
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
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
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
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
|
-
|
88
|
+
describe 'has_one' do
|
112
89
|
let(:has_one_opts) do
|
113
|
-
{
|
90
|
+
{ mapper: widget_mapper,
|
91
|
+
rel: 'http://foo.bar/rels/widgets' }
|
114
92
|
end
|
115
93
|
|
116
94
|
before do
|
117
|
-
|
118
|
-
|
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
|
-
|
130
|
-
|
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
|
-
|
135
|
-
|
136
|
-
|
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
|
-
|
141
|
-
|
142
|
-
|
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
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
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
|
-
|
157
|
-
|
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
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
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
|
-
|
169
|
-
|
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
|
-
|
175
|
-
|
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
|
-
|
180
|
-
|
181
|
-
|
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
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
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
|
-
|
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
|
197
|
-
expect(mapper.
|
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) { {
|
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(
|
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
|