lhs 0.3.0 → 0.4.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: 143dbe83c79a073cfcb8af80d7a17792fefe5258
4
- data.tar.gz: 96dd3c2a2fecd33be80846cfca58d05386df3b42
3
+ metadata.gz: 0ff25a42028f674f80afd08d6a152b42bf61b08b
4
+ data.tar.gz: c123d197b870914fcb564ba2ce152293105c81c9
5
5
  SHA512:
6
- metadata.gz: 6737f3af323f812f43158b378fede1b1a034aa1c831c7a01ee5bb66a424f03b549f3c5ed7f1547edc93f4c9f38257cf3e9ad30e31dbf15d65720e03939f05cc3
7
- data.tar.gz: 9b77bd37281a5c85be48b9fdea43e80e4273a1959cb8ef7935f118b8c4f98bc4c223559a266a33fef398c3cfa3d1fed4de05f6f41561dfa56fdcad9ee307a229
6
+ metadata.gz: 333cf3687bde474f71b5fcda3ffae1b375ce565fd9c7341888d5fd810d07ae51a137b1b34eb0b31b6d40f5e5d16b94e2330020327df605f3771f3a35224f2bbc
7
+ data.tar.gz: b43d5f519fa79472617485bbfe21bdc757d97b38ba49b17d2e1d485562f5821e4968d66dd371a3ea5d81a67ae6d071d9610d3d0f14053ff37c378459596918bf
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source 'https://rubygems.intra.local.ch/'
1
+ source 'https://rubygems.org/'
2
2
 
3
3
  # Declare your gem's dependencies in lhs.gemspec.
4
4
  # Bundler will treat runtime dependencies like base dependencies, and
@@ -8,4 +8,4 @@ gemspec
8
8
  # Declare any dependencies that are still in development here instead of in
9
9
  # your gemspec. These might include edge Rails or gems from your path or
10
10
  # Git. Remember to move these dependencies to your gemspec before releasing
11
- # your gem to rubygems.org.
11
+ # your gem to rubygems.org.
@@ -5,6 +5,10 @@ A LHS::Service makes data available using multiple endpoints.
5
5
 
6
6
  ![Service](service.jpg)
7
7
 
8
+ ## Convention
9
+
10
+ Please store all defined services in `app/services` as they are autoloaded from this directory.
11
+
8
12
  ## Endpoints
9
13
 
10
14
  You setup a service by configure one or multiple backend endpoints.
@@ -177,6 +181,32 @@ and you should read it to understand this feature in all its glory.
177
181
  feedbacks.first.campaign.entry.name # 'Casa Ferlin'
178
182
  ```
179
183
 
184
+ ### Known services are used to request linked resources
185
+
186
+ When including linked resources with `includes`, known/defined services and endpoints are used to make those requests.
187
+ That also means that options for endpoints of linked resources are applied when requesting those in addition.
188
+ This enables you to include protected resources (e.g. OAuth) as endpoint options for oauth authentication get applied.
189
+
190
+ The [Auth Inteceptor](https://github.com/local-ch/lhc-core-interceptors#auth-interceptor) from [lhc-core-interceptors](https://github.com/local-ch/lhc-core-interceptors) is used to configure the following endpoints.
191
+ ```ruby
192
+ class Favorite < LHS::Service
193
+
194
+ endpoint ':datastore/:user_id/favorites', auth: { bearer: -> { bearer_token } }
195
+ endpoint ':datastore/:user_id/favorites/:id', auth: { bearer: -> { bearer_token } }
196
+
197
+ end
198
+
199
+ class Place < LHS::Service
200
+
201
+ endpoint ':datastore/v2/places', auth: { bearer: -> { bearer_token } }
202
+ endpoint ':datastore/v2/places/:id', auth: { bearer: -> { bearer_token } }
203
+
204
+ end
205
+
206
+ Favorite.includes(:place).where(user_id: current_user.id)
207
+ # Will include places and applies endpoint options to authenticate the request.
208
+ ```
209
+
180
210
  ## Map data
181
211
 
182
212
  To influence how data is accessed/provied, you can use mapping to either map deep nested data or to manipulate data when its accessed:
@@ -20,7 +20,7 @@ Gem::Specification.new do |s|
20
20
  s.requirements << 'Ruby >= 1.9.2'
21
21
  s.required_ruby_version = '>= 1.9.2'
22
22
 
23
- s.add_dependency 'lhc', '>= 0.2.0'
23
+ s.add_dependency 'lhc', '>= 1.2.0'
24
24
  s.add_dependency 'lhc-core-interceptors', '>= 0.1.0'
25
25
 
26
26
  s.add_development_dependency 'rspec-rails', '>= 3.0.0'
data/lib/lhs.rb CHANGED
@@ -4,3 +4,10 @@ module LHS
4
4
  end
5
5
 
6
6
  Gem.find_files('lhs/**/*.rb').each { |path| require path }
7
+
8
+ # Preload all the services that are defined in app/services
9
+ class Engine < Rails::Engine
10
+ initializer 'Load all services' do |app|
11
+ Dir.glob(app.root.join('app/services/**/*.rb')).each {|file| require file }
12
+ end
13
+ end
@@ -10,6 +10,7 @@ class LHS::Service
10
10
  extend ActiveSupport::Concern
11
11
 
12
12
  attr_accessor :endpoints
13
+ mattr_accessor :all
13
14
 
14
15
  module ClassMethods
15
16
 
@@ -18,6 +19,15 @@ class LHS::Service
18
19
  endpoint = LHC::Endpoint.new(url, options)
19
20
  instance.sanity_check(endpoint)
20
21
  instance.endpoints.push(endpoint)
22
+ LHS::Service::Endpoints.all ||= {}
23
+ LHS::Service::Endpoints.all[url] = instance
24
+ end
25
+
26
+ def for_url(url)
27
+ template, service = LHS::Service::Endpoints.all.detect do |template, _service|
28
+ LHC::Endpoint.match?(url, template)
29
+ end
30
+ service
21
31
  end
22
32
  end
23
33
 
@@ -15,38 +15,34 @@ class LHS::Service
15
15
 
16
16
  private
17
17
 
18
- def multiple_requests(options)
19
- options.map { |options| process_options(options) }
20
- responses = LHC.request(options)
21
- data = responses.map{ |response| LHS::Data.new(response.body, nil, self.class, response.request) }
22
- data = LHS::Data.new(data, nil, self.class)
23
- handle_includes(data) if includes
24
- data
18
+ # Convert URLs in options to endpoint templates
19
+ def convert_options_to_endpoints(options)
20
+ if options.respond_to?(:map)
21
+ options.map { |option| convert_option_to_endpoints(option) }
22
+ else
23
+ convert_option_to_endpoints(options)
24
+ end
25
25
  end
26
26
 
27
- def single_request(options)
28
- response = LHC.request(process_options(options))
29
- data = LHS::Data.new(response.body, nil, self.class, response.request)
30
- handle_includes(data) if includes
31
- data
27
+ def convert_option_to_endpoints(option)
28
+ new_options = option.dup
29
+ url = option[:url]
30
+ return unless endpoint = LHS::Endpoint.for_url(url)
31
+ template = endpoint.url
32
+ new_options = new_options.merge(params: LHC::Endpoint.values_as_params(template, url))
33
+ new_options[:url] = template
34
+ new_options
32
35
  end
33
36
 
34
- # Merge explicit params and take configured endpoints options as base
35
- def process_options(options)
36
- endpoint = find_endpoint(options[:params])
37
- options = (endpoint.options || {}).merge(options)
38
- options[:url] = compute_url!(options[:params]) unless options.key?(:url)
39
- merge_explicit_params!(options[:params])
40
- options.delete(:params) if options[:params] && options[:params].empty?
41
- options
42
- end
43
-
44
- # Merge explicit params nested in 'params' namespace with original hash.
45
- def merge_explicit_params!(params)
46
- return true unless params
47
- explicit_params = params[:params]
48
- params.delete(:params)
49
- params.merge!(explicit_params) if explicit_params
37
+ def extend(data, addition, key)
38
+ if data._proxy.is_a? LHS::Collection
39
+ data.each_with_index do |item, i|
40
+ item = item[i] if item.is_a? LHS::Collection
41
+ item._raw[key.to_s].merge!(addition[i]._raw)
42
+ end
43
+ elsif data._proxy.is_a? LHS::Item
44
+ data._raw[key.to_s].merge!(addition._raw)
45
+ end
50
46
  end
51
47
 
52
48
  def handle_includes(data)
@@ -59,36 +55,77 @@ class LHS::Service
59
55
 
60
56
  def handle_include(data, key)
61
57
  options = if data._proxy.is_a? LHS::Collection
62
- include_multiple(data, key)
58
+ options_for_multiple(data, key)
63
59
  else
64
- include_single(data, key)
60
+ url_option_for(data, key)
65
61
  end
62
+ service = service_for_options(options) || self
63
+ options = convert_options_to_endpoints(options) if service_for_options(options)
66
64
  addition = if (further_keys = includes.fetch(key, nil) if includes.is_a? Hash)
67
- self.class.includes(further_keys).instance.request(options)
65
+ service.class.includes(further_keys).instance.request(options)
68
66
  else
69
- self.class.includes(nil).instance.request(options)
67
+ service.class.includes(nil).instance.request(options)
70
68
  end
71
69
  extend(data, addition, key)
72
70
  end
73
71
 
74
- def extend(data, addition, key)
75
- if data._proxy.is_a? LHS::Collection
76
- data.each_with_index do |item, i|
77
- item = item[i] if item.is_a? LHS::Collection
78
- item._raw[key.to_s].merge!(addition[i]._raw)
79
- end
80
- elsif data._proxy.is_a? LHS::Item
81
- data._raw[key.to_s].merge!(addition._raw)
82
- end
72
+ # Merge explicit params nested in 'params' namespace with original hash.
73
+ def merge_explicit_params!(params)
74
+ return true unless params
75
+ explicit_params = params[:params]
76
+ params.delete(:params)
77
+ params.merge!(explicit_params) if explicit_params
78
+ end
79
+
80
+ def multiple_requests(options)
81
+ options = options.map { |options| process_options(options) }
82
+ responses = LHC.request(options)
83
+ data = responses.map{ |response| LHS::Data.new(response.body, nil, self.class, response.request) }
84
+ data = LHS::Data.new(data, nil, self.class)
85
+ handle_includes(data) if includes
86
+ data
83
87
  end
84
88
 
85
- def include_multiple(data, key)
89
+ def options_for_multiple(data, key)
86
90
  data.map do |item|
87
- include_single(item, key)
91
+ url_option_for(item, key)
88
92
  end
89
93
  end
90
94
 
91
- def include_single(item, key)
95
+ # Merge explicit params and take configured endpoints options as base
96
+ def process_options(options)
97
+ options ||= {}
98
+ options = options.dup
99
+ endpoint = find_endpoint(options[:params])
100
+ options = (endpoint.options || {}).merge(options)
101
+ options[:url] = compute_url!(options[:params]) unless options.key?(:url)
102
+ merge_explicit_params!(options[:params])
103
+ options.delete(:params) if options[:params] && options[:params].empty?
104
+ options
105
+ end
106
+
107
+ def service_for_options(options)
108
+ services = []
109
+ if options.is_a?(Array)
110
+ options.each do |option|
111
+ next unless service = LHS::Service.for_url(option[:url])
112
+ services.push(service)
113
+ end
114
+ fail 'Found more than one service that could be used to do the request' if services.uniq.count > 1
115
+ services.uniq.first
116
+ else # Hash
117
+ LHS::Service.for_url(options[:url])
118
+ end
119
+ end
120
+
121
+ def single_request(options)
122
+ response = LHC.request(process_options(options))
123
+ data = LHS::Data.new(response.body, nil, self.class, response.request)
124
+ handle_includes(data) if includes
125
+ data
126
+ end
127
+
128
+ def url_option_for(item, key)
92
129
  link = item[key]
93
130
  { url: link.href }
94
131
  end
@@ -0,0 +1,11 @@
1
+ # An endpoint is used as source to fetch objects
2
+ class LHS::Endpoint
3
+
4
+ def self.for_url(url)
5
+ template, service = LHS::Service::Endpoints.all.detect do |template, _service|
6
+ LHC::Endpoint.match?(url, template)
7
+ end
8
+ service.endpoints.detect { |endpoint| endpoint.url == template } if service
9
+ end
10
+ end
11
+
@@ -1,3 +1,3 @@
1
1
  module LHS
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -0,0 +1,27 @@
1
+ require 'rails_helper'
2
+
3
+ describe LHS::Endpoint do
4
+
5
+ context 'for url' do
6
+
7
+ before(:each) do
8
+ class SomeService < LHS::Service
9
+ endpoint ':datastore/entries/:entry_id/content-ads/:campaign_id/feedbacks'
10
+ endpoint ':datastore/:campaign_id/feedbacks'
11
+ endpoint ':datastore/feedbacks'
12
+ end
13
+ end
14
+
15
+ it 'provides the endpoint for a given url' do
16
+ expect(
17
+ LHS::Endpoint.for_url('http://datastore.local.ch/v2/entries/123/content-ads/456/feedbacks').url
18
+ ).to eq ':datastore/entries/:entry_id/content-ads/:campaign_id/feedbacks'
19
+ expect(
20
+ LHS::Endpoint.for_url('http://datastore.local.ch/123/feedbacks').url
21
+ ).to eq ':datastore/:campaign_id/feedbacks'
22
+ expect(
23
+ LHS::Endpoint.for_url('http://datastore.local.ch/feedbacks').url
24
+ ).to eq ':datastore/feedbacks'
25
+ end
26
+ end
27
+ end
@@ -15,6 +15,12 @@ describe LHS::Service do
15
15
  end
16
16
  end
17
17
 
18
+ it 'stores all the endpoints by url' do
19
+ expect(LHS::Service::Endpoints.all[':datastore/entries/:entry_id/content-ads/:campaign_id/feedbacks']).to be
20
+ expect(LHS::Service::Endpoints.all[':datastore/:campaign_id/feedbacks']).to be
21
+ expect(LHS::Service::Endpoints.all[':datastore/feedbacks']).to be
22
+ end
23
+
18
24
  it 'stores the endpoints of the service' do
19
25
  expect(SomeService.instance.endpoints.count).to eq 3
20
26
  expect(SomeService.instance.endpoints[0].url).to eq ':datastore/entries/:entry_id/content-ads/:campaign_id/feedbacks'
@@ -5,40 +5,40 @@ describe LHS::Service do
5
5
  let(:datastore) { 'http://datastore-stg.lb-service.sunrise.intra.local.ch/v2' }
6
6
  before(:each) { LHC.config.placeholder('datastore', datastore) }
7
7
 
8
+ let(:stub_campaign_request) do
9
+ stub_request(:get, "#{datastore}/content-ads/51dfc5690cf271c375c5a12d")
10
+ .to_return(body: {
11
+ 'href' => "#{datastore}/content-ads/51dfc5690cf271c375c5a12d",
12
+ 'entry' => { 'href' => "#{datastore}/local-entries/lakj35asdflkj1203va" }
13
+ }.to_json)
14
+ end
15
+
16
+ let(:stub_entry_request) do
17
+ stub_request(:get, "#{datastore}/local-entries/lakj35asdflkj1203va")
18
+ .to_return(body: { 'name' => 'Casa Ferlin' }.to_json)
19
+ end
20
+
8
21
  context 'includes' do
9
22
 
10
23
  before(:each) do
11
24
  class Feedback < LHS::Service
12
25
  endpoint ':datastore/feedbacks'
13
26
  end
14
-
15
- stub_request(:get, "#{datastore}/content-ads/51dfc5690cf271c375c5a12d")
16
- .to_return(status: 200, body: {
17
- "href" => "#{datastore}/content-ads/51dfc5690cf271c375c5a12d",
18
- "entry" => {
19
- "href" => "#{datastore}/local-entries/lakj35asdflkj1203va"
20
- }
21
- }.to_json)
22
-
23
- stub_request(:get, "#{datastore}/local-entries/lakj35asdflkj1203va")
24
- .to_return(status: 200, body: {
25
- "name" => 'Casa Ferlin'
26
- }.to_json)
27
+ stub_campaign_request
28
+ stub_entry_request
27
29
  end
28
30
 
29
31
  it 'includes linked resources while fetching multiple resources from one service' do
30
32
 
31
33
  stub_request(:get, "#{datastore}/feedbacks?has_reviews=true")
32
- .to_return(status: 200, body: {
33
- items:[
34
+ .to_return(status: 200, body: {
35
+ items: [
34
36
  {
35
- "href" => "#{datastore}/feedbacks/-Sc4_pYNpqfsudzhtivfkA",
36
- "campaign" => {
37
- "href" => "#{datastore}/content-ads/51dfc5690cf271c375c5a12d"
38
- }
37
+ 'href' => "#{datastore}/feedbacks/-Sc4_pYNpqfsudzhtivfkA",
38
+ 'campaign' => { 'href' => "#{datastore}/content-ads/51dfc5690cf271c375c5a12d" }
39
39
  }
40
40
  ]
41
- }.to_json)
41
+ }.to_json)
42
42
 
43
43
  feedbacks = Feedback.includes(campaign: :entry).where(has_reviews: true)
44
44
  expect(feedbacks.first.campaign.entry.name).to eq 'Casa Ferlin'
@@ -47,15 +47,49 @@ describe LHS::Service do
47
47
  it 'includes linked resources while fetching a single resource from one service' do
48
48
 
49
49
  stub_request(:get, "#{datastore}/feedbacks/123")
50
- .to_return(status: 200, body: {
51
- "href" => "#{datastore}/feedbacks/-Sc4_pYNpqfsudzhtivfkA",
52
- "campaign" => {
53
- "href" => "#{datastore}/content-ads/51dfc5690cf271c375c5a12d"
54
- }
55
- }.to_json)
50
+ .to_return(status: 200, body: {
51
+ 'href' => "#{datastore}/feedbacks/-Sc4_pYNpqfsudzhtivfkA",
52
+ 'campaign' => { 'href' => "#{datastore}/content-ads/51dfc5690cf271c375c5a12d" }
53
+ }.to_json)
56
54
 
57
55
  feedbacks = Feedback.includes(campaign: :entry).find(123)
58
56
  expect(feedbacks.campaign.entry.name).to eq 'Casa Ferlin'
59
57
  end
58
+
59
+ context 'include objects from known services' do
60
+
61
+ let(:stub_feedback_request) do
62
+ stub_request(:get, "#{datastore}/feedbacks")
63
+ .to_return(status: 200, body: {
64
+ items: [
65
+ {
66
+ 'href' => "#{datastore}/feedbacks/-Sc4_pYNpqfsudzhtivfkA",
67
+ 'entry' => {
68
+ 'href' => "#{datastore}/local-entries/lakj35asdflkj1203va"
69
+ }
70
+ }
71
+ ]
72
+ }.to_json)
73
+ end
74
+
75
+ before(:each) do
76
+ class Entry < LHS::Service
77
+ endpoint ':datastore/local-entries/:id'
78
+ end
79
+ class SomeInterceptor < LHC::Interceptor; end
80
+ LHC.config.interceptors = [SomeInterceptor]
81
+ end
82
+
83
+ it 'uses interceptors for included links from known services' do
84
+ stub_feedback_request
85
+ stub_entry_request
86
+
87
+ @called = 0
88
+ allow_any_instance_of(SomeInterceptor).to receive(:before_request) { @called += 1 }
89
+
90
+ expect(Feedback.includes(:entry).where.first.entry.name).to eq 'Casa Ferlin'
91
+ expect(@called).to eq 2
92
+ end
93
+ end
60
94
  end
61
95
  end
@@ -0,0 +1,6 @@
1
+ RSpec.configure do |config|
2
+
3
+ config.before(:each) do
4
+ LHS::Service::Endpoints.all = {}
5
+ end
6
+ 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: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - local.ch
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-09-28 00:00:00.000000000 Z
11
+ date: 2015-10-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lhc
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.2.0
19
+ version: 1.2.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 0.2.0
26
+ version: 1.2.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: lhc-core-interceptors
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -148,6 +148,7 @@ files:
148
148
  - lib/lhs/concerns/service/request.rb
149
149
  - lib/lhs/concerns/service/where.rb
150
150
  - lib/lhs/data.rb
151
+ - lib/lhs/endpoint.rb
151
152
  - lib/lhs/errors.rb
152
153
  - lib/lhs/item.rb
153
154
  - lib/lhs/proxy.rb
@@ -204,6 +205,7 @@ files:
204
205
  - spec/dummy/public/422.html
205
206
  - spec/dummy/public/500.html
206
207
  - spec/dummy/public/favicon.ico
208
+ - spec/endpoint/for_url_spec.rb
207
209
  - spec/item/destroy_spec.rb
208
210
  - spec/item/getter_spec.rb
209
211
  - spec/item/respond_to_spec.rb
@@ -232,6 +234,7 @@ files:
232
234
  - spec/service/where_spec.rb
233
235
  - spec/spec_helper.rb
234
236
  - spec/support/cleanup_configuration.rb
237
+ - spec/support/cleanup_endpoints.rb
235
238
  - spec/support/cleanup_services.rb
236
239
  - spec/support/fixtures/json/feedback.json
237
240
  - spec/support/fixtures/json/feedbacks.json
@@ -311,6 +314,7 @@ test_files:
311
314
  - spec/dummy/public/422.html
312
315
  - spec/dummy/public/500.html
313
316
  - spec/dummy/public/favicon.ico
317
+ - spec/endpoint/for_url_spec.rb
314
318
  - spec/item/destroy_spec.rb
315
319
  - spec/item/getter_spec.rb
316
320
  - spec/item/respond_to_spec.rb
@@ -339,6 +343,7 @@ test_files:
339
343
  - spec/service/where_spec.rb
340
344
  - spec/spec_helper.rb
341
345
  - spec/support/cleanup_configuration.rb
346
+ - spec/support/cleanup_endpoints.rb
342
347
  - spec/support/cleanup_services.rb
343
348
  - spec/support/fixtures/json/feedback.json
344
349
  - spec/support/fixtures/json/feedbacks.json