lhs 7.1.0 → 7.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1020f5e98607e2dc01ebebe52f23961c9e9a4034
4
- data.tar.gz: b15fddb284dcdca4254c155e07d09075a5dacfe4
3
+ metadata.gz: 2de08930a39d1136d185c43d33b8353abe09847d
4
+ data.tar.gz: 0ff2a9b46b4d66aece4ec6c7eeb33b5124511a5f
5
5
  SHA512:
6
- metadata.gz: c0106d5e6468c5a81a60e3fe216406225fa937364884def8a8e1d5c6ce6065259c306909f7e25bea56eee9f8fbcf67df42490974ee672b2596dd23803c2e938f
7
- data.tar.gz: 5a22af86fa15061d27946df81439b75a8dde9773be8d8ff7c7bceda700f50c632a3d1f75678408875ce6b916a7fbecaf40a331322cb68ad3c426a5657cfa9c80
6
+ metadata.gz: 047e5066a227d3631690a4f61d90c21fe9ca8424ee0361713a552700ca54f890fbb7f4de58654de3b12ac8f819e6fe1a673c4588a89d9aee61221fb9c89e414f
7
+ data.tar.gz: 8ca3222a86205b07a1e853073f365729137c941642c4b500f359c4a9b70dd68d3d4e8463deec067fb46c51ce988136cd8675e5f82fb79b9cf2b26d26cfff0ae8
data/README.md CHANGED
@@ -282,6 +282,80 @@ When creation fails, the object contains errors. It provides them through the `e
282
282
  record.errors.message # ratings must be set when review or name or review_title is set | The property value is required; it cannot be null, empty, or blank."
283
283
  ```
284
284
 
285
+ ## Create records through associations (nested resources)
286
+
287
+ ```ruby
288
+ class Review < LHS::Record
289
+ endpoint ':service/reviews'
290
+ end
291
+
292
+ class Comment < LHS::Record
293
+ endpoint ':service/reviews/:review_id/comments'
294
+ end
295
+ ```
296
+
297
+ ### Item
298
+ ```ruby
299
+ review = Review.find(1)
300
+ # Review#1
301
+ # :href => ':service/reviews/1
302
+ # :text => 'Simply awesome'
303
+ # :comment => { :href => ':service/reviews/1/comments }
304
+
305
+ review.comment.create(text: 'Thank you!')
306
+ # Comment#1
307
+ # :href => ':service/reviews/1/comments
308
+ # :text => 'Thank you!'
309
+
310
+ review
311
+ # Review#1
312
+ # :href => ':service/reviews/1
313
+ # :text => 'Simply awesome'
314
+ # :comment => { :href => ':service/reviews/1/comments, :text => 'Thank you!' }
315
+ ```
316
+
317
+ If the item already exists `ArgumentError` is raised.
318
+
319
+ ### Expanded collection
320
+ ```ruby
321
+ review = Review.includes(:comments).find(1)
322
+ # Review#1
323
+ # :href => ':service/reviews/1'
324
+ # :text => 'Simply awesome'
325
+ # :comments => { :href => ':service/reviews/1/comments, :items => [] }
326
+
327
+ review.comments.create(text: 'Thank you!')
328
+ # Comment#1
329
+ # :href => ':service/reviews/1/comments/1'
330
+ # :text => 'Thank you!'
331
+
332
+ review
333
+ # Review#1
334
+ # :href => ':service/reviews/1'
335
+ # :text => 'Simply awesome'
336
+ # :comments => { :href => ':service/reviews/1/comments, :items => [{ :href => ':service/reviews/1/comments/1', :text => 'Thank you!' }] }
337
+ ```
338
+
339
+ ### Not expanded collection
340
+ ```ruby
341
+ review = Review.find(1)
342
+ # Review#1
343
+ # :href => ':service/reviews/1'
344
+ # :text => 'Simply awesome'
345
+ # :comments => { :href => ':service/reviews/1/comments' }
346
+
347
+ review.comments.create(text: 'Thank you!')
348
+ # Comment#1
349
+ # :href => ':service/reviews/1/comments/1'
350
+ # :text => 'Thank you!'
351
+
352
+ review
353
+ # Review#1
354
+ # :href => ':service/reviews/1
355
+ # :text => 'Simply awesome'
356
+ # :comments => { :href => ':service/reviews/1/comments', :items => [{ :href => ':service/reviews/1/comments/1', :text => 'Thank you!' }] }
357
+ ```
358
+
285
359
  ## Build new records
286
360
 
287
361
  Build and persist new items from scratch are done either with `new` or it's alias `build`.
@@ -5,6 +5,7 @@ Dir[File.dirname(__FILE__) + '/concerns/collection/*.rb'].each { |file| require
5
5
  # that contains multiple items
6
6
  class LHS::Collection < LHS::Proxy
7
7
  include InternalCollection
8
+ include Create
8
9
 
9
10
  delegate :select, :length, :size, to: :_collection
10
11
  delegate :_record, :_raw, to: :_data
@@ -28,6 +29,10 @@ class LHS::Collection < LHS::Proxy
28
29
  true
29
30
  end
30
31
 
32
+ def item?
33
+ false
34
+ end
35
+
31
36
  protected
32
37
 
33
38
  def method_missing(name, *args, &block)
@@ -12,7 +12,8 @@ class LHS::Collection < LHS::Proxy
12
12
  include Enumerable
13
13
 
14
14
  attr_accessor :raw
15
- delegate :length, :size, :last, :sample, :[], :present?, :blank?, :empty?, to: :raw
15
+ delegate :length, :size, :last, :sample, :[], :present?, :blank?, :empty?,
16
+ :<<, :push, to: :raw
16
17
 
17
18
  def initialize(raw, parent, record)
18
19
  self.raw = raw
@@ -32,7 +32,7 @@ class LHS::Item < LHS::Proxy
32
32
 
33
33
  def merge_validation_params!(endpoint)
34
34
  validates_params = endpoint.options[:validates].select { |key, _| key.to_sym != :path }
35
- params = endpoint.options.fetch(:params, {}).merge(params_from_embeded_href)
35
+ params = endpoint.options.fetch(:params, {}).merge(params_from_link)
36
36
  params = params.merge(validates_params) if validates_params.is_a?(Hash)
37
37
  params
38
38
  end
@@ -50,20 +50,11 @@ class LHS::Item < LHS::Proxy
50
50
  end
51
51
 
52
52
  def validation_endpoint
53
- endpoint = embeded_endpoint if _data.href # take embeded first
53
+ endpoint = endpoint_from_link if _data.href # take embeded first
54
54
  endpoint ||= _data._record.find_endpoint(_data._raw)
55
55
  validates = endpoint.options && endpoint.options.fetch(:validates, false)
56
56
  raise 'Endpoint does not support validations!' unless validates
57
57
  endpoint
58
58
  end
59
-
60
- def embeded_endpoint
61
- LHS::Endpoint.for_url(_data.href)
62
- end
63
-
64
- def params_from_embeded_href
65
- return {} if !_data.href || !embeded_endpoint
66
- LHC::Endpoint.values_as_params(embeded_endpoint.url, _data.href)
67
- end
68
59
  end
69
60
  end
@@ -0,0 +1,43 @@
1
+ require 'active_support'
2
+
3
+ class LHS::Proxy
4
+
5
+ module Create
6
+ extend ActiveSupport::Concern
7
+
8
+ def create(data = {}, options = nil)
9
+ record_creation!(options) do
10
+ record_from_link.create(
11
+ data,
12
+ options_for_creation(options)
13
+ )
14
+ end
15
+ end
16
+
17
+ def create!(data = {}, options = nil)
18
+ record_creation!(options) do
19
+ record_from_link.create!(
20
+ data,
21
+ options_for_creation(options)
22
+ )
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def record_creation!(options)
29
+ raise(ArgumentError, 'Record already exists') if _raw.keys != [:href] && item?
30
+
31
+ record = yield
32
+ # Needed to handle unexpanded collection which looks the same as item
33
+ reload!(options) if record.errors.empty?
34
+ record
35
+ end
36
+
37
+ def options_for_creation(options)
38
+ return options if params_from_link.blank?
39
+ options = {} if options.blank?
40
+ options.deep_merge(params: params_from_link)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,24 @@
1
+ require 'active_support'
2
+ # require File.dirname(__FILE__) + '/../../proxy'
3
+
4
+ class LHS::Proxy
5
+
6
+ module Link
7
+ extend ActiveSupport::Concern
8
+
9
+ private
10
+
11
+ def record_from_link
12
+ LHS::Record.for_url(_data.href)
13
+ end
14
+
15
+ def endpoint_from_link
16
+ LHS::Endpoint.for_url(_data.href)
17
+ end
18
+
19
+ def params_from_link
20
+ return {} if !_data.href || !endpoint_from_link
21
+ LHC::Endpoint.values_as_params(endpoint_from_link.url, _data.href)
22
+ end
23
+ end
24
+ end
@@ -4,6 +4,7 @@ Dir[File.dirname(__FILE__) + '/concerns/item/*.rb'].each { |file| require file }
4
4
  # An item is a concrete record.
5
5
  # It can be part of another proxy like collection.
6
6
  class LHS::Item < LHS::Proxy
7
+ include Create
7
8
  include Destroy
8
9
  include Save
9
10
  include Update
@@ -21,6 +22,10 @@ class LHS::Item < LHS::Proxy
21
22
  super(data)
22
23
  end
23
24
 
25
+ def collection?
26
+ false
27
+ end
28
+
24
29
  def item?
25
30
  true
26
31
  end
@@ -1,7 +1,12 @@
1
+ Dir[File.dirname(__FILE__) + '/concerns/proxy/*.rb'].each { |file| require file }
2
+
1
3
  # Proxy makes different kind of data accessible
2
4
  # If href is present it also alows loading/reloading
3
5
  class LHS::Proxy
4
6
 
7
+ include Create
8
+ include Link
9
+
5
10
  # prevent clashing with attributes of underlying data
6
11
  attr_accessor :_data, :_loaded
7
12
 
@@ -10,17 +15,18 @@ class LHS::Proxy
10
15
  self._loaded = false
11
16
  end
12
17
 
13
- def load!
18
+ def load!(options = nil)
14
19
  return self if _loaded
15
- reload!
20
+ reload!(options)
16
21
  end
17
22
 
18
- def reload!
23
+ def reload!(options = nil)
19
24
  raise 'No href found' unless _data.href
20
- data = _data.class.request(url: _data.href, method: :get)
25
+ options = {} if options.blank?
26
+
27
+ data = _data.class.request(options.merge(url: _data.href, method: :get))
21
28
  _data.merge_raw!(data)
22
29
  self._loaded = true
23
30
  self
24
31
  end
25
-
26
32
  end
@@ -1,3 +1,3 @@
1
1
  module LHS
2
- VERSION = "7.1.0"
2
+ VERSION = "7.2.0"
3
3
  end
@@ -0,0 +1,180 @@
1
+ require 'rails_helper'
2
+
3
+ describe LHS::Item do
4
+ before(:each) do
5
+ class Feedback < LHS::Record
6
+ endpoint 'http://datastore/v2/feedbacks/:id'
7
+ end
8
+
9
+ class Review < LHS::Record
10
+ endpoint 'http://datastore/v2/feedbacks/:feedback_id/reviews'
11
+ end
12
+ end
13
+
14
+ context 'create sub-resource' do
15
+ let!(:create_review_request) do
16
+ stub_request(:post, "http://datastore/v2/feedbacks/1/reviews")
17
+ .to_return(body: {
18
+ href: 'http://datastore/v2/feedbacks/1/reviews/1',
19
+ title: 'Simply awesome'
20
+ }.to_json)
21
+ end
22
+
23
+ context 'for a nested item' do
24
+ let(:feedback) { Feedback.find(1) }
25
+ let(:review) do
26
+ feedback.review.create(
27
+ title: 'Simply awesome'
28
+ )
29
+ end
30
+
31
+ before do
32
+ stub_request(:get, "http://datastore/v2/feedbacks/1/reviews")
33
+ .to_return(body: {
34
+ href: 'http://datastore/v2/feedbacks/1/reviews',
35
+ title: 'Simply awesome'
36
+ }.to_json)
37
+ end
38
+
39
+ context 'without the object' do
40
+ before(:each) do
41
+ stub_request(:get, "http://datastore/v2/feedbacks/1")
42
+ .to_return(body: {
43
+ review: {
44
+ href: 'http://datastore/v2/feedbacks/1/reviews'
45
+ }
46
+ }.to_json)
47
+ end
48
+
49
+ it 'creates an item' do
50
+ review
51
+ assert_requested(create_review_request)
52
+
53
+ expect(feedback.review.title).to eq 'Simply awesome'
54
+ expect(review.title).to eq 'Simply awesome'
55
+ end
56
+ end
57
+
58
+ context 'with existing item' do
59
+ before(:each) do
60
+ stub_request(:get, "http://datastore/v2/feedbacks/1")
61
+ .to_return(body: {
62
+ review: {
63
+ href: 'http://datastore/v2/feedbacks/1/reviews',
64
+ title: 'Simply awesome'
65
+ }
66
+ }.to_json)
67
+ end
68
+
69
+ it 'raises error' do
70
+ expect { review }.to raise_error(ArgumentError)
71
+
72
+ assert_not_requested(create_review_request)
73
+ end
74
+ end
75
+ end
76
+
77
+ context 'for a nested collection' do
78
+ let(:review) do
79
+ feedback.reviews.create(
80
+ title: 'Simply awesome'
81
+ )
82
+ end
83
+
84
+ before do
85
+ stub_request(:get, "http://datastore/v2/feedbacks/1/reviews")
86
+ .to_return(body: {
87
+ items: [{
88
+ href: 'http://datastore/v2/feedbacks/1/reviews/1',
89
+ title: 'Simply awesome'
90
+ }]
91
+ }.to_json)
92
+ end
93
+
94
+ context 'when expanded' do
95
+ before(:each) do
96
+ stub_request(:get, "http://datastore/v2/feedbacks/1")
97
+ .to_return(body: {
98
+ reviews: {
99
+ href: 'http://datastore/v2/feedbacks/1/reviews',
100
+ items: []
101
+ }
102
+ }.to_json)
103
+ end
104
+
105
+ let(:feedback) { Feedback.includes(:reviews).find(1) }
106
+
107
+ it 'creates an item' do
108
+ review
109
+ assert_requested(create_review_request)
110
+
111
+ expect(feedback.reviews.first.title).to eq 'Simply awesome'
112
+ expect(review.title).to eq 'Simply awesome'
113
+ end
114
+ end
115
+
116
+ context 'when not expanded' do
117
+ before(:each) do
118
+ stub_request(:get, "http://datastore/v2/feedbacks/1")
119
+ .to_return(body: {
120
+ reviews: {
121
+ href: 'http://datastore/v2/feedbacks/1/reviews'
122
+ }
123
+ }.to_json)
124
+ end
125
+
126
+ let(:feedback) { Feedback.find(1) }
127
+
128
+ it 'creates an item' do
129
+ review
130
+ assert_requested(create_review_request)
131
+
132
+ expect(feedback.reviews.first.title).to eq 'Simply awesome'
133
+ expect(review.title).to eq 'Simply awesome'
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+ context 'error messages' do
140
+ let!(:create_review_request) do
141
+ stub_request(:post, "http://datastore/v2/feedbacks/1/reviews")
142
+ .to_return(
143
+ status: 400,
144
+ body: {
145
+ status: 400,
146
+ message: 'Validation failed',
147
+ field_errors: [{
148
+ code: 'UNSATISFIED_PROPERTY_VALUE_MAXIMUM_LENGTH',
149
+ path: ['title'],
150
+ message: 'Title is too long'
151
+ }]
152
+ }.to_json
153
+ )
154
+ end
155
+
156
+ let(:feedback) { Feedback.find(1) }
157
+ let(:review) do
158
+ feedback.review.create(
159
+ title: 'Simply awesome'
160
+ )
161
+ end
162
+
163
+ before do
164
+ stub_request(:get, "http://datastore/v2/feedbacks/1")
165
+ .to_return(body: {
166
+ review: {
167
+ href: 'http://datastore/v2/feedbacks/1/reviews'
168
+ }
169
+ }.to_json)
170
+ end
171
+
172
+ it 'are propagated when creation fails' do
173
+ review
174
+ assert_requested(create_review_request)
175
+
176
+ expect(review.title).to eq 'Simply awesome'
177
+ expect(review.errors.messages[:title]).to include('UNSATISFIED_PROPERTY_VALUE_MAXIMUM_LENGTH')
178
+ end
179
+ end
180
+ end
@@ -54,10 +54,22 @@ describe LHS::Proxy do
54
54
  AnotherRecord.new(href: 'http://datastore/v2/feedbacks')
55
55
  end
56
56
 
57
- it 'applys endpoint options on load!' do
57
+ it 'applies endpoint options on load!' do
58
58
  stub_request(:get, 'http://datastore/v2/feedbacks?color=blue')
59
59
  .to_return(body: {}.to_json)
60
60
  record.load!
61
61
  end
62
62
  end
63
+
64
+ context 'per request options' do
65
+ let(:record) do
66
+ Record.new(href: 'http://datastore/v2/feedbacks')
67
+ end
68
+
69
+ it 'applies options passed to load' do
70
+ stub_request(:get, 'http://datastore/v2/feedbacks?color=blue')
71
+ .to_return(body: {}.to_json)
72
+ record.load!(params: { color: 'blue' })
73
+ end
74
+ end
63
75
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lhs
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.1.0
4
+ version: 7.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - https://github.com/local-ch/lhs/graphs/contributors
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-12-14 00:00:00.000000000 Z
11
+ date: 2016-12-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lhc
@@ -202,6 +202,8 @@ files:
202
202
  - lib/lhs/concerns/item/save.rb
203
203
  - lib/lhs/concerns/item/update.rb
204
204
  - lib/lhs/concerns/item/validation.rb
205
+ - lib/lhs/concerns/proxy/create.rb
206
+ - lib/lhs/concerns/proxy/link.rb
205
207
  - lib/lhs/concerns/record/all.rb
206
208
  - lib/lhs/concerns/record/batch.rb
207
209
  - lib/lhs/concerns/record/chainable.rb
@@ -301,6 +303,7 @@ files:
301
303
  - spec/item/update_spec.rb
302
304
  - spec/item/validation_spec.rb
303
305
  - spec/pagination/pages_left_spec.rb
306
+ - spec/proxy/create_sub_resource_spec.rb
304
307
  - spec/proxy/load_spec.rb
305
308
  - spec/rails_helper.rb
306
309
  - spec/record/all_spec.rb
@@ -373,7 +376,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
373
376
  requirements:
374
377
  - Ruby >= 2.0.0
375
378
  rubyforge_project:
376
- rubygems_version: 2.6.8
379
+ rubygems_version: 2.5.2
377
380
  signing_key:
378
381
  specification_version: 4
379
382
  summary: Rails gem providing an easy, active-record-like interface for http json services
@@ -451,6 +454,7 @@ test_files:
451
454
  - spec/item/update_spec.rb
452
455
  - spec/item/validation_spec.rb
453
456
  - spec/pagination/pages_left_spec.rb
457
+ - spec/proxy/create_sub_resource_spec.rb
454
458
  - spec/proxy/load_spec.rb
455
459
  - spec/rails_helper.rb
456
460
  - spec/record/all_spec.rb