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.
Files changed (67) hide show
  1. checksums.yaml +13 -5
  2. data/.gitignore +1 -0
  3. data/.travis.yml +23 -0
  4. data/Gemfile +1 -1
  5. data/Gemfile.lock +32 -6
  6. data/README.md +182 -80
  7. data/examples/hal01.rb +126 -0
  8. data/examples/jsonapi01.rb +68 -0
  9. data/examples/jsonapi02.rb +62 -0
  10. data/examples/jsonapi03.rb +86 -0
  11. data/lib/yaks.rb +55 -25
  12. data/lib/yaks/collection_mapper.rb +33 -0
  13. data/lib/yaks/collection_resource.rb +65 -0
  14. data/lib/yaks/default_policy.rb +13 -0
  15. data/lib/yaks/hal_serializer.rb +59 -0
  16. data/lib/yaks/json_api_serializer.rb +59 -0
  17. data/lib/yaks/link_lookup.rb +23 -0
  18. data/lib/yaks/mapper.rb +59 -0
  19. data/lib/yaks/mapper/association.rb +43 -0
  20. data/lib/yaks/mapper/class_methods.rb +36 -0
  21. data/lib/yaks/mapper/config.rb +79 -0
  22. data/lib/yaks/mapper/has_many.rb +15 -0
  23. data/lib/yaks/mapper/has_one.rb +10 -0
  24. data/lib/yaks/mapper/link.rb +74 -0
  25. data/lib/yaks/mapper/lookup.rb +19 -0
  26. data/lib/yaks/mapper/map_links.rb +17 -0
  27. data/lib/yaks/null_resource.rb +28 -0
  28. data/lib/yaks/primitivize.rb +34 -15
  29. data/lib/yaks/profile_registry.rb +60 -0
  30. data/lib/yaks/rel_registry.rb +20 -0
  31. data/lib/yaks/resource.rb +28 -0
  32. data/lib/yaks/resource/link.rb +21 -0
  33. data/lib/yaks/serializer.rb +11 -53
  34. data/lib/yaks/shared_options.rb +15 -0
  35. data/lib/yaks/util.rb +76 -5
  36. data/lib/yaks/version.rb +1 -1
  37. data/spec/integration/map_to_resource_spec.rb +30 -0
  38. data/spec/json/hal_plant_collection.json +34 -0
  39. data/spec/spec_helper.rb +10 -1
  40. data/spec/support/friends_mapper.rb +29 -0
  41. data/spec/support/pet_mapper.rb +5 -0
  42. data/spec/support/pet_peeve_mapper.rb +3 -0
  43. data/spec/support/serializers.rb +11 -11
  44. data/spec/support/shared_contexts.rb +47 -0
  45. data/spec/support/shorthands.rb +22 -0
  46. data/spec/yaks/collection_resource_spec.rb +9 -0
  47. data/spec/yaks/hal_serializer_spec.rb +9 -0
  48. data/spec/yaks/mapper/association_spec.rb +21 -0
  49. data/spec/yaks/mapper/class_methods_spec.rb +28 -0
  50. data/spec/yaks/mapper/config_spec.rb +77 -0
  51. data/spec/yaks/mapper/has_one_spec.rb +16 -0
  52. data/spec/yaks/mapper/link_spec.rb +42 -0
  53. data/spec/yaks/mapper/map_links_spec.rb +46 -0
  54. data/spec/yaks/mapper_spec.rb +47 -0
  55. data/spec/yaks/resource_spec.rb +23 -0
  56. data/yaks.gemspec +6 -3
  57. metadata +115 -27
  58. data/lib/yaks/dumper.rb +0 -23
  59. data/lib/yaks/fold_ams_compat.rb +0 -33
  60. data/lib/yaks/fold_json_api.rb +0 -61
  61. data/lib/yaks/serializable_association.rb +0 -21
  62. data/lib/yaks/serializable_collection.rb +0 -10
  63. data/lib/yaks/serializable_object.rb +0 -18
  64. data/lib/yaks/serializer/class_methods.rb +0 -76
  65. data/spec/integration_spec.rb +0 -57
  66. data/spec/yaks/json_api_folder_spec.rb +0 -63
  67. 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/serializers'
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
@@ -0,0 +1,5 @@
1
+ class PetMapper < Yaks::Mapper
2
+ attributes :id, :name, :species
3
+
4
+ #link :collection, '/api/pets/{id*}'
5
+ end
@@ -0,0 +1,3 @@
1
+ class PetPeeveMapper < Yaks::Mapper
2
+ attributes :id, :type
3
+ end
@@ -1,14 +1,14 @@
1
- class FriendSerializer < Yaks::Serializer
2
- attributes :id, :name
1
+ # class FriendSerializer < Yaks::Mapper
2
+ # attributes :id, :name
3
3
 
4
- has_many :pets
5
- has_one :pet_peeve
6
- end
4
+ # has_many :pets
5
+ # has_one :pet_peeve
6
+ # end
7
7
 
8
- class PetSerializer < Yaks::Serializer
9
- attributes :id, :name, :species
10
- end
8
+ # class PetSerializer < Yaks::Mapper
9
+ # attributes :id, :name, :species
10
+ # end
11
11
 
12
- class PetPeeveSerializer < Yaks::Serializer
13
- attributes :id, :type
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,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe Yaks::CollectionResource do
4
+ it 'should normalize its arguments' do
5
+ expect(described_class.new(nil, nil)).to eq(
6
+ described_class.new(Yaks::List(), Yaks::List())
7
+ )
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe Yaks::HalSerializer do
4
+ include_context 'plant collection resource'
5
+
6
+ subject { described_class.new(resource).serialize }
7
+
8
+ it { should eq(load_json_fixture('hal_plant_collection')) }
9
+ 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