yaks 0.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +13 -5
- data/.gitignore +1 -0
- data/.travis.yml +23 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +32 -6
- data/README.md +182 -80
- data/examples/hal01.rb +126 -0
- data/examples/jsonapi01.rb +68 -0
- data/examples/jsonapi02.rb +62 -0
- data/examples/jsonapi03.rb +86 -0
- data/lib/yaks.rb +55 -25
- data/lib/yaks/collection_mapper.rb +33 -0
- data/lib/yaks/collection_resource.rb +65 -0
- data/lib/yaks/default_policy.rb +13 -0
- data/lib/yaks/hal_serializer.rb +59 -0
- data/lib/yaks/json_api_serializer.rb +59 -0
- data/lib/yaks/link_lookup.rb +23 -0
- data/lib/yaks/mapper.rb +59 -0
- data/lib/yaks/mapper/association.rb +43 -0
- data/lib/yaks/mapper/class_methods.rb +36 -0
- data/lib/yaks/mapper/config.rb +79 -0
- data/lib/yaks/mapper/has_many.rb +15 -0
- data/lib/yaks/mapper/has_one.rb +10 -0
- data/lib/yaks/mapper/link.rb +74 -0
- data/lib/yaks/mapper/lookup.rb +19 -0
- data/lib/yaks/mapper/map_links.rb +17 -0
- data/lib/yaks/null_resource.rb +28 -0
- data/lib/yaks/primitivize.rb +34 -15
- data/lib/yaks/profile_registry.rb +60 -0
- data/lib/yaks/rel_registry.rb +20 -0
- data/lib/yaks/resource.rb +28 -0
- data/lib/yaks/resource/link.rb +21 -0
- data/lib/yaks/serializer.rb +11 -53
- data/lib/yaks/shared_options.rb +15 -0
- data/lib/yaks/util.rb +76 -5
- data/lib/yaks/version.rb +1 -1
- data/spec/integration/map_to_resource_spec.rb +30 -0
- data/spec/json/hal_plant_collection.json +34 -0
- data/spec/spec_helper.rb +10 -1
- data/spec/support/friends_mapper.rb +29 -0
- data/spec/support/pet_mapper.rb +5 -0
- data/spec/support/pet_peeve_mapper.rb +3 -0
- data/spec/support/serializers.rb +11 -11
- data/spec/support/shared_contexts.rb +47 -0
- data/spec/support/shorthands.rb +22 -0
- data/spec/yaks/collection_resource_spec.rb +9 -0
- data/spec/yaks/hal_serializer_spec.rb +9 -0
- data/spec/yaks/mapper/association_spec.rb +21 -0
- data/spec/yaks/mapper/class_methods_spec.rb +28 -0
- data/spec/yaks/mapper/config_spec.rb +77 -0
- data/spec/yaks/mapper/has_one_spec.rb +16 -0
- data/spec/yaks/mapper/link_spec.rb +42 -0
- data/spec/yaks/mapper/map_links_spec.rb +46 -0
- data/spec/yaks/mapper_spec.rb +47 -0
- data/spec/yaks/resource_spec.rb +23 -0
- data/yaks.gemspec +6 -3
- metadata +115 -27
- data/lib/yaks/dumper.rb +0 -23
- data/lib/yaks/fold_ams_compat.rb +0 -33
- data/lib/yaks/fold_json_api.rb +0 -61
- data/lib/yaks/serializable_association.rb +0 -21
- data/lib/yaks/serializable_collection.rb +0 -10
- data/lib/yaks/serializable_object.rb +0 -18
- data/lib/yaks/serializer/class_methods.rb +0 -76
- data/spec/integration_spec.rb +0 -57
- data/spec/yaks/json_api_folder_spec.rb +0 -63
- data/spec/yaks/serializer_spec.rb +0 -12
@@ -0,0 +1,34 @@
|
|
1
|
+
{
|
2
|
+
"_links" : {
|
3
|
+
"profile" : { "href" : "http://api.example.com/doc/plant_collection" },
|
4
|
+
"self" : { "href" : "http://api.example.com/plants" }
|
5
|
+
},
|
6
|
+
"_embedded" : {
|
7
|
+
"http://api.example.com/doc/plant_collection" : [
|
8
|
+
{
|
9
|
+
"_links" : {
|
10
|
+
"profile" : { "href" : "http://api.example.com/doc/plant" },
|
11
|
+
"self" : { "href" : "http://api.example.com/plants/7/plain_grass" }
|
12
|
+
},
|
13
|
+
"name" : "Plain grass",
|
14
|
+
"type" : "grass"
|
15
|
+
},
|
16
|
+
{
|
17
|
+
"_links" : {
|
18
|
+
"profile" : { "href" : "http://api.example.com/doc/plant" },
|
19
|
+
"self" : { "href" : "http://api.example.com/plants/15/oak" }
|
20
|
+
},
|
21
|
+
"name" : "Oak",
|
22
|
+
"type" : "tree"
|
23
|
+
},
|
24
|
+
{
|
25
|
+
"_links" : {
|
26
|
+
"profile" : { "href" : "http://api.example.com/doc/plant" },
|
27
|
+
"self" : { "href" : "http://api.example.com/plants/33/passiflora" }
|
28
|
+
},
|
29
|
+
"name" : "Passiflora",
|
30
|
+
"type" : "flower"
|
31
|
+
}
|
32
|
+
]
|
33
|
+
}
|
34
|
+
}
|
data/spec/spec_helper.rb
CHANGED
@@ -6,7 +6,16 @@ $LOAD_PATH.unshift(ROOT.join('lib'))
|
|
6
6
|
|
7
7
|
require 'yaks'
|
8
8
|
require 'virtus'
|
9
|
+
require 'json'
|
9
10
|
|
10
11
|
require_relative 'support/models'
|
11
|
-
require_relative 'support/
|
12
|
+
require_relative 'support/pet_mapper'
|
13
|
+
require_relative 'support/pet_peeve_mapper'
|
14
|
+
require_relative 'support/friends_mapper'
|
12
15
|
require_relative 'support/fixtures'
|
16
|
+
require_relative 'support/shorthands'
|
17
|
+
require_relative 'support/shared_contexts'
|
18
|
+
|
19
|
+
def load_json_fixture(name)
|
20
|
+
JSON.parse(ROOT.join('spec/json', name + '.json').read)
|
21
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class FriendMapper < Yaks::Mapper
|
2
|
+
attributes :id, :name
|
3
|
+
|
4
|
+
link :copyright, '/api/copyright/{year}'
|
5
|
+
|
6
|
+
def year
|
7
|
+
2024
|
8
|
+
end
|
9
|
+
|
10
|
+
has_one :pet_peeve, mapper: PetPeeveMapper
|
11
|
+
has_many :pets, mapper: PetMapper
|
12
|
+
|
13
|
+
# has_one :production_company, # retrieve as `show.production_company`
|
14
|
+
# profile: :company, # use the company profile link from the registry, e.g. 'http://foo.api/profiles/company'
|
15
|
+
# as: :producer, # serialize as {"producers" => []} in JSON-API or [{class: ["producer"]}] in Siren
|
16
|
+
# embed: :link, # don't embed the whole data, link to it, could also be embed: :resource
|
17
|
+
# mapper: CompanyMapper, # use this to find the resource URL, or to map the embedded resource
|
18
|
+
# rel: :show_production_company # find the relation in the relation registry, e.g. http://foo.apo/rels/show_production_company
|
19
|
+
|
20
|
+
# # Full derived defaults
|
21
|
+
# has_one :production_company
|
22
|
+
# # profile: :production_company, # same as the 'name'
|
23
|
+
# # as: :production_company, # same as the 'name'
|
24
|
+
# # embed: :links # depends on what is configured as default
|
25
|
+
# # mapper: CompanyMapper # found by matching the profile
|
26
|
+
# # rel: :show_production_company # "#{context.profile_name}_#{relation.profile_name}"
|
27
|
+
|
28
|
+
# # has_many :pets
|
29
|
+
end
|
data/spec/support/serializers.rb
CHANGED
@@ -1,14 +1,14 @@
|
|
1
|
-
class FriendSerializer < Yaks::
|
2
|
-
|
1
|
+
# class FriendSerializer < Yaks::Mapper
|
2
|
+
# attributes :id, :name
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
end
|
4
|
+
# has_many :pets
|
5
|
+
# has_one :pet_peeve
|
6
|
+
# end
|
7
7
|
|
8
|
-
class PetSerializer < Yaks::
|
9
|
-
|
10
|
-
end
|
8
|
+
# class PetSerializer < Yaks::Mapper
|
9
|
+
# attributes :id, :name, :species
|
10
|
+
# end
|
11
11
|
|
12
|
-
class PetPeeveSerializer < Yaks::
|
13
|
-
|
14
|
-
end
|
12
|
+
# class PetPeeveSerializer < Yaks::Mapper
|
13
|
+
# attributes :id, :type
|
14
|
+
# end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
shared_context 'collection resource' do
|
2
|
+
let(:resource) { Yaks::CollectionResource.new(links, members) }
|
3
|
+
let(:links) { [] }
|
4
|
+
let(:members) { [] }
|
5
|
+
end
|
6
|
+
|
7
|
+
shared_context 'plant collection resource' do
|
8
|
+
include_context 'collection resource'
|
9
|
+
|
10
|
+
let(:links) { [ plants_self_link, plants_profile_link ] }
|
11
|
+
let(:members) { [ plain_grass, oak, passiflora ] }
|
12
|
+
|
13
|
+
[
|
14
|
+
[ :plant , :profile , 'http://api.example.com/doc/plant' ],
|
15
|
+
[ :plants , :profile , 'http://api.example.com/doc/plant_collection' ],
|
16
|
+
[ :plants , :self , 'http://api.example.com/plants' ],
|
17
|
+
[ :plain_grass , :self , 'http://api.example.com/plants/7/plain_grass' ],
|
18
|
+
[ :oak , :self , 'http://api.example.com/plants/15/oak' ],
|
19
|
+
[ :passiflora , :self , 'http://api.example.com/plants/33/passiflora' ],
|
20
|
+
].each do |name, type, uri|
|
21
|
+
let(:"#{name}_#{type}_link") { Yaks::Resource::Link.new(type, uri, {}) }
|
22
|
+
end
|
23
|
+
|
24
|
+
let(:plain_grass) do
|
25
|
+
Yaks::Resource.new(
|
26
|
+
{name: "Plain grass", type: "grass"},
|
27
|
+
[plain_grass_self_link, plant_profile_link],
|
28
|
+
{}
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
let(:oak) do
|
33
|
+
Yaks::Resource.new(
|
34
|
+
{name: "Oak", type: "tree"},
|
35
|
+
[oak_self_link, plant_profile_link],
|
36
|
+
{}
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
let(:passiflora) do
|
41
|
+
Yaks::Resource.new(
|
42
|
+
{name: "Passiflora", type: "flower"},
|
43
|
+
[passiflora_self_link, plant_profile_link],
|
44
|
+
{}
|
45
|
+
)
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
shared_context 'shorthands' do
|
2
|
+
let(:resource) {
|
3
|
+
->(attrs, links = nil) {
|
4
|
+
Yaks::Resource.new(Yaks::Hash(attrs), links, nil)
|
5
|
+
}
|
6
|
+
}
|
7
|
+
|
8
|
+
let(:collection_resource) {
|
9
|
+
->(*attr_hashes) {
|
10
|
+
Yaks::CollectionResource.new(
|
11
|
+
nil,
|
12
|
+
Yaks::List(attr_hashes.map(&resource.method(:call)))
|
13
|
+
)
|
14
|
+
}
|
15
|
+
}
|
16
|
+
|
17
|
+
let(:resource_link) {
|
18
|
+
->(rel, uri, options = {}) {
|
19
|
+
Yaks::Resource::Link.new(rel, uri, options)
|
20
|
+
}
|
21
|
+
}
|
22
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Yaks::Mapper::Association do
|
4
|
+
describe 'self_link' do
|
5
|
+
context 'without a :self link' do
|
6
|
+
subject { described_class.new(nil, nil, nil, Yaks::List(Yaks::Mapper::Link.new(:alternate, '/foo')), {}) }
|
7
|
+
|
8
|
+
it 'should be nil' do
|
9
|
+
expect(subject.self_link).to be_nil
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'with a self link' do
|
15
|
+
subject { described_class.new(nil, nil, nil, Yaks::List(Yaks::Mapper::Link.new(:self, '/self')), {}) }
|
16
|
+
|
17
|
+
it 'should resolve to the self link' do
|
18
|
+
expect(subject.self_link).to eq Yaks::Mapper::Link.new(:self, '/self')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Yaks::Mapper::ClassMethods do
|
4
|
+
subject { Class.new { extend Yaks::Mapper::ClassMethods } }
|
5
|
+
|
6
|
+
describe 'attributes' do
|
7
|
+
before do
|
8
|
+
subject.attributes(:foo, :bar)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should allow setting them' do
|
12
|
+
expect( subject.attributes ).to eq Hamster.list(:foo, :bar)
|
13
|
+
end
|
14
|
+
|
15
|
+
describe 'with inheritance' do
|
16
|
+
let(:child) { Class.new(subject) }
|
17
|
+
before { child.attributes(:baz) }
|
18
|
+
|
19
|
+
it 'should inherit attributes from the parent' do
|
20
|
+
expect(child.attributes).to eq Hamster.list(:foo, :bar, :baz)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should not alter the parent' do
|
24
|
+
expect(subject.attributes).to eq Hamster.list(:foo, :bar)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Yaks::Mapper::Config do
|
4
|
+
subject(:config) { described_class.new }
|
5
|
+
|
6
|
+
describe 'attributes' do
|
7
|
+
context 'an empty config' do
|
8
|
+
it 'should return an empty attributes list' do
|
9
|
+
expect(config.attributes).to eq Yaks::List()
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should add attributes' do
|
14
|
+
expect(config.attributes(:foo, :bar, :baz).attributes)
|
15
|
+
.to eq Hamster.list(:foo, :bar, :baz)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should be chainable' do
|
19
|
+
expect(
|
20
|
+
config
|
21
|
+
.attributes(:foo, :bar)
|
22
|
+
.attributes(:baz)
|
23
|
+
.attributes
|
24
|
+
).to eq Hamster.list(:foo, :bar, :baz)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe 'links' do
|
29
|
+
context 'an empty config' do
|
30
|
+
it 'should have an empty link list' do
|
31
|
+
expect(config.links).to eq Yaks::List()
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe 'adding a link' do
|
36
|
+
let(:config) { subject.link(:self, '/foo/bar/{id}') }
|
37
|
+
|
38
|
+
it 'should have it in the link list' do
|
39
|
+
expect(config.links).to eq Yaks::List(Yaks::Mapper::Link.new(:self, '/foo/bar/{id}'))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe 'setting a profile' do
|
44
|
+
let(:config) { subject.profile :post }
|
45
|
+
|
46
|
+
it 'should set the profile name' do
|
47
|
+
expect(config.profile).to eq :post
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe 'associations' do
|
53
|
+
describe 'has_one' do
|
54
|
+
let(:config) { subject.has_one :mother, mapper: Yaks::Mapper }
|
55
|
+
|
56
|
+
it 'should have the association configured' do
|
57
|
+
expect(config.associations).to eq Yaks::List(Yaks::Mapper::HasOne.new(:mother, :mother, Yaks::Mapper, Yaks::List(), {}))
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe 'has_many' do
|
62
|
+
let(:config) { subject.has_many :shoes, mapper: Yaks::Mapper }
|
63
|
+
|
64
|
+
it 'should have the association configured' do
|
65
|
+
expect(config.associations).to eq Yaks::List(Yaks::Mapper::HasMany.new(:shoes, :shoes, Yaks::Mapper, Yaks::List(), {}))
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'with an :as alternate key' do
|
70
|
+
let(:config) { subject.has_many :shoes, as: :footwear, mapper: Yaks::Mapper }
|
71
|
+
|
72
|
+
it 'should have the given value as its "key"' do
|
73
|
+
expect(config.associations.first.key).to eq :footwear
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Yaks::Mapper::HasOne do
|
4
|
+
include_context 'shorthands'
|
5
|
+
|
6
|
+
AuthorMapper = Class.new(Yaks::Mapper) { attributes :name }
|
7
|
+
|
8
|
+
let(:name) { 'William S. Burroughs' }
|
9
|
+
let(:mapper) { AuthorMapper }
|
10
|
+
let(:has_one) { described_class.new(:author, :author, mapper, [], {}) }
|
11
|
+
let(:author) { Struct.new(:name).new(name) }
|
12
|
+
|
13
|
+
it 'should map to a single Resource' do
|
14
|
+
expect(has_one.map_resource(author, {})).to eq resource[{name: name}, [resource_link[:profile, 'author']]]
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Yaks::Mapper::Link do
|
4
|
+
include_context 'shorthands'
|
5
|
+
|
6
|
+
subject(:link) { described_class.new(rel, template, options) }
|
7
|
+
|
8
|
+
let(:rel) { :next }
|
9
|
+
let(:template) { '/foo/bar/{x}/{y}' }
|
10
|
+
let(:options) { {} }
|
11
|
+
its(:variables) { should eq ['x', 'y'] }
|
12
|
+
its(:uri_template) { should eq URITemplate.new(template) }
|
13
|
+
|
14
|
+
it 'should expand the template' do
|
15
|
+
expect(link.expand(:x => 3, :y => 'foo')).to eq '/foo/bar/3/foo'
|
16
|
+
end
|
17
|
+
|
18
|
+
describe 'expand_with' do
|
19
|
+
it 'should look up expansion values through the provided callable' do
|
20
|
+
expect(link.expand_with(->(var){ var.upcase })).to eq '/foo/bar/X/Y'
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'with expansion turned off' do
|
24
|
+
let(:options) { {expand: false} }
|
25
|
+
|
26
|
+
it 'should keep the template in the response' do
|
27
|
+
expect(link.expand_with(->{ })).to eq '/foo/bar/{x}/{y}'
|
28
|
+
end
|
29
|
+
|
30
|
+
its(:expand?) { should be_false }
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'with a URI without expansion variables' do
|
34
|
+
let(:template) { '/orders' }
|
35
|
+
|
36
|
+
it 'should return the link as is' do
|
37
|
+
expect(link.expand_with(->{ })).to eq '/orders'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Yaks::Mapper::MapLinks do
|
4
|
+
let(:policy) { Class.new(Yaks::DefaultPolicy) { def derive_profile_from_mapper(*); :the_profile ; end }.new }
|
5
|
+
let(:mapper_class) do
|
6
|
+
Class.new(Yaks::Mapper) do
|
7
|
+
def get_title
|
8
|
+
"The Title"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
subject(:mapper) do
|
14
|
+
mapper_class.new(Object.new, policy: policy)
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'with a title proc' do
|
18
|
+
before do
|
19
|
+
mapper_class.link :the_link_rel, '/foo/bar', title: ->{ get_title }
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should resolve the title' do
|
23
|
+
expect(mapper.map_links).to include Yaks::Resource::Link.new(:the_link_rel, '/foo/bar', title: 'The Title')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'with a title string' do
|
28
|
+
before do
|
29
|
+
mapper_class.link :the_link_rel, '/foo/bar', title: 'fixed string'
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should resolve the title' do
|
33
|
+
expect(mapper.map_links).to include Yaks::Resource::Link.new(:the_link_rel, '/foo/bar', title: 'fixed string')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'with no title set' do
|
38
|
+
before do
|
39
|
+
mapper_class.link :the_link_rel, '/foo/bar'
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should resolve the title' do
|
43
|
+
expect(mapper.map_links).to include Yaks::Resource::Link.new(:the_link_rel, '/foo/bar', {})
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|