yaks 0.4.2 → 0.4.3

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +65 -5
  4. data/README.md +38 -8
  5. data/Rakefile +33 -0
  6. data/lib/yaks/breaking_changes.rb +22 -0
  7. data/lib/yaks/collection_mapper.rb +18 -21
  8. data/lib/yaks/collection_resource.rb +19 -5
  9. data/lib/yaks/config/dsl.rb +78 -0
  10. data/lib/yaks/config.rb +37 -63
  11. data/lib/yaks/default_policy.rb +27 -9
  12. data/lib/yaks/{serializer → format}/collection_json.rb +7 -3
  13. data/lib/yaks/{serializer → format}/hal.rb +14 -4
  14. data/lib/yaks/{serializer → format}/json_api.rb +22 -4
  15. data/lib/yaks/{serializer.rb → format.rb} +5 -5
  16. data/lib/yaks/fp/hash_updatable.rb +17 -0
  17. data/lib/yaks/fp/updatable.rb +15 -0
  18. data/lib/yaks/mapper/association.rb +24 -21
  19. data/lib/yaks/mapper/association_mapper.rb +42 -0
  20. data/lib/yaks/mapper/attribute.rb +17 -0
  21. data/lib/yaks/mapper/class_methods.rb +0 -1
  22. data/lib/yaks/mapper/config.rb +8 -28
  23. data/lib/yaks/mapper/has_many.rb +8 -3
  24. data/lib/yaks/mapper/has_one.rb +1 -1
  25. data/lib/yaks/mapper/link.rb +13 -13
  26. data/lib/yaks/mapper.rb +28 -32
  27. data/lib/yaks/null_resource.rb +1 -0
  28. data/lib/yaks/resource.rb +15 -5
  29. data/lib/yaks/version.rb +1 -1
  30. data/lib/yaks.rb +16 -10
  31. data/spec/acceptance/acceptance_spec.rb +16 -17
  32. data/spec/acceptance/json_shared_examples.rb +8 -0
  33. data/spec/acceptance/models.rb +2 -2
  34. data/spec/integration/map_to_resource_spec.rb +3 -3
  35. data/spec/json/confucius.collection.json +39 -0
  36. data/spec/json/confucius.hal.json +7 -4
  37. data/spec/json/confucius.json_api.json +1 -1
  38. data/spec/spec_helper.rb +6 -0
  39. data/spec/support/classes_for_policy_testing.rb +36 -0
  40. data/spec/support/shared_contexts.rb +1 -1
  41. data/spec/unit/yaks/collection_mapper_spec.rb +34 -9
  42. data/spec/unit/yaks/collection_resource_spec.rb +4 -4
  43. data/spec/unit/yaks/config/dsl_spec.rb +91 -0
  44. data/spec/unit/yaks/config_spec.rb +10 -6
  45. data/spec/unit/yaks/default_policy/derive_mapper_from_object_spec.rb +80 -0
  46. data/spec/unit/yaks/default_policy_spec.rb +50 -0
  47. data/spec/unit/yaks/{serializer → format}/hal_spec.rb +1 -1
  48. data/spec/unit/yaks/format/json_api_spec.rb +42 -0
  49. data/spec/unit/yaks/format_spec.rb +12 -0
  50. data/spec/unit/yaks/fp/hash_updatable_spec.rb +22 -0
  51. data/spec/unit/yaks/fp/updatable_spec.rb +22 -0
  52. data/spec/unit/yaks/mapper/association_mapper_spec.rb +60 -0
  53. data/spec/unit/yaks/mapper/association_spec.rb +96 -41
  54. data/spec/unit/yaks/mapper/attribute_spec.rb +20 -0
  55. data/spec/unit/yaks/mapper/class_methods_spec.rb +49 -10
  56. data/spec/unit/yaks/mapper/config_spec.rb +25 -50
  57. data/spec/unit/yaks/mapper/has_many_spec.rb +33 -5
  58. data/spec/unit/yaks/mapper/has_one_spec.rb +32 -17
  59. data/spec/unit/yaks/mapper/link_spec.rb +44 -12
  60. data/spec/unit/yaks/mapper_spec.rb +45 -17
  61. data/spec/unit/yaks/resource_spec.rb +41 -7
  62. data/yaks.gemspec +7 -1
  63. metadata +72 -21
  64. data/examples/hal01.rb +0 -126
  65. data/examples/jsonapi01.rb +0 -68
  66. data/examples/jsonapi02.rb +0 -62
  67. data/examples/jsonapi03.rb +0 -86
  68. data/spec/support/serializers.rb +0 -14
  69. data/spec/unit/yaks/serializer_spec.rb +0 -12
@@ -14,45 +14,6 @@ RSpec.describe Yaks::Mapper::Config do
14
14
  its(:associations) { should eql [:c] }
15
15
  end
16
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
17
  describe '#attributes' do
57
18
  context 'an empty config' do
58
19
  it 'should return an empty attributes list' do
@@ -61,8 +22,11 @@ RSpec.describe Yaks::Mapper::Config do
61
22
  end
62
23
 
63
24
  it 'should add attributes' do
64
- expect(config.attributes(:foo, :bar, :baz).attributes)
65
- .to eq [:foo, :bar, :baz]
25
+ expect(config.attributes(:foo, :bar, :baz).attributes).to eq [
26
+ Yaks::Mapper::Attribute.new(:foo),
27
+ Yaks::Mapper::Attribute.new(:bar),
28
+ Yaks::Mapper::Attribute.new(:baz)
29
+ ]
66
30
  end
67
31
 
68
32
  it 'should be chainable' do
@@ -71,7 +35,11 @@ RSpec.describe Yaks::Mapper::Config do
71
35
  .attributes(:foo, :bar)
72
36
  .attributes(:baz)
73
37
  .attributes
74
- ).to eq [:foo, :bar, :baz]
38
+ ).to eq [
39
+ Yaks::Mapper::Attribute.new(:foo),
40
+ Yaks::Mapper::Attribute.new(:bar),
41
+ Yaks::Mapper::Attribute.new(:baz)
42
+ ]
75
43
  end
76
44
  end
77
45
 
@@ -117,7 +85,7 @@ RSpec.describe Yaks::Mapper::Config do
117
85
 
118
86
  it 'should have the association configured' do
119
87
  expect(config.associations).to eq [
120
- Yaks::Mapper::HasOne.new(:mother, Yaks::Mapper, Undefined, Undefined)
88
+ Yaks::Mapper::HasOne.new(name: :mother, mapper: Yaks::Mapper)
121
89
  ]
122
90
  end
123
91
  end
@@ -127,7 +95,7 @@ RSpec.describe Yaks::Mapper::Config do
127
95
 
128
96
  it 'should have undefined mapper, rel, collection_mapper' do
129
97
  expect(config.associations).to eq [
130
- Yaks::Mapper::HasOne.new(:mother, Undefined, Undefined, Undefined)
98
+ Yaks::Mapper::HasOne.new(name: :mother)
131
99
  ]
132
100
  end
133
101
  end
@@ -137,7 +105,7 @@ RSpec.describe Yaks::Mapper::Config do
137
105
 
138
106
  it 'should have the rel' do
139
107
  expect(config.associations).to eq [
140
- Yaks::Mapper::HasOne.new(:mother, Undefined, '/api/rels/mother', Undefined)
108
+ Yaks::Mapper::HasOne.new(name: :mother, rel: '/api/rels/mother')
141
109
  ]
142
110
  end
143
111
 
@@ -150,7 +118,7 @@ RSpec.describe Yaks::Mapper::Config do
150
118
 
151
119
  it 'should have the association configured' do
152
120
  expect(config.associations).to eq [
153
- Yaks::Mapper::HasMany.new(:shoes, Yaks::Mapper, Undefined, Undefined)
121
+ Yaks::Mapper::HasMany.new(name: :shoes, mapper: Yaks::Mapper)
154
122
  ]
155
123
  end
156
124
  end
@@ -160,7 +128,7 @@ RSpec.describe Yaks::Mapper::Config do
160
128
 
161
129
  it 'should have undefined mapper, rel, collection_mapper' do
162
130
  expect(config.associations).to eq [
163
- Yaks::Mapper::HasMany.new(:shoes, Undefined, Undefined, Undefined)
131
+ Yaks::Mapper::HasMany.new(name: :shoes)
164
132
  ]
165
133
  end
166
134
  end
@@ -170,12 +138,19 @@ RSpec.describe Yaks::Mapper::Config do
170
138
 
171
139
  it 'should have the association configured' do
172
140
  expect(config.associations).to eq [
173
- Yaks::Mapper::HasMany.new(:shoes, Undefined, Undefined, :a_collection_mapper)
141
+ Yaks::Mapper::HasMany.new(name: :shoes, collection_mapper: :a_collection_mapper)
174
142
  ]
175
143
  end
176
144
  end
177
145
  end
178
146
 
147
+ describe "#type" do
148
+ it "should update the type" do
149
+ config = config().type :shoe
150
+ expect(config.type).to be :shoe
151
+ end
152
+ end
153
+
179
154
  context 'multiple associations' do
180
155
  let(:config) {
181
156
  subject
@@ -184,8 +159,8 @@ RSpec.describe Yaks::Mapper::Config do
184
159
  }
185
160
 
186
161
  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)
162
+ expect(config.associations).to include Yaks::Mapper::HasOne.new(name: :mother)
163
+ expect(config.associations).to include Yaks::Mapper::HasMany.new(name: :shoes)
189
164
  end
190
165
  end
191
166
  end
@@ -19,10 +19,10 @@ RSpec.describe Yaks::Mapper::HasMany do
19
19
  its(:singular_name) { should eq 'shoe' }
20
20
 
21
21
  let(:closet) {
22
- double(
22
+ fake(
23
23
  :shoes => [
24
- double(size: 9, color: :blue),
25
- double(size: 11.5, color: :red),
24
+ fake(size: 9, color: :blue),
25
+ fake(size: 11.5, color: :red),
26
26
  ]
27
27
  )
28
28
  }
@@ -35,14 +35,42 @@ RSpec.describe Yaks::Mapper::HasMany do
35
35
  Yaks::Resource.new(type: 'shoe', attributes: {:size => 9, :color => :blue}),
36
36
  Yaks::Resource.new(type: 'shoe', attributes: {:size => 11.5, :color => :red})
37
37
  ],
38
- members_rel: 'rel:src=collection&dest=shoes'
38
+ collection_rel: 'rel:shoes'
39
39
  )
40
40
  )
41
41
  end
42
42
 
43
+ it 'should map nil to a NullResource collection' do
44
+ expect(closet_mapper.call(fake(shoes: nil)).subresources).to eql(
45
+ 'http://foo/shoes' => Yaks::NullResource.new(collection: true)
46
+ )
47
+ end
48
+
49
+ context 'without an explicit mapper' do
50
+ let(:dress_mapper) {
51
+ Class.new(Yaks::Mapper) { type 'dress' ; attributes :color }
52
+ }
53
+
54
+ before do
55
+ closet_mapper_class.class_eval do
56
+ has_many :dresses
57
+ end
58
+
59
+ stub(closet_mapper.policy).derive_mapper_from_association(any_args) do
60
+ dress_mapper
61
+ end
62
+ end
63
+
64
+ it 'should derive it from policy' do
65
+ expect(closet_mapper.policy).to equal policy
66
+ closet_mapper.call(fake(shoes: [], dresses: [fake(color: 'blue')]))
67
+ end
68
+ end
69
+
70
+
43
71
  describe '#collection_mapper' do
44
72
  let(:collection_mapper) { Yaks::Undefined }
45
- subject(:has_many) { described_class.new(:name, :mapper, :rel, collection_mapper) }
73
+ subject(:has_many) { described_class.new(name: :name, mapper: :mapper, rel: :rel, collection_mapper: collection_mapper) }
46
74
 
47
75
  context 'when the collection mapper is undefined' do
48
76
  it 'should derive one from collection and policy' do
@@ -1,35 +1,50 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe Yaks::Mapper::HasOne do
4
+ include_context 'yaks context'
5
+
4
6
  AuthorMapper = Class.new(Yaks::Mapper) { attributes :name }
5
7
 
6
- subject(:has_one) { described_class.new(:author, mapper, 'http://rel', Yaks::Undefined) }
7
- let(:name) { 'William S. Burroughs' }
8
- let(:mapper) { AuthorMapper }
9
- let(:author) { double(:name => name) }
10
- let(:policy) {
11
- double(
12
- Yaks::DefaultPolicy,
13
- derive_type_from_mapper_class: 'author',
14
- derive_mapper_from_association: AuthorMapper
8
+ subject(:has_one) do
9
+ described_class.new(
10
+ name: :author,
11
+ mapper: association_mapper,
12
+ rel: 'http://rel'
15
13
  )
16
- }
17
- let(:context) {{policy: policy, env: {}}}
14
+ end
15
+
16
+ let(:association_mapper) { AuthorMapper }
17
+ let(:name) { 'William S. Burroughs' }
18
+ let(:author) { fake(:name => name) }
19
+
20
+ fake(:policy,
21
+ derive_type_from_mapper_class: 'author',
22
+ derive_mapper_from_association: AuthorMapper
23
+ ){ Yaks::DefaultPolicy }
18
24
 
19
25
  its(:singular_name) { should eq 'author' }
20
26
 
21
27
  it 'should map to a single Resource' do
22
- expect(has_one.map_resource(author, context)).to eq Yaks::Resource.new(type: 'author', attributes: {name: name})
28
+ expect(has_one.map_resource(author, yaks_context)).to eq Yaks::Resource.new(type: 'author', attributes: {name: name})
23
29
  end
24
30
 
25
31
  context 'with no mapper specified' do
26
- let(:mapper) { Yaks::Undefined }
32
+ subject(:subresource) { has_one.add_to_resource(Yaks::Resource.new, parent_mapper, yaks_context) }
33
+ let(:association_mapper) { Yaks::Undefined }
34
+ fake(:parent_mapper) { Yaks::Mapper }
35
+
36
+ before do
37
+ stub(parent_mapper).load_association(:author) { author }
38
+ end
27
39
 
28
40
  it 'should derive one based on policy' do
29
- expect(has_one.create_subresource(nil, {author: author}, context)).to eql [
30
- 'http://rel',
31
- Yaks::Resource.new(type: 'author', attributes: {name: name})
32
- ]
41
+ expect(subresource).to eql(
42
+ Yaks::Resource.new(
43
+ subresources: {
44
+ 'http://rel' => Yaks::Resource.new(type: 'author', attributes: {name: name})
45
+ }
46
+ )
47
+ )
33
48
  end
34
49
 
35
50
  end
@@ -11,8 +11,43 @@ RSpec.describe Yaks::Mapper::Link do
11
11
 
12
12
  its(:template_variables) { should eq [:x, :y] }
13
13
  its(:uri_template) { should eq URITemplate.new(template) }
14
- its(:expand?) { should be true }
15
14
 
15
+ let(:object) { Struct.new(:x, :y, :returns_nil).new(3, 4, nil) }
16
+
17
+ let(:mapper_class) do
18
+ Class.new(Yaks::Mapper) do
19
+ type 'foo'
20
+ end
21
+ end
22
+
23
+ let(:mapper) do
24
+ mapper_class.new(yaks_context).tap do |mapper|
25
+ mapper.call(object) # set @object
26
+ end
27
+ end
28
+
29
+ describe '#add_to_resource' do
30
+ it 'should add itself to the resource' do
31
+ expect(link.add_to_resource(Yaks::Resource.new, mapper, yaks_context)).to eql(
32
+ Yaks::Resource.new(links: [Yaks::Resource::Link.new(:next, "/foo/bar/3/4", {})])
33
+ )
34
+ end
35
+
36
+ context 'with a link function returning nothing' do
37
+ let(:template) { :link_computer }
38
+ before do
39
+ mapper_class.class_eval do
40
+ def link_computer ; end
41
+ end
42
+ end
43
+
44
+ it 'should not render the link' do
45
+ expect(link.add_to_resource(Yaks::Resource.new, mapper, yaks_context)).to eql(
46
+ Yaks::Resource.new
47
+ )
48
+ end
49
+ end
50
+ end
16
51
 
17
52
  describe '#rel?' do
18
53
  it 'should return true if the relation matches' do
@@ -47,8 +82,6 @@ RSpec.describe Yaks::Mapper::Link do
47
82
  it 'should keep the template in the response' do
48
83
  expect(link.expand_with(->{ })).to eq '/foo/bar/{x}/{y}'
49
84
  end
50
-
51
- its(:expand?) { should be false }
52
85
  end
53
86
 
54
87
  context 'with a URI without expansion variables' do
@@ -81,14 +114,6 @@ RSpec.describe Yaks::Mapper::Link do
81
114
 
82
115
  its(:rel) { should eq :next }
83
116
 
84
- let(:object) { Struct.new(:x, :y, :returns_nil).new(3, 4, nil) }
85
-
86
- let(:mapper) do
87
- Yaks::Mapper.new(yaks_context).tap do |mapper|
88
- mapper.call(object) # set @object
89
- end
90
- end
91
-
92
117
  context 'with attributes' do
93
118
  it 'should not have a title' do
94
119
  expect(resource_link.options.key?(:title)).to be false
@@ -146,8 +171,15 @@ RSpec.describe Yaks::Mapper::Link do
146
171
  context 'with a title lambda' do
147
172
  let(:options) { { title: -> { "say #{mapper_method}" } } }
148
173
 
174
+ before do
175
+ mapper_class.class_eval do
176
+ def mapper_method
177
+ 'hello'
178
+ end
179
+ end
180
+ end
181
+
149
182
  it 'should evaluate the lambda in the context of the mapper' do
150
- expect(mapper).to receive(:mapper_method).and_return('hello')
151
183
  expect(resource_link.title).to eq 'say hello'
152
184
  end
153
185
  end
@@ -7,7 +7,8 @@ RSpec.describe Yaks::Mapper do
7
7
  let(:resource) { mapper.call(instance) }
8
8
 
9
9
  let(:mapper_class) { Class.new(Yaks::Mapper) { type 'foo' } }
10
- let(:instance) { double(foo: 'hello', bar: 'world') }
10
+
11
+ let(:instance) { fake(foo: 'hello', bar: 'world') }
11
12
 
12
13
  its(:env) { should equal rack_env }
13
14
 
@@ -17,7 +18,10 @@ RSpec.describe Yaks::Mapper do
17
18
  end
18
19
 
19
20
  it 'should make the configured attributes available on the instance' do
20
- expect(mapper.attributes).to eq [:foo, :bar]
21
+ expect(mapper.attributes).to eq [
22
+ Yaks::Mapper::Attribute.new(:foo),
23
+ Yaks::Mapper::Attribute.new(:bar)
24
+ ]
21
25
  end
22
26
 
23
27
  it 'should load them from the model' do
@@ -27,8 +31,8 @@ RSpec.describe Yaks::Mapper do
27
31
  context 'with attribute filtering' do
28
32
  before do
29
33
  mapper_class.class_eval do
30
- def filter(attrs)
31
- attrs.to_a - [:foo]
34
+ def attributes
35
+ super.reject {|attr| attr.name == :foo}
32
36
  end
33
37
  end
34
38
  end
@@ -75,10 +79,10 @@ RSpec.describe Yaks::Mapper do
75
79
  end
76
80
 
77
81
  context 'with subresources' do
78
- let(:instance) { double(widget: widget) }
79
- let(:widget) { double(type: 'super_widget') }
82
+ let(:widget) { fake(type: 'super_widget') }
83
+ let(:instance) { fake(widget: widget) }
80
84
  let(:widget_mapper) { Class.new(Yaks::Mapper) { type 'widget' } }
81
- let(:policy) { double('Policy') }
85
+ fake(:policy) { Yaks::DefaultPolicy }
82
86
 
83
87
  describe 'has_one' do
84
88
  let(:has_one_opts) do
@@ -109,11 +113,13 @@ RSpec.describe Yaks::Mapper do
109
113
  { rel: 'http://foo.bar/rels/widgets' }
110
114
  end
111
115
 
112
- it 'should derive the mapper based on policy' do
113
- expect(policy).to receive(:derive_mapper_from_association) {|assoc|
114
- expect(assoc).to be_a Yaks::Mapper::HasOne
116
+ before do
117
+ stub(policy).derive_mapper_from_association(mapper.associations.first) do
115
118
  widget_mapper
116
- }
119
+ end
120
+ end
121
+
122
+ it 'should derive the mapper based on policy' do
117
123
  expect(resource.subresources).to eq(
118
124
  "http://foo.bar/rels/widgets" => Yaks::Resource.new(type: 'widget', attributes: {:type => "super_widget"})
119
125
  )
@@ -125,12 +131,13 @@ RSpec.describe Yaks::Mapper do
125
131
  { mapper: widget_mapper }
126
132
  end
127
133
 
128
- it 'should derive the rel based on policy' do
129
- expect(policy).to receive(:derive_rel_from_association) {|parent_mapper, assoc|
130
- expect(parent_mapper).to equal mapper
131
- expect(assoc).to be_a Yaks::Mapper::HasOne
134
+ before do
135
+ stub(policy).derive_rel_from_association(mapper.associations.first) do
132
136
  'http://rel/rel'
133
- }
137
+ end
138
+ end
139
+
140
+ it 'should derive the rel based on policy' do
134
141
  expect(resource.subresources).to eq(
135
142
  "http://rel/rel" => Yaks::Resource.new(type: 'widget', attributes: {:type => "super_widget"})
136
143
  )
@@ -140,7 +147,9 @@ RSpec.describe Yaks::Mapper do
140
147
  context 'with the association filtered out' do
141
148
  before do
142
149
  mapper_class.class_eval do
143
- def filter(attrs) [] end
150
+ def associations
151
+ []
152
+ end
144
153
  end
145
154
  end
146
155
 
@@ -172,4 +181,23 @@ RSpec.describe Yaks::Mapper do
172
181
  expect(mapper.call(nil)).to be_a Yaks::NullResource
173
182
  end
174
183
  end
184
+
185
+ context 'with a link generated by a method that returns nil' do
186
+ before do
187
+ mapper_class.class_eval do
188
+ attributes :id
189
+ link :bar_link, :link_generating_method
190
+
191
+ def link_generating_method
192
+ end
193
+ end
194
+ end
195
+
196
+ it 'should not render the link' do
197
+ expect(mapper.call(fake(id: 123))).to eql Yaks::Resource.new(
198
+ type: 'foo',
199
+ attributes: {id: 123}
200
+ )
201
+ end
202
+ end
175
203
  end
@@ -4,13 +4,17 @@ RSpec.describe Yaks::Resource do
4
4
  subject(:resource) { described_class.new(init_opts) }
5
5
  let(:init_opts) { {} }
6
6
 
7
- its(:type) { should be_nil }
8
- its(:attributes) { should eql({}) }
9
- its(:links) { should eql [] }
10
- its(:subresources) { should eql({}) }
11
- its(:self_link) { should be_nil }
12
- its(:null_resource?) { should be false }
13
- its(:collection?) { should be false }
7
+ context 'with a zero-arg constructor' do
8
+ subject(:resource) { described_class.new }
9
+
10
+ its(:type) { should be_nil }
11
+ its(:attributes) { should eql({}) }
12
+ its(:links) { should eql [] }
13
+ its(:subresources) { should eql({}) }
14
+ its(:self_link) { should be_nil }
15
+ its(:null_resource?) { should be false }
16
+ its(:collection?) { should be false }
17
+ end
14
18
 
15
19
  context 'with a type' do
16
20
  let(:init_opts) { { type: 'post' } }
@@ -56,4 +60,34 @@ RSpec.describe Yaks::Resource do
56
60
  it 'should act as a collection of one' do
57
61
  expect(resource.each.to_a).to eql [resource]
58
62
  end
63
+
64
+ describe 'persistent updates' do
65
+ let(:resource) {
66
+ Yaks::Resource.new(
67
+ attributes: {x: :y},
68
+ links: [:one],
69
+ subresources: {foo_rel: :subres}
70
+ )
71
+ }
72
+
73
+ it 'should do updates without modifying the original' do
74
+ expect(
75
+ resource
76
+ .update_attributes(foo: :bar)
77
+ .add_link(:a_link)
78
+ .add_subresource(:rel, :a_subresource)
79
+ .update_attributes(foo: :baz)
80
+ ).to eq Yaks::Resource.new(
81
+ attributes: {x: :y, foo: :baz},
82
+ links: [:one, :a_link],
83
+ subresources: {foo_rel: :subres, rel: :a_subresource}
84
+ )
85
+
86
+ expect(resource).to eq Yaks::Resource.new(
87
+ attributes: {x: :y},
88
+ links: [:one],
89
+ subresources: {foo_rel: :subres}
90
+ )
91
+ end
92
+ end
59
93
  end
data/yaks.gemspec CHANGED
@@ -1,6 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  require File.expand_path('../lib/yaks/version', __FILE__)
4
+ require File.expand_path('../lib/yaks/breaking_changes', __FILE__)
4
5
 
5
6
  Gem::Specification.new do |gem|
6
7
  gem.name = 'yaks'
@@ -17,6 +18,10 @@ Gem::Specification.new do |gem|
17
18
  gem.test_files = `git ls-files -- spec`.split($/)
18
19
  gem.extra_rdoc_files = %w[README.md]
19
20
 
21
+ if Yaks::BreakingChanges.key? Yaks::VERSION
22
+ gem.post_install_message = Yaks::BreakingChanges[Yaks::VERSION]
23
+ end
24
+
20
25
  gem.add_runtime_dependency 'inflection' , '~> 1.0'
21
26
  gem.add_runtime_dependency 'concord' , '~> 0.1.4'
22
27
  gem.add_runtime_dependency 'uri_template' , '~> 0.6.0'
@@ -24,9 +29,10 @@ Gem::Specification.new do |gem|
24
29
 
25
30
  gem.add_development_dependency 'virtus'
26
31
  gem.add_development_dependency 'rspec', '~> 2.99'
32
+ gem.add_development_dependency 'bogus'
27
33
  gem.add_development_dependency 'rake'
28
34
  gem.add_development_dependency 'mutant-rspec'
29
- gem.add_development_dependency 'mutant', '0.5.12'
35
+ gem.add_development_dependency 'mutant'
30
36
  gem.add_development_dependency 'rspec-its'
31
37
  gem.add_development_dependency 'benchmark-ips'
32
38
  end