yaks 0.0.0 → 0.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.
- 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
|