yaks 0.3.1 → 0.4.0.rc1

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +11 -0
  4. data/Gemfile +0 -2
  5. data/LICENSE +7 -0
  6. data/README.md +160 -35
  7. data/Rakefile +2 -1
  8. data/lib/yaks/collection_mapper.rb +25 -18
  9. data/lib/yaks/collection_resource.rb +11 -17
  10. data/lib/yaks/config.rb +96 -0
  11. data/lib/yaks/default_policy.rb +34 -4
  12. data/lib/yaks/fp.rb +18 -0
  13. data/lib/yaks/mapper/association.rb +19 -27
  14. data/lib/yaks/mapper/class_methods.rb +4 -2
  15. data/lib/yaks/mapper/config.rb +24 -39
  16. data/lib/yaks/mapper/has_many.rb +7 -6
  17. data/lib/yaks/mapper/has_one.rb +4 -3
  18. data/lib/yaks/mapper/link.rb +52 -55
  19. data/lib/yaks/mapper.rb +38 -26
  20. data/lib/yaks/null_resource.rb +3 -3
  21. data/lib/yaks/primitivize.rb +29 -27
  22. data/lib/yaks/resource/link.rb +4 -0
  23. data/lib/yaks/resource.rb +18 -7
  24. data/lib/yaks/serializer/collection_json.rb +38 -0
  25. data/lib/yaks/serializer/hal.rb +55 -0
  26. data/lib/yaks/serializer/json_api.rb +61 -0
  27. data/lib/yaks/serializer.rb +25 -4
  28. data/lib/yaks/util.rb +2 -42
  29. data/lib/yaks/version.rb +1 -1
  30. data/lib/yaks.rb +10 -32
  31. data/notes.org +72 -0
  32. data/shaved_yak.gif +0 -0
  33. data/spec/acceptance/acceptance_spec.rb +46 -0
  34. data/spec/acceptance/models.rb +28 -0
  35. data/spec/integration/map_to_resource_spec.rb +11 -15
  36. data/spec/json/confucius.hal.json +23 -0
  37. data/spec/json/confucius.json_api.json +22 -0
  38. data/spec/json/john.hal.json +29 -0
  39. data/spec/json/youtypeitwepostit.collection.json +45 -0
  40. data/spec/spec_helper.rb +12 -1
  41. data/spec/support/shared_contexts.rb +7 -10
  42. data/spec/support/youtypeit_models_mappers.rb +20 -0
  43. data/spec/unit/yaks/collection_mapper_spec.rb +84 -0
  44. data/spec/unit/yaks/collection_resource_spec.rb +72 -0
  45. data/spec/unit/yaks/config_spec.rb +129 -0
  46. data/spec/unit/yaks/fp_spec.rb +31 -0
  47. data/spec/unit/yaks/mapper/association_spec.rb +80 -0
  48. data/spec/{yaks → unit/yaks}/mapper/class_methods_spec.rb +4 -4
  49. data/spec/unit/yaks/mapper/config_spec.rb +191 -0
  50. data/spec/unit/yaks/mapper/has_many_spec.rb +46 -0
  51. data/spec/unit/yaks/mapper/has_one_spec.rb +34 -0
  52. data/spec/unit/yaks/mapper/link_spec.rb +152 -0
  53. data/spec/unit/yaks/mapper_spec.rb +177 -0
  54. data/spec/unit/yaks/resource_spec.rb +40 -0
  55. data/spec/{yaks/hal_serializer_spec.rb → unit/yaks/serializer/hal_spec.rb} +2 -2
  56. data/spec/unit/yaks/serializer_spec.rb +12 -0
  57. data/spec/unit/yaks/util_spec.rb +43 -0
  58. data/spec/yaml/confucius.yaml +10 -0
  59. data/spec/yaml/youtypeitwepostit.yaml +9 -0
  60. data/yaks.gemspec +7 -8
  61. metadata +92 -53
  62. data/Gemfile.lock +0 -111
  63. data/lib/yaks/hal_serializer.rb +0 -59
  64. data/lib/yaks/json_api_serializer.rb +0 -59
  65. data/lib/yaks/link_lookup.rb +0 -23
  66. data/lib/yaks/mapper/lookup.rb +0 -19
  67. data/lib/yaks/mapper/map_links.rb +0 -17
  68. data/lib/yaks/profile_registry.rb +0 -60
  69. data/lib/yaks/rel_registry.rb +0 -20
  70. data/lib/yaks/shared_options.rb +0 -15
  71. data/spec/support/shorthands.rb +0 -22
  72. data/spec/yaks/collection_resource_spec.rb +0 -9
  73. data/spec/yaks/mapper/association_spec.rb +0 -21
  74. data/spec/yaks/mapper/config_spec.rb +0 -77
  75. data/spec/yaks/mapper/has_one_spec.rb +0 -16
  76. data/spec/yaks/mapper/link_spec.rb +0 -38
  77. data/spec/yaks/mapper/map_links_spec.rb +0 -46
  78. data/spec/yaks/mapper_spec.rb +0 -65
  79. data/spec/yaks/resource_spec.rb +0 -23
@@ -0,0 +1,20 @@
1
+ module Youtypeitwepostit
2
+ class Message
3
+ include Virtus.model
4
+
5
+ attribute :id, Integer
6
+ attribute :text, String
7
+ attribute :date_posted, String
8
+ end
9
+
10
+ class MessageMapper < Yaks::Mapper
11
+ link :self, 'http://www.youtypeitwepostit.com/api/{id}'
12
+ link :profile, 'http://www.youtypeitwepostit.com/profiles/message'
13
+
14
+ attributes :text, :date_posted
15
+ end
16
+
17
+ class CollectionMapper < Yaks::CollectionMapper
18
+ link :self, 'http://www.youtypeitwepostit.com/api/'
19
+ end
20
+ end
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Yaks::CollectionMapper do
4
+ include_context 'fixtures'
5
+
6
+ subject(:mapper) { described_class.new(collection, context) }
7
+ let(:context) {
8
+ { resource_mapper: resource_mapper,
9
+ policy: policy,
10
+ env: {}
11
+ }
12
+ }
13
+ let(:collection) { [] }
14
+ let(:resource_mapper) { Class.new(Yaks::Mapper) { type 'the_type' } }
15
+ let(:policy) { Yaks::DefaultPolicy.new }
16
+
17
+ its(:to_resource) {
18
+ should eql Yaks::CollectionResource.new(
19
+ type: 'the_type',
20
+ links: [],
21
+ attributes: {},
22
+ members: []
23
+ )
24
+ }
25
+
26
+ context 'with members' do
27
+ let(:collection) { [boingboing, wassup]}
28
+ let(:resource_mapper) { PetMapper }
29
+
30
+ its(:to_resource) {
31
+ should eql Yaks::CollectionResource.new(
32
+ type: 'pet',
33
+ links: [],
34
+ attributes: {},
35
+ members: [
36
+ Yaks::Resource.new(type: 'pet', attributes: {:id => 2, :species => "dog", :name => "boingboing"}),
37
+ Yaks::Resource.new(type: 'pet', attributes: {:id => 3, :species => "cat", :name => "wassup"})
38
+ ]
39
+ )
40
+ }
41
+ end
42
+
43
+ context 'with collection attributes' do
44
+ subject(:mapper) {
45
+ Class.new(Yaks::CollectionMapper) do
46
+ attributes :foo, :bar
47
+ end.new(collection, context)
48
+ }
49
+
50
+ let(:collection) {
51
+ Class.new(SimpleDelegator) do
52
+ def foo ; 123 ; end
53
+ def bar ; 'pi la~~~' ; end
54
+ end.new([])
55
+ }
56
+
57
+ its(:to_resource) {
58
+ should eql Yaks::CollectionResource.new(
59
+ type: 'the_type',
60
+ links: [],
61
+ attributes: { foo: 123, bar: 'pi la~~~' },
62
+ members: []
63
+ )
64
+ }
65
+ end
66
+
67
+ context 'with collection links' do
68
+ subject(:mapper) {
69
+ Class.new(Yaks::CollectionMapper) do
70
+ link :self, 'http://api.example.com/orders'
71
+ end.new(collection, context)
72
+ }
73
+
74
+ its(:to_resource) {
75
+ should eql Yaks::CollectionResource.new(
76
+ type: 'the_type',
77
+ links: [ Yaks::Resource::Link.new(:self, 'http://api.example.com/orders', {}) ],
78
+ attributes: { },
79
+ members: []
80
+ )
81
+ }
82
+ end
83
+
84
+ end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Yaks::CollectionResource do
4
+ subject(:collection) { described_class.new(init_opts) }
5
+ let(:init_opts) { {} }
6
+
7
+ its(:collection?) { should equal true }
8
+
9
+ context 'with nothing passed in the contstructor' do
10
+ its(:type) { should be_nil }
11
+ its(:links) { should eql [] }
12
+ its(:attributes) { should eql({}) }
13
+ its(:members) { should eql [] }
14
+ its(:subresources) { should eql({}) }
15
+ end
16
+
17
+ context 'with a full constructor' do
18
+ let(:init_opts) {
19
+ {
20
+ type: 'order',
21
+ links: [
22
+ Yaks::Resource::Link.new('http://rels/summary', 'http://order/10/summary', {}),
23
+ Yaks::Resource::Link.new(:profile, 'http://rels/collection', {})
24
+ ],
25
+ attributes: { total: 10.00 },
26
+ members: [
27
+ Yaks::Resource.new(
28
+ type: 'order',
29
+ links: [Yaks::Resource::Link.new(:self, 'http://order/10', {})],
30
+ attributes: { customer: 'John Doe', price: 10.00 }
31
+ )
32
+ ]
33
+ }
34
+ }
35
+
36
+ its(:type) { should eql 'order' }
37
+ its(:links) { should eql [
38
+ Yaks::Resource::Link.new('http://rels/summary', 'http://order/10/summary', {}),
39
+ Yaks::Resource::Link.new(:profile, 'http://rels/collection', {})
40
+ ]
41
+ }
42
+ its(:attributes) { should eql( total: 10.00 ) }
43
+ its(:members) { should eql [
44
+ Yaks::Resource.new(
45
+ type: 'order',
46
+ links: [Yaks::Resource::Link.new(:self, 'http://order/10', {})],
47
+ attributes: { customer: 'John Doe', price: 10.00 }
48
+ )
49
+ ]
50
+ }
51
+
52
+ its(:subresources) { should eql(
53
+ 'http://rels/collection' => Yaks::CollectionResource.new(
54
+ type: 'order',
55
+ attributes: { total: 10.00 },
56
+ links: [
57
+ Yaks::Resource::Link.new('http://rels/summary', 'http://order/10/summary', {}),
58
+ Yaks::Resource::Link.new(:profile, 'http://rels/collection', {})
59
+ ],
60
+ members: [
61
+ Yaks::Resource.new(
62
+ type: 'order',
63
+ links: [Yaks::Resource::Link.new(:self, 'http://order/10', {})],
64
+ attributes: { customer: 'John Doe', price: 10.00 }
65
+ )
66
+ ]
67
+ )
68
+ )
69
+ }
70
+
71
+ end
72
+ end
@@ -0,0 +1,129 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Yaks::Config do
4
+ include_context 'fixtures'
5
+
6
+ def self.configure(&blk)
7
+ subject(:config) { described_class.new(&blk) }
8
+ end
9
+
10
+ context 'defaults' do
11
+ configure {}
12
+
13
+ its(:default_format) { should equal :hal }
14
+ its(:policy_class) { should < Yaks::DefaultPolicy }
15
+
16
+ it 'should have empty format options' do
17
+ expect(config.options_for_format(:hal)).to eql({})
18
+ end
19
+ end
20
+
21
+ context 'with a default format' do
22
+ configure do
23
+ default_format :json_api
24
+ end
25
+
26
+ its(:default_format) { should equal :json_api }
27
+ end
28
+
29
+ context 'with a custom policy class' do
30
+ MyPolicy = Struct.new(:options)
31
+ configure do
32
+ policy MyPolicy
33
+ end
34
+
35
+ its(:policy_class) { should equal MyPolicy }
36
+ its(:policy) { should be_a MyPolicy }
37
+ end
38
+
39
+ context 'with a rel template' do
40
+ configure do
41
+ rel_template 'http://rel/foo'
42
+ end
43
+
44
+ its(:policy_options) { should eql(rel_template: 'http://rel/foo') }
45
+ end
46
+
47
+ context 'with format options' do
48
+ configure do
49
+ format_options :hal, plural_links: [:self, :profile]
50
+ end
51
+
52
+ specify do
53
+ expect(config.options_for_format(:hal)).to eql(plural_links: [:self, :profile])
54
+ end
55
+ end
56
+
57
+ describe '#serialize' do
58
+ configure do
59
+ rel_template 'http://api.mysuperfriends.com/{association_name}'
60
+ format_options :hal, plural_links: [:copyright]
61
+ end
62
+
63
+ specify do
64
+ expect(config.serialize(john)).to eql(load_json_fixture 'john.hal')
65
+ end
66
+ end
67
+
68
+ describe '#mapper_namespace' do
69
+ module MyMappers
70
+ class PetMapper < Yaks::Mapper
71
+ end
72
+ end
73
+
74
+ configure do
75
+ mapper_namespace MyMappers
76
+ end
77
+
78
+ specify do
79
+ expect(config.policy.derive_mapper_from_object(boingboing)).to eql(MyMappers::PetMapper)
80
+ end
81
+ end
82
+
83
+ describe '#map_to_primitive' do
84
+ class TheMapper < Yaks::Mapper
85
+ attributes :a_date
86
+ end
87
+
88
+ TheModel = Struct.new(:a_date)
89
+
90
+ configure do
91
+ map_to_primitive Date do |object|
92
+ object.iso8601
93
+ end
94
+ end
95
+
96
+ let(:model) {
97
+ TheModel.new(Date.new(2014, 5, 6))
98
+ }
99
+
100
+ specify do
101
+ expect(config.serialize(model, mapper: TheMapper)).to eq({"a_date"=>"2014-05-06"})
102
+ end
103
+ end
104
+
105
+ context 'passing in a rack env' do
106
+ configure do
107
+ default_format :collection_json
108
+ end
109
+
110
+ let(:rack_env) {
111
+ { 'HTTP_ACCEPT' => 'application/hal+json;q=0.8, application/vnd.api+json' }
112
+ }
113
+
114
+ it 'should detect serializer based on accept header' do
115
+ rack_env = { 'HTTP_ACCEPT' => 'application/hal+json;q=0.8, application/vnd.api+json' }
116
+ expect(config.serializer_class({}, rack_env)).to equal Yaks::Serializer::JsonApi
117
+ end
118
+
119
+ it 'should know to pick the best match' do
120
+ rack_env = { 'HTTP_ACCEPT' => 'application/hal+json;q=0.8, application/vnd.api+json;q=0.7' }
121
+ expect(config.serializer_class({}, rack_env)).to equal Yaks::Serializer::Hal
122
+ end
123
+
124
+ it 'should fall back to the default when no mime type is recognized' do
125
+ rack_env = { 'HTTP_ACCEPT' => 'text/html, application/json' }
126
+ expect(config.serializer_class({}, rack_env)).to equal Yaks::Serializer::CollectionJson
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Yaks::FP do
4
+ include described_class
5
+
6
+ describe '#curry_method' do
7
+ def method_3args(a,b,c)
8
+ "#{a}-#{b}-#{c}"
9
+ end
10
+
11
+ it 'should curry the method' do
12
+ expect(curry_method(:method_3args).(1).(2,3)).to eql "1-2-3"
13
+ end
14
+ end
15
+
16
+ describe '#send_with_args' do
17
+ it 'should bind the arguments' do
18
+ expect(send_with_args(:+, 'foo').('bar')).to eql 'barfoo'
19
+ end
20
+
21
+ it 'should bind the block' do
22
+ expect(send_with_args(:map) {|x| x.upcase }.(['bar'])).to eql ['BAR']
23
+ end
24
+ end
25
+
26
+ describe '#identity_function' do
27
+ it 'should return whatever you pass it' do
28
+ expect(identity_function.(:foo)).to equal :foo
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Yaks::Mapper::Association do
4
+ include Yaks::FP
5
+
6
+ let(:name) { :shoes }
7
+ let(:mapper) { Yaks::Mapper }
8
+ let(:rel) { Yaks::Undefined }
9
+ let(:collection_mapper) { Yaks::Undefined }
10
+ let(:parent_mapper) { Yaks::Undefined }
11
+ let(:map_resource) { ->(obj, policy) {} }
12
+ let(:lookup) { ->(*) {} }
13
+ let(:policy) { Yaks::DefaultPolicy.new }
14
+ let(:context) { { policy: policy, env: {} } }
15
+
16
+ its(:name) { should equal :shoes }
17
+
18
+ subject(:association) do
19
+ described_class.new(name, mapper, rel, collection_mapper)
20
+ end
21
+
22
+ describe '#create_subresource' do
23
+ subject(:resource_pair) do
24
+ association.create_subresource(parent_mapper, lookup, context)
25
+ end
26
+
27
+ context 'with a rel specified' do
28
+ let(:rel) { 'http://api.com/rels/shoes' }
29
+
30
+ it 'should use the specified rel' do
31
+ expect(resource_pair[0]).to eql 'http://api.com/rels/shoes'
32
+ end
33
+ end
34
+
35
+ context 'without a rel specified' do
36
+ it 'should infer a rel based on policy' do
37
+ expect(policy)
38
+ .to receive(:derive_rel_from_association)
39
+ .with(parent_mapper, association)
40
+ .and_return('http://api.com/rel/derived')
41
+
42
+ expect(resource_pair[0]).to eql 'http://api.com/rel/derived'
43
+ end
44
+ end
45
+
46
+ let(:lookup) { { shoes: 'unmapped resource' } }
47
+
48
+ it 'should delegate to the map_resource method, to be overridden in child classes' do
49
+ expect(association)
50
+ .to receive(:map_resource)
51
+ .with('unmapped resource', context)
52
+ .and_return('mapped resource')
53
+
54
+ expect(resource_pair[1]).to eql 'mapped resource'
55
+ end
56
+ end
57
+
58
+ describe '#association_mapper' do
59
+ context 'with a specified mapper' do
60
+ let(:mapper) { :a_specific_mapper_class }
61
+
62
+ it 'should return the mapper' do
63
+ expect(association.association_mapper(nil)).to equal :a_specific_mapper_class
64
+ end
65
+ end
66
+
67
+ context 'with the mapper undefined' do
68
+ let(:mapper) { Yaks::Undefined }
69
+
70
+ it 'should derive a mapper based on policy' do
71
+ expect(policy)
72
+ .to receive(:derive_mapper_from_association)
73
+ .with(association)
74
+ .and_return(:a_derived_mapper_class)
75
+
76
+ expect(association.association_mapper(policy)).to equal :a_derived_mapper_class
77
+ end
78
+ end
79
+ end
80
+ end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Yaks::Mapper::ClassMethods do
3
+ RSpec.describe Yaks::Mapper::ClassMethods do
4
4
  subject { Class.new { extend Yaks::Mapper::ClassMethods } }
5
5
 
6
6
  describe 'attributes' do
@@ -9,7 +9,7 @@ describe Yaks::Mapper::ClassMethods do
9
9
  end
10
10
 
11
11
  it 'should allow setting them' do
12
- expect( subject.attributes ).to eq Hamster.list(:foo, :bar)
12
+ expect( subject.attributes ).to eq [:foo, :bar]
13
13
  end
14
14
 
15
15
  describe 'with inheritance' do
@@ -17,11 +17,11 @@ describe Yaks::Mapper::ClassMethods do
17
17
  before { child.attributes(:baz) }
18
18
 
19
19
  it 'should inherit attributes from the parent' do
20
- expect(child.attributes).to eq Hamster.list(:foo, :bar, :baz)
20
+ expect(child.attributes).to eq [:foo, :bar, :baz]
21
21
  end
22
22
 
23
23
  it 'should not alter the parent' do
24
- expect(subject.attributes).to eq Hamster.list(:foo, :bar)
24
+ expect(subject.attributes).to eq [:foo, :bar]
25
25
  end
26
26
  end
27
27
  end
@@ -0,0 +1,191 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Yaks::Mapper::Config do
4
+ Undefined = Yaks::Undefined
5
+
6
+ subject(:config) { described_class.new(nil, [], [], []) }
7
+
8
+ describe '#initialize' do
9
+ subject(:config) { described_class.new('foo', [:a], [:b], [:c]) }
10
+
11
+ its(:type) { should eql 'foo' }
12
+ its(:attributes) { should eql [:a] }
13
+ its(:links) { should eql [:b] }
14
+ its(:associations) { should eql [:c] }
15
+ end
16
+
17
+ describe '#updated' do
18
+ context 'with no updates' do
19
+ let(:config) {
20
+ super()
21
+ .type('foo')
22
+ .attributes(:a, :b, :c)
23
+ .link(:foo, 'http://bar')
24
+ .has_many(:bars)
25
+ }
26
+
27
+ it 'should update attributes' do
28
+ expect(config.updated(attributes: [:foo])).to eql described_class.new(
29
+ 'foo',
30
+ [:foo],
31
+ [Yaks::Mapper::Link.new(:foo, 'http://bar', {})],
32
+ [Yaks::Mapper::HasMany.new(:bars, Undefined, Undefined, Undefined)]
33
+ )
34
+ end
35
+
36
+ it 'should update links' do
37
+ expect(config.updated(links: [:foo])).to eql described_class.new(
38
+ 'foo',
39
+ [:a, :b, :c],
40
+ [:foo],
41
+ [Yaks::Mapper::HasMany.new(:bars, Undefined, Undefined, Undefined)]
42
+ )
43
+ end
44
+
45
+ it 'should update associations' do
46
+ expect(config.updated(associations: [:foo])).to eql described_class.new(
47
+ 'foo',
48
+ [:a, :b, :c],
49
+ [Yaks::Mapper::Link.new(:foo, 'http://bar', {})],
50
+ [:foo]
51
+ )
52
+ end
53
+ end
54
+ end
55
+
56
+ describe '#attributes' do
57
+ context 'an empty config' do
58
+ it 'should return an empty attributes list' do
59
+ expect(config.attributes).to eq []
60
+ end
61
+ end
62
+
63
+ it 'should add attributes' do
64
+ expect(config.attributes(:foo, :bar, :baz).attributes)
65
+ .to eq [:foo, :bar, :baz]
66
+ end
67
+
68
+ it 'should be chainable' do
69
+ expect(
70
+ config
71
+ .attributes(:foo, :bar)
72
+ .attributes(:baz)
73
+ .attributes
74
+ ).to eq [:foo, :bar, :baz]
75
+ end
76
+ end
77
+
78
+ describe '#links' do
79
+ context 'an empty config' do
80
+ it 'should have an empty link list' do
81
+ expect(config.links).to eq []
82
+ end
83
+ end
84
+
85
+ describe 'adding links' do
86
+ let(:config) {
87
+ subject
88
+ .link(:self, '/foo/bar/{id}')
89
+ .link(:profile, '/profile/foo')
90
+ }
91
+
92
+ it 'should have the links in the link list' do
93
+ expect(config.links).to include Yaks::Mapper::Link.new(:profile, '/profile/foo', {})
94
+ expect(config.links).to include Yaks::Mapper::Link.new(:self, '/foo/bar/{id}', {})
95
+ end
96
+ end
97
+
98
+ describe 'links with the same rel' do
99
+ let(:config) {
100
+ subject
101
+ .link(:self, '/foo/self')
102
+ .link(:self, '/foo/me')
103
+ }
104
+
105
+ it 'should have the links in the defined order' do
106
+ expect(config.links).to eql [
107
+ Yaks::Mapper::Link.new(:self, '/foo/self', {}),
108
+ Yaks::Mapper::Link.new(:self, '/foo/me', {})
109
+ ]
110
+ end
111
+ end
112
+ end
113
+
114
+ describe '#has_one' do
115
+ context 'with a mapper specified' do
116
+ let(:config) { subject.has_one :mother, mapper: Yaks::Mapper }
117
+
118
+ it 'should have the association configured' do
119
+ expect(config.associations).to eq [
120
+ Yaks::Mapper::HasOne.new(:mother, Yaks::Mapper, Undefined, Undefined)
121
+ ]
122
+ end
123
+ end
124
+
125
+ context 'with no options' do
126
+ let(:config) { subject.has_one :mother }
127
+
128
+ it 'should have undefined mapper, rel, collection_mapper' do
129
+ expect(config.associations).to eq [
130
+ Yaks::Mapper::HasOne.new(:mother, Undefined, Undefined, Undefined)
131
+ ]
132
+ end
133
+ end
134
+
135
+ context 'with a rel specified' do
136
+ let(:config) { subject.has_one :mother, rel: '/api/rels/mother' }
137
+
138
+ it 'should have the rel' do
139
+ expect(config.associations).to eq [
140
+ Yaks::Mapper::HasOne.new(:mother, Undefined, '/api/rels/mother', Undefined)
141
+ ]
142
+ end
143
+
144
+ end
145
+ end
146
+
147
+ describe '#has_many' do
148
+ context 'with a mapper specified' do
149
+ let(:config) { subject.has_many :shoes, mapper: Yaks::Mapper }
150
+
151
+ it 'should have the association configured' do
152
+ expect(config.associations).to eq [
153
+ Yaks::Mapper::HasMany.new(:shoes, Yaks::Mapper, Undefined, Undefined)
154
+ ]
155
+ end
156
+ end
157
+
158
+ context 'with no options' do
159
+ let(:config) { subject.has_many :shoes }
160
+
161
+ it 'should have undefined mapper, rel, collection_mapper' do
162
+ expect(config.associations).to eq [
163
+ Yaks::Mapper::HasMany.new(:shoes, Undefined, Undefined, Undefined)
164
+ ]
165
+ end
166
+ end
167
+
168
+ context 'with a collection mapper set' do
169
+ let(:config) { subject.has_many :shoes, collection_mapper: :a_collection_mapper }
170
+
171
+ it 'should have the association configured' do
172
+ expect(config.associations).to eq [
173
+ Yaks::Mapper::HasMany.new(:shoes, Undefined, Undefined, :a_collection_mapper)
174
+ ]
175
+ end
176
+ end
177
+ end
178
+
179
+ context 'multiple associations' do
180
+ let(:config) {
181
+ subject
182
+ .has_many(:shoes)
183
+ .has_one(:mother)
184
+ }
185
+
186
+ it 'should have them all present' do
187
+ expect(config.associations).to include Yaks::Mapper::HasOne.new(:mother, Undefined, Undefined, Undefined)
188
+ expect(config.associations).to include Yaks::Mapper::HasMany.new(:shoes, Undefined, Undefined, Undefined)
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Yaks::Mapper::HasMany do
4
+ let(:closet_mapper) do
5
+ Class.new(Yaks::Mapper) do
6
+ has_many :shoes,
7
+ rel: 'http://foo/shoes',
8
+ mapper: Class.new(Yaks::Mapper) { type 'shoes' ; attributes :size, :color }
9
+ end
10
+ end
11
+
12
+ let(:closet) {
13
+ double(
14
+ :shoes => [
15
+ double(size: 9, color: :blue),
16
+ double(size: 11.5, color: :red),
17
+ ]
18
+ )
19
+ }
20
+
21
+ it 'should map the subresources' do
22
+ expect(closet_mapper.new(closet, policy: Yaks::DefaultPolicy.new, env: {}).map_subresources).to eql(
23
+ "http://foo/shoes" => Yaks::CollectionResource.new(
24
+ type: 'shoes',
25
+ members: [
26
+ Yaks::Resource.new(type: 'shoes', attributes: {:size => 9, :color => :blue}),
27
+ Yaks::Resource.new(type: 'shoes', attributes: {:size => 11.5, :color => :red})
28
+ ]
29
+ )
30
+ )
31
+ end
32
+
33
+ describe '#collection_mapper' do
34
+ let(:collection_mapper) { Yaks::Undefined }
35
+ subject(:has_many) { described_class.new(:name, :mapper, :rel, collection_mapper) }
36
+
37
+ context 'when the collection mapper is undefined' do
38
+ its(:collection_mapper) { should equal Yaks::CollectionMapper }
39
+ end
40
+
41
+ context 'when the collection mapper is specified' do
42
+ let(:collection_mapper) { :foo }
43
+ its(:collection_mapper) { should equal :foo }
44
+ end
45
+ end
46
+ end