yaks 0.4.2 → 0.4.3

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