yaks 0.3.1 → 0.4.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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