lhs 21.3.0 → 23.0.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
  SHA256:
3
- metadata.gz: cadb661567a1a911f12d612030d14ca963216bc6cca0874525b50ec9afe6d2c1
4
- data.tar.gz: f110c85be3c7ff12f00797f1268f14ba9376c235e1d8dbe9665fb51515f7c0b2
3
+ metadata.gz: 562b089ef00109a1306d02abae66e41f5c8145f8543a9b9f9dffb75fe2133d7b
4
+ data.tar.gz: c04bd6e68e91cc8b31348cc6745970775719ab7f7a00ee7c9bdbbf71f96deb79
5
5
  SHA512:
6
- metadata.gz: ed5dc79199bd8563b951f0131dbb4965e7133898f703104f054cbffe52953ed2c6d9526d046d2077d7b1420ba15e6a3c1ef6ac8168a8087c62d9cc50e227a8cc
7
- data.tar.gz: 2cf77b9a2955eac7cf408f3f35174863b6b773133149b77e0aea47a74808d5d43ab43d99e00cd7bdab4a1c41e8c59c831d31dfae64f8f5d3723f65102288aa06
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: includes_all](#ensure-the-whole-linked-collection-is-included-includes_all)
123
- * [Include the first linked page or single item is included: include](#include-the-first-linked-page-or-single-item-is-included-include)
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` or `includes_all` (to enforce fetching all remote objects for paginated endpoints), LHS ensures that all matching and explicitly linked resources are loaded and merged.
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: includes_all
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 `includes_all`.
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.includes_all(contracts: :products).find(1)
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 or single item is included: include
2169
+ #### Include only the first linked page of a linked collection: includes_first_page
2150
2170
 
2151
- `includes` includes the first page/response when loading the linked resource. **If the endpoint is paginated, only the first page will be included.**
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.includes(contracts: :products).find(1)
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` and `includes_all`, allows you include hyperlinks stored in deep nested data strutures:
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` or `includes_all`, already defined records and their endpoints and configurations are used to make the requests to fetch the additional data.
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.
@@ -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', '>= 10', '< 12'
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 includes(*args)
67
+ def includes_first_page(*args)
68
68
  Chain.new(self, Include.new(Chain.unfold(args)))
69
69
  end
70
70
 
71
- def includes_all(*args)
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 includes(*args)
262
+ def includes_first_page(*args)
263
263
  push(Include.new(Chain.unfold(args)))
264
264
  end
265
265
 
266
- def includes_all(*args)
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) if no_expanded_data?(addition)
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.except(:url))
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 = begin
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 no_expanded_data?(addition)
216
+ def expanded_data?(addition)
220
217
  return false if addition.blank?
221
218
  if addition.item?
222
- (addition._raw.keys - [:href]).empty?
219
+ (addition._raw.keys - [:href]).any?
223
220
  elsif addition.collection?
224
- addition.all? do |item|
221
+ addition.any? do |item|
225
222
  next if item.blank?
226
223
  if item._raw.is_a?(Hash)
227
- (item._raw.keys - [:href]).empty?
224
+ (item._raw.keys - [:href]).any?
228
225
  elsif item._raw.is_a?(Array)
229
- item.any? { |item| (item._raw.keys - [:href]).empty? }
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 unless reference
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, data, sub_includes, references)
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
- begin
355
- prepare_options_for_include_request!(options, sub_includes, references)
356
- if references && references[:all] # include all linked resources
357
- load_include_all!(options, record, sub_includes, references)
358
- else # simply request first page/batch
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
@@ -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 :AttributeAssignment,
49
- 'lhs/concerns/record/attribute_assignment'
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 AttributeAssignment
78
+ include Update
76
79
 
77
80
  delegate :_proxy, :_endpoint, :merge_raw!, :select, :becomes, :respond_to?, to: :_data
78
81
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LHS
4
- VERSION = '21.3.0'
4
+ VERSION = '23.0.0'
5
5
  end
@@ -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
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DummyRecordWithAutoOauthProvider < Providers::InternalServices
4
+ endpoint 'http://internalservice/v2/records'
5
+ endpoint 'http://internalservice/v2/records/{id}'
6
+ end