zaikio-client-helpers 0.1.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 +7 -0
- data/CHANGELOG.md +5 -0
- data/MIT-LICENSE +20 -0
- data/README.md +84 -0
- data/Rakefile +12 -0
- data/lib/zaikio-client-helpers.rb +5 -0
- data/lib/zaikio/client/helpers/json_parser.rb +33 -0
- data/lib/zaikio/client/helpers/pagination.rb +111 -0
- data/lib/zaikio/client/helpers/version.rb +9 -0
- data/lib/zaikio/client/model.rb +9 -0
- metadata +125 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3409eab2392246cb7990f31dff3d24b47fa8616b150ad217ed0bb5a7f89607a9
|
4
|
+
data.tar.gz: cefdc3ec682204a0552484e0d37ad06dda3449ff05c0ec00637aa4b85c695415
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a0b859d93659e155556e14c36b66b7ab2fbadcea7922f226b431e34d13668104144441239de59656055f321b92d84fc246ce1fe60f295f82d57a5f5879d7360a
|
7
|
+
data.tar.gz: e420427ff71df44f79ef37fa59ccb54a22c2becf3863021ce80b2a3164806c8336c0701ce0809f7571e9e7efd5c66dac07829adf861e15e4410900ac0632ac0f
|
data/CHANGELOG.md
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2020 Zaikio GmbH
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# Zaikio::Client::Helpers
|
2
|
+
|
3
|
+
A small gem which includes helpers for parsing & pagination when working with
|
4
|
+
Zaikio APIs using the [Spyke] library. Parts of this are referenced in:
|
5
|
+
|
6
|
+
* [Zaikio Hub Client]
|
7
|
+
* [Zaikio Procurement Client]
|
8
|
+
|
9
|
+
[Spyke]: https://github.com/balvig/spyke
|
10
|
+
[Zaikio Hub Client]: https://github.com/zaikio/zaikio-hub-ruby
|
11
|
+
[Zaikio Procurement Client]: https://github.com/zaikio/zaikio-procurement-ruby
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
1. Add to your gemspec or Gemfile:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
spec.add_dependency "zaikio-client-helpers"
|
19
|
+
```
|
20
|
+
|
21
|
+
2. Require it when your code is loaded:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
require "zaikio-client-helpers"
|
25
|
+
```
|
26
|
+
|
27
|
+
3. Extend the base class for your models:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
class User < Zaikio::Client::Model
|
31
|
+
# ...
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
## Parsing API responses
|
36
|
+
|
37
|
+
Spyke needs a couple of extra things in addition to JSON parsing, so this library exposes
|
38
|
+
a Spyke-compatible JSON parser that raises the correct exceptions when things go wrong:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
Faraday.new do |f|
|
42
|
+
f.use Zaikio::Client::Helpers::JSONParser
|
43
|
+
...
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
> Note: if you're also using the pagination middleware below, you should set this
|
48
|
+
> middleware _after_ the pagination middleware, because it needs to run before pagination
|
49
|
+
> can happen.
|
50
|
+
|
51
|
+
## Automatic pagination
|
52
|
+
|
53
|
+
First, we need to configure the Faraday middleware, wherever the Faraday::Connection is
|
54
|
+
constructed:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
Faraday.new do |f|
|
58
|
+
f.use Zaikio::Client::Helpers::Pagination::FaradayMiddleware
|
59
|
+
f.use Zaikio::Client::Helpers::JSONParser
|
60
|
+
...
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
If you aren't inheriting `Zaikio::Client::Model`, you should mixin our pagination library
|
65
|
+
like so: `include Zaikio::Client::Helpers::Pagination`.
|
66
|
+
|
67
|
+
The library works by overriding the `#all` method on a relation, so it will keep fetching
|
68
|
+
pages from the remote API until there are none left:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
Model.all.map(&:id)
|
72
|
+
#=> There are 3 pages, I will load more pages automatically
|
73
|
+
#=> [1,2,3]
|
74
|
+
```
|
75
|
+
|
76
|
+
If you wish to opt-out of automatic pagination, you can use the Lazy version (don't forget
|
77
|
+
to call `each` or `to_a` to materialize the records):
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
Model.all.lazy.take(2).map(&:id).to_a
|
81
|
+
```
|
82
|
+
|
83
|
+
Note that if the endpoint doesn't support pagination (i.e. doesn't return a `Current-Page`
|
84
|
+
header), pagination is automatically disabled.
|
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require "faraday"
|
2
|
+
require "multi_json"
|
3
|
+
|
4
|
+
module Zaikio::Client::Helpers
|
5
|
+
class JSONParser < Faraday::Response::Middleware
|
6
|
+
def on_complete(env)
|
7
|
+
connection_error(env) unless /^(2\d\d)|422|404$/.match?(env.status.to_s)
|
8
|
+
|
9
|
+
raise Spyke::ResourceNotFound if env.status.to_s == "404"
|
10
|
+
|
11
|
+
env.body = parse_body(env.body)
|
12
|
+
end
|
13
|
+
|
14
|
+
def connection_error(env)
|
15
|
+
raise Spyke::ConnectionError, "Status: #{env.status}, URL: #{env.url}, body: #{env.body}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def parse_body(body)
|
19
|
+
json = MultiJson.load(body, symbolize_keys: true)
|
20
|
+
{
|
21
|
+
data: json,
|
22
|
+
metadata: {},
|
23
|
+
errors: json.is_a?(Hash) ? json[:errors] : {}
|
24
|
+
}
|
25
|
+
rescue MultiJson::ParseError
|
26
|
+
{
|
27
|
+
data: {},
|
28
|
+
metadata: {},
|
29
|
+
errors: {}
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require "faraday"
|
2
|
+
require "spyke"
|
3
|
+
|
4
|
+
module Zaikio::Client::Helpers
|
5
|
+
module Pagination
|
6
|
+
HEADERS = {
|
7
|
+
total_count: "Total-Count",
|
8
|
+
total_pages: "Total-Pages",
|
9
|
+
current_page: "Current-Page"
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
METADATA_KEY = :pagination
|
13
|
+
|
14
|
+
# Faraday Middleware for extracting any pagination headers into the a top-level
|
15
|
+
# :metadata hash, or the env hash for non-JSON responses.
|
16
|
+
#
|
17
|
+
# @example Usage
|
18
|
+
# conn = Faraday.new do |f|
|
19
|
+
# f.use Zaikio::Client::Helpers::Pagination::FaradayMiddleware
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# response = conn.get("/")
|
23
|
+
# response.env[METADATA_KEY]
|
24
|
+
# #=> {total_count: 4, total_pages: 1, current_page: 1}
|
25
|
+
class FaradayMiddleware < Faraday::Response::Middleware
|
26
|
+
def on_complete(env)
|
27
|
+
@env = env
|
28
|
+
|
29
|
+
metadata = HEADERS.transform_values do |key|
|
30
|
+
header(key)
|
31
|
+
end
|
32
|
+
|
33
|
+
if env.body.is_a?(Hash)
|
34
|
+
@env.body[:metadata] ||= {}
|
35
|
+
@env.body[:metadata][METADATA_KEY] = metadata
|
36
|
+
else
|
37
|
+
@env[METADATA_KEY] = metadata
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def header(key)
|
44
|
+
value = @env.response_headers[key]
|
45
|
+
value.try(:to_i)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
extend ActiveSupport::Concern
|
50
|
+
|
51
|
+
included do
|
52
|
+
scope :page, ->(value) { where(page: value) }
|
53
|
+
scope :per_page, ->(value) { where(per_page: value) }
|
54
|
+
end
|
55
|
+
|
56
|
+
module ClassMethods
|
57
|
+
# Overrides the method included by Spyke::Scoping to return paginated relations.
|
58
|
+
def all
|
59
|
+
current_scope || Relation.new(self, uri: uri)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class Relation < Spyke::Relation
|
64
|
+
HEADERS.each_key do |symbol|
|
65
|
+
define_method(symbol) do
|
66
|
+
find_some.metadata[METADATA_KEY][symbol]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def first_page?
|
71
|
+
current_page == 1
|
72
|
+
end
|
73
|
+
|
74
|
+
def next_page
|
75
|
+
current_page + 1
|
76
|
+
end
|
77
|
+
|
78
|
+
def last_page?
|
79
|
+
current_page >= total_pages
|
80
|
+
end
|
81
|
+
|
82
|
+
def supports_pagination?
|
83
|
+
current_page.present?
|
84
|
+
end
|
85
|
+
|
86
|
+
# Unlike the default implementation in Spyke, this version of #each is recursive
|
87
|
+
# and will repeatedly paginate through the remote API until it runs out of
|
88
|
+
# records.
|
89
|
+
#
|
90
|
+
# To avoid this behaviour, you can ask for a Lazy enumerator to take just the
|
91
|
+
# records you need, e.g.:
|
92
|
+
# User.all.lazy.take(3).to_a
|
93
|
+
# #=> [.., .., ..]
|
94
|
+
def each(&block)
|
95
|
+
return to_enum(:each) unless block_given?
|
96
|
+
|
97
|
+
find_some.each(&block)
|
98
|
+
return if !supports_pagination? || last_page?
|
99
|
+
|
100
|
+
puts "There are #{total_count} pages, I will load more pages automatically" if first_page?
|
101
|
+
clone.page(next_page).each(&block)
|
102
|
+
end
|
103
|
+
|
104
|
+
def clone
|
105
|
+
# We use cloning when fetching a second page using the same scope/query, however
|
106
|
+
# we want to clear any loaded records from @find_some before doing so.
|
107
|
+
super.tap { |obj| obj.instance_variable_set(:@find_some, nil) }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
metadata
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: zaikio-client-helpers
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Zaikio GMBH
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-06-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: faraday
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: multi_json
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: spyke
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: vcr
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: webmock
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description:
|
84
|
+
email:
|
85
|
+
- suppoert@zaikio.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- CHANGELOG.md
|
91
|
+
- MIT-LICENSE
|
92
|
+
- README.md
|
93
|
+
- Rakefile
|
94
|
+
- lib/zaikio-client-helpers.rb
|
95
|
+
- lib/zaikio/client/helpers/json_parser.rb
|
96
|
+
- lib/zaikio/client/helpers/pagination.rb
|
97
|
+
- lib/zaikio/client/helpers/version.rb
|
98
|
+
- lib/zaikio/client/model.rb
|
99
|
+
homepage: https://github.com/zaikio/zaikio-client-helpers
|
100
|
+
licenses:
|
101
|
+
- MIT
|
102
|
+
metadata:
|
103
|
+
changelog_uri: https://github.com/zaikio/zaikio-client-helpers/blob/main/CHANGELOG.md
|
104
|
+
homepage_uri: https://github.com/zaikio/zaikio-client-helpers
|
105
|
+
source_code_uri: https://github.com/zaikio/zaikio-client-helpers
|
106
|
+
post_install_message:
|
107
|
+
rdoc_options: []
|
108
|
+
require_paths:
|
109
|
+
- lib
|
110
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
111
|
+
requirements:
|
112
|
+
- - ">="
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '0'
|
115
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - ">="
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
requirements: []
|
121
|
+
rubygems_version: 3.2.15
|
122
|
+
signing_key:
|
123
|
+
specification_version: 4
|
124
|
+
summary: Small meta-gem with middleware and tools for working with Zaikio APIs
|
125
|
+
test_files: []
|