lhs 7.1.0 → 7.2.0

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.
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