lhs 2.2.2 → 3.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 +296 -30
- data/lhs.gemspec +3 -3
- data/lib/lhs.rb +0 -7
- data/lib/lhs/collection.rb +4 -4
- data/lib/lhs/concerns/item/destroy.rb +2 -2
- data/lib/lhs/concerns/item/save.rb +3 -3
- data/lib/lhs/concerns/item/update.rb +2 -2
- data/lib/lhs/concerns/item/validation.rb +4 -5
- data/lib/lhs/concerns/{service → record}/all.rb +2 -2
- data/lib/lhs/concerns/{service → record}/batch.rb +3 -3
- data/lib/lhs/concerns/{service → record}/create.rb +4 -3
- data/lib/lhs/concerns/{service → record}/endpoints.rb +6 -6
- data/lib/lhs/concerns/{service → record}/find.rb +3 -2
- data/lib/lhs/concerns/{service → record}/find_by.rb +3 -2
- data/lib/lhs/concerns/{service → record}/first.rb +1 -1
- data/lib/lhs/concerns/{service → record}/includes.rb +4 -2
- data/lib/lhs/concerns/{service → record}/mapping.rb +1 -1
- data/lib/lhs/concerns/{service → record}/model.rb +1 -1
- data/lib/lhs/concerns/{service → record}/request.rb +12 -12
- data/lib/lhs/concerns/{service → record}/where.rb +3 -2
- data/lib/lhs/data.rb +25 -53
- data/lib/lhs/endpoint.rb +2 -2
- data/lib/lhs/item.rb +8 -2
- data/lib/lhs/proxy.rb +2 -2
- data/lib/lhs/record.rb +42 -0
- data/lib/lhs/version.rb +1 -1
- data/spec/collection/meta_data_spec.rb +2 -2
- data/spec/collection/without_object_items_spec.rb +1 -1
- data/spec/data/item_spec.rb +2 -14
- data/spec/data/merge_spec.rb +3 -3
- data/spec/data/raw_spec.rb +1 -1
- data/spec/data/respond_to_spec.rb +3 -3
- data/spec/data/root_spec.rb +2 -2
- data/spec/data/to_json_spec.rb +2 -2
- data/spec/endpoint/for_url_spec.rb +1 -1
- data/spec/item/delegate_spec.rb +2 -2
- data/spec/item/destroy_spec.rb +4 -4
- data/spec/item/errors_spec.rb +4 -4
- data/spec/item/getter_spec.rb +2 -2
- data/spec/item/internal_data_structure_spec.rb +1 -1
- data/spec/item/save_spec.rb +3 -4
- data/spec/item/setter_spec.rb +2 -2
- data/spec/item/update_spec.rb +2 -2
- data/spec/item/validation_spec.rb +47 -75
- data/spec/proxy/load_spec.rb +2 -2
- data/spec/{service → record}/all_spec.rb +7 -7
- data/spec/{service → record}/build_spec.rb +5 -4
- data/spec/{service → record}/create_spec.rb +6 -5
- data/spec/{service → record}/creation_failed_spec.rb +6 -5
- data/spec/record/definitions_spec.rb +29 -0
- data/spec/{service → record}/endpoint_misconfiguration_spec.rb +3 -3
- data/spec/{service → record}/endpoint_options_spec.rb +4 -4
- data/spec/{service → record}/endpoints_spec.rb +15 -15
- data/spec/{service → record}/find_by_spec.rb +8 -8
- data/spec/{service → record}/find_each_spec.rb +3 -3
- data/spec/{service → record}/find_in_batches_spec.rb +6 -6
- data/spec/{service → record}/find_spec.rb +10 -9
- data/spec/{service → record}/first_spec.rb +7 -6
- data/spec/{service → record}/includes_spec.rb +7 -7
- data/spec/{service → record}/mapping_spec.rb +27 -16
- data/spec/{service → record}/model_name_spec.rb +2 -2
- data/spec/{service → record}/new_spec.rb +4 -3
- data/spec/{service → record}/request_spec.rb +3 -3
- data/spec/record/where_spec.rb +34 -0
- data/spec/support/cleanup_endpoints.rb +1 -1
- data/spec/support/{cleanup_services.rb → cleanup_records.rb} +2 -2
- metadata +60 -67
- data/docs/collections.md +0 -28
- data/docs/data.md +0 -39
- data/docs/items.md +0 -79
- data/docs/service.jpg +0 -0
- data/docs/service.pdf +2 -650
- data/docs/services.md +0 -266
- data/lib/lhs/concerns/service/build.rb +0 -19
- data/lib/lhs/service.rb +0 -18
- data/spec/data/pagination_spec.rb +0 -60
- data/spec/dummy/README.rdoc +0 -28
- data/spec/service/where_spec.rb +0 -33
data/docs/services.md
DELETED
@@ -1,266 +0,0 @@
|
|
1
|
-
Services
|
2
|
-
===
|
3
|
-
|
4
|
-
A LHS::Service makes data available using multiple endpoints.
|
5
|
-
|
6
|
-

|
7
|
-
|
8
|
-
## Convention
|
9
|
-
|
10
|
-
Please store all defined services in `app/services` as they are autoloaded from this directory.
|
11
|
-
|
12
|
-
## Endpoints
|
13
|
-
|
14
|
-
You setup a service by configure one or multiple backend endpoints.
|
15
|
-
You can also add request options for an endpoint (see following example).
|
16
|
-
|
17
|
-
```ruby
|
18
|
-
class Feedback < LHS::Service
|
19
|
-
|
20
|
-
endpoint ':datastore/v2/content-ads/:campaign_id/feedbacks'
|
21
|
-
endpoint ':datastore/v2/content-ads/:campaign_id/feedbacks/:id'
|
22
|
-
endpoint ':datastore/v2/feedbacks', cache: true, cache_expires_in: 1.day
|
23
|
-
endpoint ':datastore/v2/feedbacks/:id', cache: true, cache_expires_in: 1.day
|
24
|
-
|
25
|
-
end
|
26
|
-
```
|
27
|
-
|
28
|
-
If you try to setup a service with clashing endpoints it will immediately raise an exception.
|
29
|
-
|
30
|
-
```ruby
|
31
|
-
class Feedback < LHS::Service
|
32
|
-
|
33
|
-
endpoint ':datastore/v2/reviews'
|
34
|
-
endpoint ':datastore/v2/feedbacks'
|
35
|
-
|
36
|
-
end
|
37
|
-
# raises: Clashing endpoints.
|
38
|
-
|
39
|
-
```
|
40
|
-
|
41
|
-
## Find multiple records
|
42
|
-
|
43
|
-
You can query the services by using `where`.
|
44
|
-
|
45
|
-
```ruby
|
46
|
-
Feedback.where(has_reviews: true) #<LHS::Data @_proxy=#<LHS::Collection>>
|
47
|
-
```
|
48
|
-
|
49
|
-
This uses the `:datastore/v2/feedbacks` endpoint, cause `:campaign_id` was not provided.
|
50
|
-
In addition it would add `?has_reviews=true` to the get parameters.
|
51
|
-
|
52
|
-
```ruby
|
53
|
-
Feedback.where(campaign_id: 'fq-a81ngsl1d') #<LHS::Data @_proxy=#<LHS::Collection>>
|
54
|
-
```
|
55
|
-
Uses the `:datastore/v2/content-ads/:campaign_id/feedbacks` endpoint.
|
56
|
-
|
57
|
-
→ [Read more about collections](collections.md)
|
58
|
-
|
59
|
-
## Find single records
|
60
|
-
|
61
|
-
`find` finds a unique item by uniqe identifier (usualy id).
|
62
|
-
|
63
|
-
If no record is found an error is raised.
|
64
|
-
|
65
|
-
```ruby
|
66
|
-
Feedback.find('z12f-3asm3ngals') #<LHS::Data @_proxy=#<LHS::Item>>
|
67
|
-
```
|
68
|
-
|
69
|
-
`find` can also be used to find a single uniqe item with parameters:
|
70
|
-
|
71
|
-
```ruby
|
72
|
-
Feedback.find(campaign_id: 123, id: 456)
|
73
|
-
```
|
74
|
-
|
75
|
-
`find_by` finds the first record matching the specified conditions.
|
76
|
-
|
77
|
-
If no record is found, `nil` is returned.
|
78
|
-
|
79
|
-
`find_by!` raises LHC::NotFound if nothing was found.
|
80
|
-
|
81
|
-
```ruby
|
82
|
-
Feedback.find_by(id: 'z12f-3asm3ngals') #<LHS::Data @_proxy=#<LHS::Item>>
|
83
|
-
Feedback.find_by(id: 'doesntexist') # nil
|
84
|
-
```
|
85
|
-
|
86
|
-
`first` is a alias for finding the first of a service without parameters.
|
87
|
-
|
88
|
-
```ruby
|
89
|
-
Feedback.first
|
90
|
-
```
|
91
|
-
|
92
|
-
If no record is found, `nil` is returned.
|
93
|
-
|
94
|
-
`first!` raises LHC::NotFound if nothing was found.
|
95
|
-
|
96
|
-
→ [Read more about items](items.md)
|
97
|
-
|
98
|
-
## Batch processing
|
99
|
-
|
100
|
-
** Be carefull using methods for batch processing. They could result in a lot of HTTP requests! **
|
101
|
-
|
102
|
-
`all` fetches all records from the backend by doing multiple requests if necessary.
|
103
|
-
|
104
|
-
```ruby
|
105
|
-
data = Feedback.all #<LHS::Data @_proxy=#<LHS::Collection>>
|
106
|
-
data.count # 998
|
107
|
-
data.total # 998
|
108
|
-
```
|
109
|
-
|
110
|
-
→ [Read more about collections](collections.md)
|
111
|
-
|
112
|
-
`find_each` is a more fine grained way to process single records that are fetched in batches.
|
113
|
-
|
114
|
-
```ruby
|
115
|
-
Feedback.find_each(start: 50, batch_size: 20, params: { has_reviews: true }) do |feedback|
|
116
|
-
# Iterates over each record. Starts with record nr. 50 and fetches 20 records each batch.
|
117
|
-
feedback #<LHS::Data @_proxy=#<LHS::Item>>
|
118
|
-
end
|
119
|
-
```
|
120
|
-
|
121
|
-
`find_in_batches` is used by `find_each` and processes batches.
|
122
|
-
```ruby
|
123
|
-
Feedback.find_in_batches(start: 50, batch_size: 20, params: { has_reviews: true }) do |feedbacks|
|
124
|
-
# Iterates over multiple records (batch size is 20). Starts with record nr. 50 and fetches 20 records each batch.
|
125
|
-
feedbacks #<LHS::Data @_proxy=#<LHS::Collection>>
|
126
|
-
end
|
127
|
-
```
|
128
|
-
|
129
|
-
## Create records
|
130
|
-
|
131
|
-
```ruby
|
132
|
-
feedback = Feedback.create(
|
133
|
-
recommended: true,
|
134
|
-
source_id: 'aaa',
|
135
|
-
content_ad_id: '1z-5r1fkaj'
|
136
|
-
) #<LHS::Data @_proxy=#<LHS::Item>>
|
137
|
-
```
|
138
|
-
|
139
|
-
When creation fails, the object contains errors in its `errors` attribute:
|
140
|
-
|
141
|
-
```ruby
|
142
|
-
feedback.errors #<LHS::Errors>
|
143
|
-
feedback.errors.include?(:ratings) # true
|
144
|
-
feedback.errors[:ratings] # ['REQUIRED_PROPERTY_VALUE']
|
145
|
-
record.errors.messages # {:ratings=>["REQUIRED_PROPERTY_VALUE"], :recommended=>["REQUIRED_PROPERTY_VALUE"]}
|
146
|
-
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."
|
147
|
-
```
|
148
|
-
|
149
|
-
## Build new records
|
150
|
-
|
151
|
-
Build and persist new items from scratch.
|
152
|
-
|
153
|
-
```ruby
|
154
|
-
feedback = Feedback.build(recommended: true)
|
155
|
-
feedback.save
|
156
|
-
```
|
157
|
-
|
158
|
-
`new` is an alias for `build`:
|
159
|
-
|
160
|
-
```ruby
|
161
|
-
Feedback.new(recommended: true)
|
162
|
-
```
|
163
|
-
|
164
|
-
→ [Read more about items](items.md)
|
165
|
-
|
166
|
-
|
167
|
-
## Include linked resources
|
168
|
-
|
169
|
-
A service lets you specify in advance all the linked resources that you want to include in the results. With includes, a service ensures that all matching and explicitly linked resources are loaded and merged.
|
170
|
-
|
171
|
-
The implementation is heavily influenced by [http://guides.rubyonrails.org/active_record_querying](http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations)
|
172
|
-
and you should read it to understand this feature in all its glory.
|
173
|
-
|
174
|
-
### One-Level `includes`
|
175
|
-
|
176
|
-
```ruby
|
177
|
-
# a claim has a localch_account
|
178
|
-
claims = Claims.includes(:localch_account).where(place_id: 'huU90mB_6vAfUdVz_uDoyA')
|
179
|
-
claims.first.localch_account.email # 'test@email.com'
|
180
|
-
```
|
181
|
-
* [see the JSON without include](examples/claim_no_include.json)
|
182
|
-
* [see the JSON with include](examples/claim_with_include.json)
|
183
|
-
|
184
|
-
### Two-Level `includes`
|
185
|
-
|
186
|
-
```ruby
|
187
|
-
# a feedback has a campaign, which has an entry
|
188
|
-
feedbacks = Feedback.includes(campaign: :entry).where(has_reviews: true)
|
189
|
-
feedbacks.first.campaign.entry.name # 'Casa Ferlin'
|
190
|
-
```
|
191
|
-
|
192
|
-
### Multiple `includes`
|
193
|
-
|
194
|
-
```ruby
|
195
|
-
# list of includes
|
196
|
-
claims = Claims.includes(:localch_account, :entry).where(place_id: 'huU90mB_6vAfUdVz_uDoyA')
|
197
|
-
|
198
|
-
# array of includes
|
199
|
-
claims = Claims.includes([:localch_account, :entry]).where(place_id: 'huU90mB_6vAfUdVz_uDoyA')
|
200
|
-
|
201
|
-
# Two-level with array of includes
|
202
|
-
feedbacks = Feedback.includes(campaign: [:entry, :user]).where(has_reviews: true)
|
203
|
-
```
|
204
|
-
|
205
|
-
### Known services are used to request linked resources
|
206
|
-
|
207
|
-
When including linked resources with `includes`, known/defined services and endpoints are used to make those requests.
|
208
|
-
That also means that options for endpoints of linked resources are applied when requesting those in addition.
|
209
|
-
This enables you to include protected resources (e.g. OAuth) as endpoint options for oauth authentication get applied.
|
210
|
-
|
211
|
-
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.
|
212
|
-
```ruby
|
213
|
-
class Favorite < LHS::Service
|
214
|
-
|
215
|
-
endpoint ':datastore/:user_id/favorites', auth: { bearer: -> { bearer_token } }
|
216
|
-
endpoint ':datastore/:user_id/favorites/:id', auth: { bearer: -> { bearer_token } }
|
217
|
-
|
218
|
-
end
|
219
|
-
|
220
|
-
class Place < LHS::Service
|
221
|
-
|
222
|
-
endpoint ':datastore/v2/places', auth: { bearer: -> { bearer_token } }
|
223
|
-
endpoint ':datastore/v2/places/:id', auth: { bearer: -> { bearer_token } }
|
224
|
-
|
225
|
-
end
|
226
|
-
|
227
|
-
Favorite.includes(:place).where(user_id: current_user.id)
|
228
|
-
# Will include places and applies endpoint options to authenticate the request.
|
229
|
-
```
|
230
|
-
|
231
|
-
## Map data
|
232
|
-
|
233
|
-
To influence how data is accessed/provied, you can use mapping to either map deep nested data or to manipulate data when its accessed:
|
234
|
-
|
235
|
-
```ruby
|
236
|
-
class LocalEntry < LHS::Service
|
237
|
-
endpoint ':datastore/v2/local-entries'
|
238
|
-
|
239
|
-
map :name, ->{ addresses.first.business.identities.first.name }
|
240
|
-
|
241
|
-
end
|
242
|
-
```
|
243
|
-
|
244
|
-
### Known services when accessing mapped data from nested data
|
245
|
-
|
246
|
-
As LHS detects services from available service definitions as soon as a link is present, mappings will also be applied on nested data:
|
247
|
-
|
248
|
-
```
|
249
|
-
class Place < LHS::Service
|
250
|
-
endpoint ':datastore/v2/places'
|
251
|
-
|
252
|
-
map :name, ->{ addresses.first.business.identities.first.name }
|
253
|
-
|
254
|
-
end
|
255
|
-
|
256
|
-
class Favorite < LHS::Service
|
257
|
-
endpoint ':datastore/v2/favorites'
|
258
|
-
end
|
259
|
-
|
260
|
-
favorite = Favorite.includes(:place).find(1)
|
261
|
-
favorite.place.name # local.ch AG
|
262
|
-
```
|
263
|
-
|
264
|
-
## Validation
|
265
|
-
|
266
|
-
See: [Item Validation](../item.md).
|
@@ -1,19 +0,0 @@
|
|
1
|
-
require 'active_support'
|
2
|
-
|
3
|
-
class LHS::Service
|
4
|
-
|
5
|
-
module Build
|
6
|
-
extend ActiveSupport::Concern
|
7
|
-
|
8
|
-
module ClassMethods
|
9
|
-
|
10
|
-
def build(data = {})
|
11
|
-
data = LHS::Data.new(data, nil, self)
|
12
|
-
item = LHS::Item.new(data)
|
13
|
-
LHS::Data.new(item, nil, self)
|
14
|
-
end
|
15
|
-
alias_method :new, :build
|
16
|
-
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
data/lib/lhs/service.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
Dir[File.dirname(__FILE__) + '/concerns/service/*.rb'].each {|file| require file }
|
2
|
-
|
3
|
-
# A Service makes data available by using backend endpoints.
|
4
|
-
class LHS::Service
|
5
|
-
include All
|
6
|
-
include Batch
|
7
|
-
include Build
|
8
|
-
include Create
|
9
|
-
include Endpoints
|
10
|
-
include Find
|
11
|
-
include FindBy
|
12
|
-
include First
|
13
|
-
include Mapping
|
14
|
-
include Model
|
15
|
-
include Includes
|
16
|
-
include Request
|
17
|
-
include Where
|
18
|
-
end
|
@@ -1,60 +0,0 @@
|
|
1
|
-
require 'rails_helper'
|
2
|
-
|
3
|
-
describe LHS::Data do
|
4
|
-
context 'pagination' do
|
5
|
-
let(:datastore) { 'http://local.ch/v2' }
|
6
|
-
let(:record) { Feedback.where(entry_id: 1) }
|
7
|
-
let(:total) { 200 }
|
8
|
-
let(:total_pages) { total / limit }
|
9
|
-
let(:current_page) { offset + 1 }
|
10
|
-
let(:prev_page) { current_page - 1 }
|
11
|
-
let(:next_page) { current_page + 1 }
|
12
|
-
let(:offset) { 0 }
|
13
|
-
let(:limit) { 10 }
|
14
|
-
let(:body_json) do
|
15
|
-
{
|
16
|
-
items: [],
|
17
|
-
total: total,
|
18
|
-
offset: offset,
|
19
|
-
limit: limit
|
20
|
-
}.to_json
|
21
|
-
end
|
22
|
-
|
23
|
-
before(:each) do
|
24
|
-
LHC.config.placeholder('datastore', datastore)
|
25
|
-
class Feedback < LHS::Service
|
26
|
-
endpoint ':datastore/feedbacks'
|
27
|
-
end
|
28
|
-
stub_request(:get, 'http://local.ch/v2/feedbacks?entry_id=1')
|
29
|
-
.to_return(body: body_json)
|
30
|
-
end
|
31
|
-
|
32
|
-
it 'responds to limit_value' do
|
33
|
-
expect(record.limit_value).to eq(limit)
|
34
|
-
end
|
35
|
-
|
36
|
-
it 'responds to total_pages' do
|
37
|
-
expect(record.total_pages).to eq(total_pages)
|
38
|
-
end
|
39
|
-
|
40
|
-
it 'responds to current_page' do
|
41
|
-
expect(record.current_page).to eq(current_page)
|
42
|
-
end
|
43
|
-
|
44
|
-
it 'responds to first_page' do
|
45
|
-
expect(record.first_page).to eq(1)
|
46
|
-
end
|
47
|
-
|
48
|
-
it 'responds to last_page' do
|
49
|
-
expect(record.last_page).to eq(total_pages)
|
50
|
-
end
|
51
|
-
|
52
|
-
it 'responds to prev_page' do
|
53
|
-
expect(record.prev_page).to eq(prev_page)
|
54
|
-
end
|
55
|
-
|
56
|
-
it 'responds to next_page' do
|
57
|
-
expect(record.next_page).to eq(next_page)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
data/spec/dummy/README.rdoc
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
== README
|
2
|
-
|
3
|
-
This README would normally document whatever steps are necessary to get the
|
4
|
-
application up and running.
|
5
|
-
|
6
|
-
Things you may want to cover:
|
7
|
-
|
8
|
-
* Ruby version
|
9
|
-
|
10
|
-
* System dependencies
|
11
|
-
|
12
|
-
* Configuration
|
13
|
-
|
14
|
-
* Database creation
|
15
|
-
|
16
|
-
* Database initialization
|
17
|
-
|
18
|
-
* How to run the test suite
|
19
|
-
|
20
|
-
* Services (job queues, cache servers, search engines, etc.)
|
21
|
-
|
22
|
-
* Deployment instructions
|
23
|
-
|
24
|
-
* ...
|
25
|
-
|
26
|
-
|
27
|
-
Please feel free to use a different markup language if you do not plan to run
|
28
|
-
<tt>rake doc:app</tt>.
|
data/spec/service/where_spec.rb
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
require 'rails_helper'
|
2
|
-
|
3
|
-
describe LHS::Service do
|
4
|
-
|
5
|
-
let(:datastore) do
|
6
|
-
'http://datastore/v2'
|
7
|
-
end
|
8
|
-
|
9
|
-
before(:each) do
|
10
|
-
LHC.config.placeholder('datastore', datastore)
|
11
|
-
class SomeService < LHS::Service
|
12
|
-
endpoint ':datastore/v2/content-ads/:campaign_id/feedbacks'
|
13
|
-
endpoint ':datastore/v2/feedbacks'
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
context 'where' do
|
18
|
-
|
19
|
-
it 'is querying relevant endpoint when using where' do
|
20
|
-
stub_request(:get, "#{datastore}/v2/feedbacks?has_review=true").to_return(status: 200)
|
21
|
-
SomeService.where(has_review: true)
|
22
|
-
stub_request(:get, "#{datastore}/v2/content-ads/123/feedbacks?has_review=true").to_return(status: 200)
|
23
|
-
SomeService.where(campaign_id: '123', has_review: true)
|
24
|
-
stub_request(:get, "#{datastore}/v2/feedbacks").to_return(status: 200, body: '')
|
25
|
-
SomeService.where
|
26
|
-
end
|
27
|
-
|
28
|
-
it 'is using params as query params explicitly when provided in params namespace' do
|
29
|
-
stub_request(:get, "#{datastore}/v2/content-ads/123/feedbacks?campaign_id=456").to_return(status: 200)
|
30
|
-
SomeService.where(campaign_id: '123', params: { campaign_id: '456' })
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|