lhs 21.3.0 → 23.0.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 +4 -4
- data/README.md +72 -11
- data/lhs.gemspec +1 -1
- data/lib/lhs/concerns/record/chainable.rb +4 -4
- data/lib/lhs/concerns/record/request.rb +19 -25
- data/lib/lhs/concerns/record/update.rb +17 -0
- data/lib/lhs/record.rb +6 -3
- data/lib/lhs/version.rb +1 -1
- data/spec/auto_oauth_spec.rb +40 -0
- data/spec/dummy/app/controllers/automatic_authentication_controller.rb +7 -0
- data/spec/dummy/app/models/dummy_record_with_auto_oauth_provider.rb +6 -0
- data/spec/dummy/app/models/providers/internal_services.rb +7 -0
- data/spec/dummy/config/routes.rb +1 -0
- data/spec/item/destroy_spec.rb +1 -1
- data/spec/proxy/record_identification_spec.rb +1 -1
- data/spec/record/all_spec.rb +1 -1
- data/spec/record/endpoints_spec.rb +1 -1
- data/spec/record/handle_includes_errors_spec.rb +1 -1
- data/spec/record/has_many_spec.rb +1 -1
- data/spec/record/has_one_spec.rb +1 -1
- data/spec/record/includes_first_page_spec.rb +737 -0
- data/spec/record/includes_spec.rb +545 -579
- data/spec/record/includes_warning_spec.rb +1 -1
- data/spec/record/mapping_spec.rb +2 -2
- data/spec/record/references_spec.rb +1 -1
- data/spec/record/relation_caching_spec.rb +3 -3
- data/spec/record/update_spec.rb +62 -0
- metadata +13 -6
- data/spec/record/includes_all_spec.rb +0 -693
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 562b089ef00109a1306d02abae66e41f5c8145f8543a9b9f9dffb75fe2133d7b
|
4
|
+
data.tar.gz: c04bd6e68e91cc8b31348cc6745970775719ab7f7a00ee7c9bdbbf71f96deb79
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e3e2fad39c0d7dd4057823a8b1645a42636dfbe020bfbb7405beff04ef578084ba13f44246556820a9ce77b4674ef8ecac2de05159e5820741273b57c1e43b1
|
7
|
+
data.tar.gz: 18e8f76ad0d212f5153c0ff4eb13c730ae9c4e3d089708fced3d8c409361ababe528d601e6761733c4a50925825e10b1fb36352ec527ea255683e55cacfd5fa6
|
data/README.md
CHANGED
@@ -99,6 +99,8 @@ record.review # "Lunch was great
|
|
99
99
|
* [Change/Update existing records](#changeupdate-existing-records)
|
100
100
|
* [save](#save)
|
101
101
|
* [update](#update)
|
102
|
+
* [Directly via Record](#directly-via-record)
|
103
|
+
* [per Instance](#per-instance)
|
102
104
|
* [partial_update](#partial_update)
|
103
105
|
* [Endpoint url parameter injection during record creation/change](#endpoint-url-parameter-injection-during-record-creationchange)
|
104
106
|
* [Record validation](#record-validation)
|
@@ -119,8 +121,8 @@ record.review # "Lunch was great
|
|
119
121
|
* [Record getters](#record-getters)
|
120
122
|
* [Include linked resources (hyperlinks and hypermedia)](#include-linked-resources-hyperlinks-and-hypermedia)
|
121
123
|
* [Generate links from parameters](#generate-links-from-parameters)
|
122
|
-
* [Ensure the whole linked collection is included
|
123
|
-
* [Include the first linked page
|
124
|
+
* [Ensure the whole linked collection is included with includes](#ensure-the-whole-linked-collection-is-included-with-includes)
|
125
|
+
* [Include only the first linked page of a linked collection: includes_first_page](#include-only-the-first-linked-page-of-a-linked-collection-includes_first_page)
|
124
126
|
* [Include various levels of linked data](#include-various-levels-of-linked-data)
|
125
127
|
* [Identify and cast known records when including records](#identify-and-cast-known-records-when-including-records)
|
126
128
|
* [Apply options for requests performed to fetch included records](#apply-options-for-requests-performed-to-fetch-included-records)
|
@@ -136,6 +138,7 @@ record.review # "Lunch was great
|
|
136
138
|
* [Disable request cycle cache](#disable-request-cycle-cache)
|
137
139
|
* [Automatic Authentication (OAuth)](#automatic-authentication-oauth)
|
138
140
|
* [Configure multiple auth providers (even per endpoint)](#configure-multiple-auth-providers-even-per-endpoint)
|
141
|
+
* [Configure providers](#configure-providers)
|
139
142
|
* [Option Blocks](#option-blocks)
|
140
143
|
* [Request tracing](#request-tracing)
|
141
144
|
* [Extended Rollbar Logging](#extended-rollbar-logging)
|
@@ -150,6 +153,8 @@ record.review # "Lunch was great
|
|
150
153
|
|
151
154
|
|
152
155
|
|
156
|
+
|
157
|
+
|
153
158
|
## Installation/Startup checklist
|
154
159
|
|
155
160
|
- [ ] Install LHS gem, preferably via `Gemfile`
|
@@ -1605,6 +1610,21 @@ POST https://service.example.com/records/1z-5r1fkaj { body: "{ 'name': 'Starbuck
|
|
1605
1610
|
|
1606
1611
|
##### update
|
1607
1612
|
|
1613
|
+
###### Directly via Record
|
1614
|
+
|
1615
|
+
```ruby
|
1616
|
+
# app/controllers/some_controller.rb
|
1617
|
+
|
1618
|
+
Record.update(id: '1z-5r1fkaj', name: 'Steve')
|
1619
|
+
|
1620
|
+
```
|
1621
|
+
```
|
1622
|
+
GET https://service.example.com/records/1z-5r1fkaj
|
1623
|
+
{ name: 'Steve' }
|
1624
|
+
```
|
1625
|
+
|
1626
|
+
###### per Instance
|
1627
|
+
|
1608
1628
|
`update` persists the whole object after new parameters are applied through arguments.
|
1609
1629
|
|
1610
1630
|
`update` will return false if persisting fails. `update!` instead will raise an exception.
|
@@ -2091,7 +2111,7 @@ In a service-oriented architecture using [hyperlinks](https://en.wikipedia.org/w
|
|
2091
2111
|
|
2092
2112
|
When fetching records with LHS, you can specify in advance all the linked resources that you want to include in the results.
|
2093
2113
|
|
2094
|
-
With `includes`
|
2114
|
+
With `includes` LHS ensures that all matching and explicitly linked resources are loaded and merged (even if the linked resources are paginated).
|
2095
2115
|
|
2096
2116
|
Including linked resources/records is heavily influenced by [https://guides.rubyonrails.org/active_record_querying.html](https://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations) and you should read it to understand this feature in all it's glory.
|
2097
2117
|
|
@@ -2110,16 +2130,16 @@ Presence.create(place: { href: Place.href_for(123) })
|
|
2110
2130
|
POST '/presences' { place: { href: "http://datastore/places/123" } }
|
2111
2131
|
```
|
2112
2132
|
|
2113
|
-
#### Ensure the whole linked collection is included
|
2133
|
+
#### Ensure the whole linked collection is included with includes
|
2114
2134
|
|
2115
|
-
In case endpoints are paginated and you are certain that you'll need all objects of a set and not only the first page/batch, use `
|
2135
|
+
In case endpoints are paginated and you are certain that you'll need all objects of a set and not only the first page/batch, use `includes`.
|
2116
2136
|
|
2117
2137
|
LHS will ensure that all linked resources are around by loading all pages (parallelized/performance optimized).
|
2118
2138
|
|
2119
2139
|
```ruby
|
2120
2140
|
# app/controllers/some_controller.rb
|
2121
2141
|
|
2122
|
-
customer = Customer.
|
2142
|
+
customer = Customer.includes(contracts: :products).find(1)
|
2123
2143
|
```
|
2124
2144
|
```
|
2125
2145
|
> GET https://service.example.com/customers/1
|
@@ -2146,14 +2166,14 @@ customer.contracts.first.products.first.name # Local Business Card
|
|
2146
2166
|
|
2147
2167
|
```
|
2148
2168
|
|
2149
|
-
#### Include the first linked page
|
2169
|
+
#### Include only the first linked page of a linked collection: includes_first_page
|
2150
2170
|
|
2151
|
-
`
|
2171
|
+
`includes_first_page` includes the first page/response when loading the linked resource. **If the endpoint is paginated, only the first page will be included.**
|
2152
2172
|
|
2153
2173
|
```ruby
|
2154
2174
|
# app/controllers/some_controller.rb
|
2155
2175
|
|
2156
|
-
customer = Customer.
|
2176
|
+
customer = Customer.includes_first_page(contracts: :products).find(1)
|
2157
2177
|
```
|
2158
2178
|
```
|
2159
2179
|
> GET https://service.example.com/customers/1
|
@@ -2177,7 +2197,7 @@ customer.contracts.first.products.first.name # Local Business Card
|
|
2177
2197
|
|
2178
2198
|
#### Include various levels of linked data
|
2179
2199
|
|
2180
|
-
The method syntax of `includes`
|
2200
|
+
The method syntax of `includes` allows you include hyperlinks stored in deep nested data strutures:
|
2181
2201
|
|
2182
2202
|
Some examples:
|
2183
2203
|
|
@@ -2197,7 +2217,7 @@ Record.includes(campaign: [:entry, :user])
|
|
2197
2217
|
|
2198
2218
|
#### Identify and cast known records when including records
|
2199
2219
|
|
2200
|
-
When including linked resources with `includes
|
2220
|
+
When including linked resources with `includes`, already defined records and their endpoints and configurations are used to make the requests to fetch the additional data.
|
2201
2221
|
|
2202
2222
|
That also means that options for endpoints of linked resources are applied when requesting those in addition.
|
2203
2223
|
|
@@ -2259,6 +2279,17 @@ In parallel:
|
|
2259
2279
|
GET https://service.example.com/places/4 { headers: { 'Authentication': 'Bearer 123' } }
|
2260
2280
|
```
|
2261
2281
|
|
2282
|
+
Here is another example, if you want to ignore errors, that occure while you fetch included resources:
|
2283
|
+
|
2284
|
+
```ruby
|
2285
|
+
# app/controllers/some_controller.rb
|
2286
|
+
|
2287
|
+
feedback = Feedback
|
2288
|
+
.includes(campaign: :entry)
|
2289
|
+
.references(campaign: { ignored_errors: [LHC::NotFound] })
|
2290
|
+
.find(12345)
|
2291
|
+
```
|
2292
|
+
|
2262
2293
|
### Record batch processing
|
2263
2294
|
|
2264
2295
|
**Be careful using methods for batch processing. They could result in a lot of HTTP requests!**
|
@@ -2545,6 +2576,36 @@ class Record < LHS::Record
|
|
2545
2576
|
end
|
2546
2577
|
```
|
2547
2578
|
|
2579
|
+
### Configure providers
|
2580
|
+
|
2581
|
+
If you're using LHS service providers, you can also configure auto auth on a provider level:
|
2582
|
+
|
2583
|
+
```ruby
|
2584
|
+
# app/models/providers/localsearch.rb
|
2585
|
+
module Providers
|
2586
|
+
class Localsearch < LHS::Record
|
2587
|
+
|
2588
|
+
provider(
|
2589
|
+
oauth: true
|
2590
|
+
)
|
2591
|
+
end
|
2592
|
+
end
|
2593
|
+
```
|
2594
|
+
|
2595
|
+
or with multiple auth providers:
|
2596
|
+
|
2597
|
+
```ruby
|
2598
|
+
# app/models/providers/localsearch.rb
|
2599
|
+
module Providers
|
2600
|
+
class Localsearch < LHS::Record
|
2601
|
+
|
2602
|
+
provider(
|
2603
|
+
oauth: :provider_1
|
2604
|
+
)
|
2605
|
+
end
|
2606
|
+
end
|
2607
|
+
```
|
2608
|
+
|
2548
2609
|
## Option Blocks
|
2549
2610
|
|
2550
2611
|
In order to apply options to all requests performed in a give block, LHS provides option blocks.
|
data/lhs.gemspec
CHANGED
@@ -24,7 +24,7 @@ Gem::Specification.new do |s|
|
|
24
24
|
|
25
25
|
s.add_dependency 'activemodel'
|
26
26
|
s.add_dependency 'activesupport', '>= 4.2.11'
|
27
|
-
s.add_dependency 'lhc', '>=
|
27
|
+
s.add_dependency 'lhc', '>= 11.2.0', '< 12'
|
28
28
|
s.add_dependency 'local_uri'
|
29
29
|
|
30
30
|
s.add_development_dependency 'capybara'
|
@@ -64,11 +64,11 @@ class LHS::Record
|
|
64
64
|
chain
|
65
65
|
end
|
66
66
|
|
67
|
-
def
|
67
|
+
def includes_first_page(*args)
|
68
68
|
Chain.new(self, Include.new(Chain.unfold(args)))
|
69
69
|
end
|
70
70
|
|
71
|
-
def
|
71
|
+
def includes(*args)
|
72
72
|
chain = Chain.new(self, Include.new(Chain.unfold(args)))
|
73
73
|
chain.include_all!(args)
|
74
74
|
chain
|
@@ -259,11 +259,11 @@ class LHS::Record
|
|
259
259
|
push(ErrorHandling.new(error_class => handler))
|
260
260
|
end
|
261
261
|
|
262
|
-
def
|
262
|
+
def includes_first_page(*args)
|
263
263
|
push(Include.new(Chain.unfold(args)))
|
264
264
|
end
|
265
265
|
|
266
|
-
def
|
266
|
+
def includes(*args)
|
267
267
|
chain = push(Include.new(Chain.unfold(args)))
|
268
268
|
chain.include_all!(args)
|
269
269
|
chain
|
@@ -138,6 +138,7 @@ class LHS::Record
|
|
138
138
|
end
|
139
139
|
|
140
140
|
def extend_base_item!(data, addition, key)
|
141
|
+
return if addition.nil?
|
141
142
|
if addition.collection?
|
142
143
|
extend_base_item_with_collection!(data, addition, key)
|
143
144
|
else # simple case merges hash into hash
|
@@ -187,7 +188,7 @@ class LHS::Record
|
|
187
188
|
options = extend_with_reference(options, reference)
|
188
189
|
addition = load_include(options, data, sub_includes, reference)
|
189
190
|
extend_raw_data!(data, addition, included)
|
190
|
-
expand_addition!(data, included, options)
|
191
|
+
expand_addition!(data, included, options) unless expanded_data?(addition)
|
191
192
|
end
|
192
193
|
end
|
193
194
|
|
@@ -205,28 +206,24 @@ class LHS::Record
|
|
205
206
|
def expand_addition!(data, included, reference)
|
206
207
|
addition = data[included]
|
207
208
|
options = options_for_data(addition)
|
208
|
-
options = extend_with_reference(options, reference
|
209
|
+
options = extend_with_reference(options, reference)
|
209
210
|
record = record_for_options(options) || self
|
210
211
|
options = convert_options_to_endpoints(options) if record_for_options(options)
|
211
|
-
expanded_data =
|
212
|
-
record.request(options)
|
213
|
-
rescue LHC::NotFound
|
214
|
-
LHS::Data.new({}, data, record)
|
215
|
-
end
|
212
|
+
expanded_data = record.request(options)
|
216
213
|
extend_raw_data!(data, expanded_data, included)
|
217
214
|
end
|
218
215
|
|
219
|
-
def
|
216
|
+
def expanded_data?(addition)
|
220
217
|
return false if addition.blank?
|
221
218
|
if addition.item?
|
222
|
-
(addition._raw.keys - [:href]).
|
219
|
+
(addition._raw.keys - [:href]).any?
|
223
220
|
elsif addition.collection?
|
224
|
-
addition.
|
221
|
+
addition.any? do |item|
|
225
222
|
next if item.blank?
|
226
223
|
if item._raw.is_a?(Hash)
|
227
|
-
(item._raw.keys - [:href]).
|
224
|
+
(item._raw.keys - [:href]).any?
|
228
225
|
elsif item._raw.is_a?(Array)
|
229
|
-
item.any? { |item| (item._raw.keys - [:href]).
|
226
|
+
item.any? { |item| (item._raw.keys - [:href]).any? }
|
230
227
|
end
|
231
228
|
end
|
232
229
|
end
|
@@ -234,7 +231,8 @@ class LHS::Record
|
|
234
231
|
|
235
232
|
# Extends request options with options provided for this reference
|
236
233
|
def extend_with_reference(options, reference)
|
237
|
-
return options
|
234
|
+
return options if reference.blank?
|
235
|
+
reference = reference.except(:url)
|
238
236
|
options ||= {}
|
239
237
|
if options.is_a?(Array)
|
240
238
|
options.map { |request_options| request_options.merge(reference) if request_options.present? }
|
@@ -348,18 +346,14 @@ class LHS::Record
|
|
348
346
|
end
|
349
347
|
|
350
348
|
# Load additional resources that are requested with include
|
351
|
-
def load_include(options,
|
349
|
+
def load_include(options, _data, sub_includes, references)
|
352
350
|
record = record_for_options(options) || self
|
353
351
|
options = convert_options_to_endpoints(options) if record_for_options(options)
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
load_include_simple!(options, record)
|
360
|
-
end
|
361
|
-
rescue LHC::NotFound
|
362
|
-
LHS::Data.new({}, data, record)
|
352
|
+
prepare_options_for_include_request!(options, sub_includes, references)
|
353
|
+
if references && references[:all] # include all linked resources
|
354
|
+
load_include_all!(options, record, sub_includes, references)
|
355
|
+
else # simply request first page/batch
|
356
|
+
load_include_simple!(options, record)
|
363
357
|
end
|
364
358
|
end
|
365
359
|
|
@@ -372,7 +366,7 @@ class LHS::Record
|
|
372
366
|
|
373
367
|
def load_include_simple!(options, record)
|
374
368
|
data = record.request(options)
|
375
|
-
warn "[WARNING] You included `#{options[:url]}`, but this endpoint is paginated. You might want to use `includes_all` instead of `includes` (https://github.com/local-ch/lhs#includes_all-for-paginated-endpoints)." if paginated?(data._raw)
|
369
|
+
warn "[WARNING] You included `#{options[:url]}`, but this endpoint is paginated. You might want to use `includes_all` instead of `includes` (https://github.com/local-ch/lhs#includes_all-for-paginated-endpoints)." if data && paginated?(data._raw)
|
376
370
|
data
|
377
371
|
end
|
378
372
|
|
@@ -532,7 +526,7 @@ class LHS::Record
|
|
532
526
|
end
|
533
527
|
|
534
528
|
endpoint = find_endpoint(options[:params], options.fetch(:url, nil))
|
535
|
-
if auto_oauth? || (endpoint.options&.dig(:oauth) && LHS.config.auto_oauth)
|
529
|
+
if auto_oauth? || (endpoint.options&.dig(:oauth) && LHS.config.auto_oauth) || options[:oauth]
|
536
530
|
inject_interceptor!(
|
537
531
|
options.merge!(record: self),
|
538
532
|
LHS::Interceptors::AutoOauth::Interceptor,
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
|
5
|
+
class LHS::Record
|
6
|
+
|
7
|
+
module Update
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
class <<self
|
12
|
+
alias_method :update, :create
|
13
|
+
alias_method :update!, :create!
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/lhs/record.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class LHS::Record
|
4
|
+
autoload :AttributeAssignment,
|
5
|
+
'lhs/concerns/record/attribute_assignment'
|
4
6
|
autoload :Batch,
|
5
7
|
'lhs/concerns/record/batch'
|
6
8
|
autoload :Chainable,
|
@@ -45,9 +47,10 @@ class LHS::Record
|
|
45
47
|
'lhs/concerns/record/scope'
|
46
48
|
autoload :Tracing,
|
47
49
|
'lhs/concerns/record/tracing'
|
48
|
-
autoload :
|
49
|
-
'lhs/concerns/record/
|
50
|
+
autoload :Update,
|
51
|
+
'lhs/concerns/record/update'
|
50
52
|
|
53
|
+
include AttributeAssignment
|
51
54
|
include Batch
|
52
55
|
include Chainable
|
53
56
|
include Configuration
|
@@ -72,7 +75,7 @@ class LHS::Record
|
|
72
75
|
include Relations
|
73
76
|
include Scope
|
74
77
|
include Tracing
|
75
|
-
include
|
78
|
+
include Update
|
76
79
|
|
77
80
|
delegate :_proxy, :_endpoint, :merge_raw!, :select, :becomes, :respond_to?, to: :_data
|
78
81
|
|
data/lib/lhs/version.rb
CHANGED
data/spec/auto_oauth_spec.rb
CHANGED
@@ -125,5 +125,45 @@ describe 'Auto OAuth Authentication', type: :request, dummy_models: true do
|
|
125
125
|
expect(record_request_per_endpoint_provider_2).to have_been_requested
|
126
126
|
end
|
127
127
|
end
|
128
|
+
|
129
|
+
context 'with provider enabled for auto oauth' do
|
130
|
+
|
131
|
+
let(:token) { ApplicationController::ACCESS_TOKEN }
|
132
|
+
|
133
|
+
let(:record_request) do
|
134
|
+
stub_request(:get, "http://internalservice/v2/records/1")
|
135
|
+
.with(
|
136
|
+
headers: { 'Authorization' => "Bearer #{token}" }
|
137
|
+
).to_return(status: 200, body: { name: 'Record' }.to_json)
|
138
|
+
end
|
139
|
+
|
140
|
+
let(:records_request) do
|
141
|
+
stub_request(:get, "http://internalservice/v2/records?color=blue")
|
142
|
+
.with(
|
143
|
+
headers: { 'Authorization' => "Bearer #{token}" }
|
144
|
+
).to_return(status: 200, body: { items: [{ name: 'Record' }] }.to_json)
|
145
|
+
end
|
146
|
+
|
147
|
+
before do
|
148
|
+
LHS.configure do |config|
|
149
|
+
config.auto_oauth = -> { access_token }
|
150
|
+
end
|
151
|
+
LHC.configure do |config|
|
152
|
+
config.interceptors = [LHC::Auth]
|
153
|
+
end
|
154
|
+
record_request
|
155
|
+
records_request
|
156
|
+
end
|
157
|
+
|
158
|
+
after do
|
159
|
+
LHC.config.reset
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'applies OAuth credentials for the individual request automatically' do
|
163
|
+
get '/automatic_authentication/oauth_with_provider'
|
164
|
+
expect(record_request).to have_been_requested
|
165
|
+
expect(records_request).to have_been_requested
|
166
|
+
end
|
167
|
+
end
|
128
168
|
end
|
129
169
|
end
|
@@ -19,4 +19,11 @@ class AutomaticAuthenticationController < ApplicationController
|
|
19
19
|
}
|
20
20
|
}
|
21
21
|
end
|
22
|
+
|
23
|
+
def o_auth_with_provider
|
24
|
+
render json: {
|
25
|
+
record: DummyRecordWithAutoOauthProvider.find(1).as_json,
|
26
|
+
records: DummyRecordWithAutoOauthProvider.where(color: 'blue').as_json
|
27
|
+
}
|
28
|
+
end
|
22
29
|
end
|