pack_api 1.0.0 → 1.0.2
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/CHANGELOG.md +8 -28
- data/README.md +69 -7
- data/lib/pack_api/rspec/shared_examples_for_api_query_methods.rb +126 -0
- data/lib/pack_api/rspec/shared_examples_for_paginated_results.rb +51 -0
- data/lib/pack_api/version.rb +1 -1
- data/lib/pack_api.rb +46 -49
- metadata +52 -54
- data/lib/pack_api/config/dry_types_initializer.rb +0 -1
- /data/lib/pack_api/{models/internal_error.rb → internal_error.rb} +0 -0
- /data/lib/pack_api/{models/mapping → mapping}/abstract_transformer.rb +0 -0
- /data/lib/pack_api/{models/mapping → mapping}/api_to_model_attributes_transformer.rb +0 -0
- /data/lib/pack_api/{models/mapping → mapping}/attribute_hash_transformer.rb +0 -0
- /data/lib/pack_api/{models/mapping → mapping}/attribute_map.rb +0 -0
- /data/lib/pack_api/{models/mapping → mapping}/attribute_map_registry.rb +0 -0
- /data/lib/pack_api/{models/mapping → mapping}/error_hash_to_api_attributes_transformer.rb +0 -0
- /data/lib/pack_api/{models/mapping → mapping}/filter_map.rb +0 -0
- /data/lib/pack_api/{models/mapping → mapping}/model_to_api_attributes_transformer.rb +0 -0
- /data/lib/pack_api/{models/mapping → mapping}/normalized_api_attribute.rb +0 -0
- /data/lib/pack_api/{models/mapping → mapping}/null_transformer.rb +0 -0
- /data/lib/pack_api/{models/mapping → mapping}/value_object_factory.rb +0 -0
- /data/lib/pack_api/{models/pagination → pagination}/opaque_token_v2.rb +0 -0
- /data/lib/pack_api/{models/pagination → pagination}/paginator.rb +0 -0
- /data/lib/pack_api/{models/pagination → pagination}/paginator_builder.rb +0 -0
- /data/lib/pack_api/{models/pagination → pagination}/paginator_cursor.rb +0 -0
- /data/lib/pack_api/{models/pagination → pagination}/snapshot_paginator.rb +0 -0
- /data/lib/pack_api/{models/querying → querying}/abstract_boolean_filter.rb +0 -0
- /data/lib/pack_api/{models/querying → querying}/abstract_enum_filter.rb +0 -0
- /data/lib/pack_api/{models/querying → querying}/abstract_filter.rb +0 -0
- /data/lib/pack_api/{models/querying → querying}/abstract_numeric_filter.rb +0 -0
- /data/lib/pack_api/{models/querying → querying}/abstract_range_filter.rb +0 -0
- /data/lib/pack_api/{models/querying → querying}/attribute_filter.rb +0 -0
- /data/lib/pack_api/{models/querying → querying}/attribute_filter_factory.rb +0 -0
- /data/lib/pack_api/{models/querying → querying}/collection_query.rb +0 -0
- /data/lib/pack_api/{models/querying → querying}/composable_query.rb +0 -0
- /data/lib/pack_api/{models/querying → querying}/default_filter.rb +0 -0
- /data/lib/pack_api/{models/querying → querying}/discoverable_filter.rb +0 -0
- /data/lib/pack_api/{models/querying → querying}/dynamic_enum_filter.rb +0 -0
- /data/lib/pack_api/{models/querying → querying}/filter_factory.rb +0 -0
- /data/lib/pack_api/{models/querying → querying}/sort_hash.rb +0 -0
- /data/lib/pack_api/{models/types → types}/aggregate_type.rb +0 -0
- /data/lib/pack_api/{models/types → types}/base_type.rb +0 -0
- /data/lib/pack_api/{models/types → types}/boolean_filter_definition.rb +0 -0
- /data/lib/pack_api/{models/types → types}/collection_result_metadata.rb +0 -0
- /data/lib/pack_api/{models/types → types}/custom_filter_definition.rb +0 -0
- /data/lib/pack_api/{models/types → types}/enum_filter_definition.rb +0 -0
- /data/lib/pack_api/{models/types → types}/filter_option.rb +0 -0
- /data/lib/pack_api/{models/types → types}/globally_identifiable.rb +0 -0
- /data/lib/pack_api/{models/types → types}/numeric_filter_definition.rb +0 -0
- /data/lib/pack_api/{models/types → types}/range_filter_definition.rb +0 -0
- /data/lib/pack_api/{models/types → types}/result.rb +0 -0
- /data/lib/pack_api/{models/types → types}/simple_filter_definition.rb +0 -0
- /data/lib/pack_api/{models/values_in_background_batches.rb → values_in_background_batches.rb} +0 -0
- /data/lib/pack_api/{models/values_in_batches.rb → values_in_batches.rb} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0f59a0e96b77d27b0c274c7bfbd3d02d58bf10139079f7e8f65f0f141700657a
|
|
4
|
+
data.tar.gz: 48f2caa082efe138a25ce949383e04959b07f2cc6e383fc277059b19c7edb489
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4b81ed5a8dd8536b534c18774efbdd21eb3d8799a9b00aa96afa015d5fed12ff626e48c5035983a7b28760f6a0a40c06b205d55bc2e93f82c67b29381360ec27
|
|
7
|
+
data.tar.gz: 7a7bda196011de7a10e1a8c8b98961c530dee455bc4d908f1d0c9b5c726c7df47bdeef29906a9f8faa0a65a169dfdfbadb8e1eb8c0cbb15dff35b3086c3abd42
|
data/CHANGELOG.md
CHANGED
|
@@ -5,35 +5,15 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [
|
|
8
|
+
## [1.0.1] - 2025-11-11
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Improved README to clarify motivations, features, and usage examples
|
|
13
|
+
- Using Zeitwerk to autoload Gem modules
|
|
14
|
+
|
|
15
|
+
## [1.0.0] - 2025-11-11
|
|
11
16
|
|
|
12
17
|
### Added
|
|
13
18
|
|
|
14
|
-
- Initial release of PackAPI gem
|
|
15
|
-
- Mapping module for transforming data between domain models and API representations
|
|
16
|
-
- AttributeMap for defining bidirectional mappings
|
|
17
|
-
- AttributeMapRegistry for centralized mapping management
|
|
18
|
-
- ModelToAPIAttributesTransformer and APIToModelAttributesTransformer
|
|
19
|
-
- ValueObjectFactory for creating value objects
|
|
20
|
-
- Querying module for building flexible query interfaces
|
|
21
|
-
- ComposableQuery and CollectionQuery
|
|
22
|
-
- Filter implementations (boolean, enum, numeric, range)
|
|
23
|
-
- FilterFactory for dynamic filter creation
|
|
24
|
-
- SortHash for handling sorting parameters
|
|
25
|
-
- Pagination module with multiple strategies
|
|
26
|
-
- Paginator for standard pagination
|
|
27
|
-
- SnapshotPaginator for consistent results
|
|
28
|
-
- PaginatorBuilder for custom configurations
|
|
29
|
-
- OpaqueTokenV2 for secure cursor tokens
|
|
30
|
-
- Types module with dry-types integration
|
|
31
|
-
- BaseType and AggregateType
|
|
32
|
-
- Result and CollectionResultMetadata
|
|
33
|
-
- Filter definition types
|
|
34
|
-
- Batch operation utilities
|
|
35
|
-
- ValuesInBatches for synchronous batch processing
|
|
36
|
-
- ValuesInBackgroundBatches for asynchronous batch processing
|
|
37
|
-
|
|
38
|
-
[Unreleased]: https://github.com/flytedesk/pack_api/compare/v0.1.0...HEAD
|
|
39
|
-
[0.1.0]: https://github.com/flytedesk/pack_api/releases/tag/v0.1.0
|
|
19
|
+
- Initial release of PackAPI gem
|
data/README.md
CHANGED
|
@@ -12,6 +12,18 @@ PackAPI provides a comprehensive set of tools for building robust API layers on
|
|
|
12
12
|
- **Query building** - Elements for building query endpoints based on user inputs (sort, filter, pagination)
|
|
13
13
|
- **Batch operations** - Elements for retrieving multiple pages of data from other query endpoints
|
|
14
14
|
|
|
15
|
+
## Motivation
|
|
16
|
+
|
|
17
|
+
Separation of concerns and information hiding within the module.
|
|
18
|
+
|
|
19
|
+
In order to separate the public interface from the (private) implementation of our subsystems (modules), we needed to
|
|
20
|
+
create an API that did not depend on our ActiveRecord models. We chose to implement this separation using value objects
|
|
21
|
+
(built using dry-types, but could have been built using Ruby Data type). Beyond the API methods, the public interface
|
|
22
|
+
definition is then captured in the explicit attribute list of these value objects.
|
|
23
|
+
|
|
24
|
+
However, building the mapping between the domain models and the API value objects can be tedious and error-prone. We
|
|
25
|
+
created this gem to provide reusable building blocks to make this mapping easier to define and maintain.
|
|
26
|
+
|
|
15
27
|
## Installation
|
|
16
28
|
|
|
17
29
|
Add this line to your application's Gemfile:
|
|
@@ -55,11 +67,11 @@ The mapping module provides tools for transforming data between domain models an
|
|
|
55
67
|
Build flexible query interfaces with support for filtering, sorting, and pagination:
|
|
56
68
|
|
|
57
69
|
- `ComposableQuery` - Build complex queries from simpler components
|
|
58
|
-
- `CollectionQuery` - Query collections
|
|
70
|
+
- `CollectionQuery` - Query ActiveRecord collections based on arguments for pagination, filtering and sorting
|
|
59
71
|
- `AbstractFilter` - Base class for custom filters
|
|
60
|
-
- Filter implementations for boolean, enum, numeric, and range filters
|
|
61
72
|
- `FilterFactory` - Create filters dynamically based on query method arguments
|
|
62
73
|
- `SortHash` - Handle sorting parameters
|
|
74
|
+
- Base class filter implementations for boolean, enum, numeric, and range filters
|
|
63
75
|
|
|
64
76
|
### Pagination
|
|
65
77
|
|
|
@@ -67,16 +79,15 @@ Enable paginated access to resources across the API:
|
|
|
67
79
|
|
|
68
80
|
- `Paginator` - Standard pagination implementation
|
|
69
81
|
- `PaginatorBuilder` - Build paginators with custom configurations
|
|
70
|
-
- `SnapshotPaginator` - Enable record iteration (one by one) across results in a page,
|
|
71
|
-
even when the underlying records change state (and may no longer be at the same position in the result set)
|
|
82
|
+
- `SnapshotPaginator` - Enable record iteration (one by one) across results in a page, even when the underlying records change state (and may no longer be at the same position in the result set)
|
|
72
83
|
|
|
73
84
|
### Types
|
|
74
85
|
|
|
75
86
|
Type definitions and validation using dry-types:
|
|
76
87
|
|
|
77
|
-
- `BaseType` - Base type for
|
|
88
|
+
- `BaseType` - Base type for value objects
|
|
78
89
|
- `CollectionResultMetadata` - Metadata for paginated collections
|
|
79
|
-
- `Result` - Generic result type
|
|
90
|
+
- `Result` - Generic result type to be returned from your API methods
|
|
80
91
|
- `AggregateType` - Composite types made of attributes from other types
|
|
81
92
|
- Filter definition types for various data types
|
|
82
93
|
|
|
@@ -174,7 +185,9 @@ end
|
|
|
174
185
|
|
|
175
186
|
```
|
|
176
187
|
|
|
177
|
-
3. Implement
|
|
188
|
+
3. Implement filters.
|
|
189
|
+
|
|
190
|
+
4. Implement a query endpoint using the attribute map:
|
|
178
191
|
|
|
179
192
|
```ruby
|
|
180
193
|
def query_blog_posts(cursor = nil, search = nil, sort = nil, page_size = 50, filters = {}, optional_attributes = [])
|
|
@@ -215,6 +228,55 @@ def query_blog_posts(cursor = nil, search = nil, sort = nil, page_size = 50, fil
|
|
|
215
228
|
end
|
|
216
229
|
```
|
|
217
230
|
|
|
231
|
+
## Testing with Shared Examples
|
|
232
|
+
|
|
233
|
+
PackAPI includes RSpec shared examples to help test your API query methods. These are opt-in and only need to be loaded if you're using RSpec.
|
|
234
|
+
|
|
235
|
+
### Loading Shared Examples
|
|
236
|
+
|
|
237
|
+
In your `spec_helper.rb` or `rails_helper.rb`, require the shared examples you need:
|
|
238
|
+
|
|
239
|
+
```ruby
|
|
240
|
+
# Load all shared examples
|
|
241
|
+
require 'pack_api/rspec/shared_examples_for_api_query_methods'
|
|
242
|
+
require 'pack_api/rspec/shared_examples_for_paginated_results'
|
|
243
|
+
|
|
244
|
+
# Or load them individually as needed
|
|
245
|
+
require 'pack_api/rspec/shared_examples_for_api_query_methods'
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Using the Shared Examples
|
|
249
|
+
|
|
250
|
+
**Testing API Query Methods:**
|
|
251
|
+
|
|
252
|
+
```ruby
|
|
253
|
+
RSpec.describe 'query_blog_posts' do
|
|
254
|
+
let(:api_query_method) { method(:query_blog_posts) }
|
|
255
|
+
let(:resources) { BlogPost.all }
|
|
256
|
+
|
|
257
|
+
it_behaves_like 'an API query method'
|
|
258
|
+
|
|
259
|
+
# With custom options
|
|
260
|
+
it_behaves_like 'an API query method',
|
|
261
|
+
model_id_attribute: :uuid,
|
|
262
|
+
supports_search: true do
|
|
263
|
+
let(:search_terms) { "searchable text" }
|
|
264
|
+
let(:matched_resources) { BlogPost.where("title LIKE ?", "%searchable%") }
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Testing Paginated Methods:**
|
|
270
|
+
|
|
271
|
+
```ruby
|
|
272
|
+
RSpec.describe 'paginated query' do
|
|
273
|
+
let(:paginated_api_query_method) { method(:query_blog_posts) }
|
|
274
|
+
let(:paginated_resources) { BlogPost.all }
|
|
275
|
+
|
|
276
|
+
it_behaves_like 'a paginated API method', model_id_attribute: :external_id
|
|
277
|
+
end
|
|
278
|
+
```
|
|
279
|
+
|
|
218
280
|
## Development
|
|
219
281
|
|
|
220
282
|
After checking out the repo, run:
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PackAPI
|
|
4
|
+
###
|
|
5
|
+
# Assumes the following variables are defined:
|
|
6
|
+
# - api_query_method: the method to call
|
|
7
|
+
# - resources: the resources to compare against (must be more than 1)
|
|
8
|
+
#
|
|
9
|
+
# If the options contain a key :model_id_attribute, then the public id will be mapped to the given model attribute.
|
|
10
|
+
# Otherwise, it defaults to :external_id.
|
|
11
|
+
#
|
|
12
|
+
# If the options contain a key: `supports_search` set to `true`, then
|
|
13
|
+
# the following variables are also required:
|
|
14
|
+
# - search_terms: a string, representing the search terms to use
|
|
15
|
+
# - matched_resources: *at least 2* resources that will be returned by the search
|
|
16
|
+
RSpec.shared_examples 'an API query method' do |**options|
|
|
17
|
+
let(:model_id_attribute) { options[:model_id_attribute] || :external_id }
|
|
18
|
+
|
|
19
|
+
context 'with no id' do
|
|
20
|
+
it 'returns a successful result with all resources' do
|
|
21
|
+
# when
|
|
22
|
+
result = api_query_method.call
|
|
23
|
+
|
|
24
|
+
# then
|
|
25
|
+
expect(result.success).to be_truthy, -> { result.errors }
|
|
26
|
+
expect(result.errors).to be_nil
|
|
27
|
+
expect(result.value).not_to be_nil
|
|
28
|
+
expect(result.value).to have(resources.count).items
|
|
29
|
+
expect(result.value.pluck(:id).sort).to match_array(resources.pluck(model_id_attribute).sort)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
context 'with nil id' do
|
|
34
|
+
it 'returns a successful result with all resources' do
|
|
35
|
+
# when
|
|
36
|
+
result = api_query_method.call(id: nil)
|
|
37
|
+
|
|
38
|
+
# then
|
|
39
|
+
expect(result.success).to be_truthy, -> { result.errors }
|
|
40
|
+
expect(result.errors).to be_nil
|
|
41
|
+
expect(result.value).not_to be_nil
|
|
42
|
+
expect(result.value).to have(resources.count).items
|
|
43
|
+
expect(result.value.pluck(:id).sort).to match_array(resources.pluck(model_id_attribute).sort)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
context 'with an empty string id' do
|
|
48
|
+
it 'returns a successful result with no resources' do
|
|
49
|
+
# when
|
|
50
|
+
result = api_query_method.call(id: '')
|
|
51
|
+
|
|
52
|
+
# then
|
|
53
|
+
expect(result.success).to be_truthy, -> { result.errors }
|
|
54
|
+
expect(result.errors).to be_nil
|
|
55
|
+
expect(result.value).not_to be_nil
|
|
56
|
+
expect(result.value).to have(0).items
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
context 'with an empty array id' do
|
|
61
|
+
it 'returns a successful result with no resources' do
|
|
62
|
+
# when
|
|
63
|
+
result = api_query_method.call(id: [])
|
|
64
|
+
|
|
65
|
+
# then
|
|
66
|
+
expect(result.success).to be_truthy, -> { result.errors }
|
|
67
|
+
expect(result.errors).to be_nil
|
|
68
|
+
expect(result.value).not_to be_nil
|
|
69
|
+
expect(result.value).to have(0).items
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
context 'with an unknown id' do
|
|
74
|
+
it 'returns an empty success result' do
|
|
75
|
+
result = api_query_method.call(id: 'unknown')
|
|
76
|
+
expect(result.success).to be_truthy, -> { result.errors }
|
|
77
|
+
expect(result.value).to have(0).items
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
context 'with a known id' do
|
|
82
|
+
it 'returns a success result with the resource' do
|
|
83
|
+
value = api_query_method.call(id: resources.first[model_id_attribute])
|
|
84
|
+
expect(value.success).to be_truthy, -> { result.errors }
|
|
85
|
+
expect(value.value).to have(1).item
|
|
86
|
+
expect(value.value.first.id).to eq(resources.first[model_id_attribute])
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it_behaves_like 'a paginated API method', model_id_attribute: options[:model_id_attribute] || :external_id do
|
|
91
|
+
let(:paginated_api_query_method) { api_query_method }
|
|
92
|
+
let(:paginated_resources) { resources }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
context 'with search terms', if: options[:supports_search] do
|
|
96
|
+
it 'limits results to matching resources' do
|
|
97
|
+
# when
|
|
98
|
+
result = api_query_method.call(search_terms:)
|
|
99
|
+
# then
|
|
100
|
+
expect(result.success).to be_truthy, -> { result.errors }
|
|
101
|
+
expect(result.value).to have(matched_resources.size).item
|
|
102
|
+
matched_resource_ids = matched_resources.pluck(model_id_attribute)
|
|
103
|
+
result.value.each do |resource|
|
|
104
|
+
expect(matched_resource_ids).to include(resource.id)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it_behaves_like 'a paginated API method', model_id_attribute: options[:model_id_attribute] || :external_id do
|
|
109
|
+
let(:paginated_api_query_method) do
|
|
110
|
+
->(**args) { api_query_method.call(search_terms:, **args) }
|
|
111
|
+
end
|
|
112
|
+
let(:paginated_resources) { matched_resources }
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it 'supports sorting by API attributes only' do
|
|
117
|
+
resources.each_with_index { |resource, index| resource.update(model_id_attribute => format('%02d', index + 1)) }
|
|
118
|
+
# when - sort by API attribute `id` in descending order
|
|
119
|
+
results = api_query_method.call(sort: { id: :desc })
|
|
120
|
+
# then - results should be in model_id_attribute in descending order
|
|
121
|
+
expect(results.success).to be_truthy, -> { results.errors }
|
|
122
|
+
expect(results.value.first.id).to eq(resources.last[model_id_attribute])
|
|
123
|
+
expect(results.value.last.id).to eq(resources.first[model_id_attribute])
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PackAPI
|
|
4
|
+
###
|
|
5
|
+
# Assumes the following variables are defined:
|
|
6
|
+
# - paginated_api_query_method: the method to call
|
|
7
|
+
# - paginated_resources: the resources to compare against (must be more than 1)
|
|
8
|
+
#
|
|
9
|
+
# If the options contain a key :model_id_attribute, then the public id will be mapped to the given model attribute.
|
|
10
|
+
# Otherwise, it defaults to :external_id.
|
|
11
|
+
# If the options contain a key :public_id_attribute, that will be used to access the resource identifier in the results.
|
|
12
|
+
RSpec.shared_examples 'a paginated API method' do |**options|
|
|
13
|
+
let(:public_id_attribute) { options[:public_id_attribute] || :id }
|
|
14
|
+
let(:model_id_attribute) { options[:model_id_attribute] || :external_id }
|
|
15
|
+
|
|
16
|
+
it 'can access the resources page-by-page' do
|
|
17
|
+
returned_object_ids = []
|
|
18
|
+
|
|
19
|
+
page_one_results = paginated_api_query_method.call(per_page: 1)
|
|
20
|
+
expect(page_one_results.success).to be_truthy, -> { page_one_results.errors }
|
|
21
|
+
expect(page_one_results.value).to have(1).item
|
|
22
|
+
expect(page_one_results.collection_metadata.next_page_cursor).not_to be_nil
|
|
23
|
+
returned_object_ids << page_one_results.value.first.send(public_id_attribute)
|
|
24
|
+
next_page_cursor = page_one_results.collection_metadata.next_page_cursor
|
|
25
|
+
|
|
26
|
+
(paginated_resources.count - 1).times do |index|
|
|
27
|
+
next_page_results = paginated_api_query_method.call(per_page: 1, cursor: next_page_cursor)
|
|
28
|
+
expect(next_page_results.success).to be_truthy, -> { next_page_results.errors }
|
|
29
|
+
expect(next_page_results.value).to have(1).item
|
|
30
|
+
if index < paginated_resources.count - 2 # if not last page
|
|
31
|
+
expect(next_page_results.collection_metadata.next_page_cursor).not_to be_nil, 'next page cursor should not be nil'
|
|
32
|
+
next_page_cursor = next_page_results.collection_metadata.next_page_cursor
|
|
33
|
+
else
|
|
34
|
+
expect(next_page_results.collection_metadata.next_page_cursor).to be_nil, 'next page cursor should be nil'
|
|
35
|
+
end
|
|
36
|
+
returned_object_ids << next_page_results.value.first.send(public_id_attribute)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# verify all objects were returned
|
|
40
|
+
expect(returned_object_ids.sort).to match_array(paginated_resources.pluck(model_id_attribute).sort)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'can access the metadata (without data)' do
|
|
44
|
+
results = paginated_api_query_method.call(per_page: 0)
|
|
45
|
+
expect(results.success).to be_truthy, -> { results.errors }
|
|
46
|
+
expect(results.value).to be_empty
|
|
47
|
+
expect(results.collection_metadata).not_to be_nil
|
|
48
|
+
expect(results.collection_metadata.total_items).to eq(paginated_resources.count)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/pack_api/version.rb
CHANGED
data/lib/pack_api.rb
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
2
|
require "active_model"
|
|
4
3
|
require "active_record"
|
|
5
4
|
require "dry-types"
|
|
@@ -8,65 +7,63 @@ require "brotli"
|
|
|
8
7
|
|
|
9
8
|
require_relative "types"
|
|
10
9
|
require_relative "pack_api/version"
|
|
11
|
-
require_relative "pack_api/config/dry_types_initializer"
|
|
12
10
|
|
|
13
|
-
# Load in dependency order
|
|
14
11
|
module PackAPI
|
|
15
|
-
autoload :InternalError, "pack_api/
|
|
16
|
-
autoload :ValuesInBatches, "pack_api/
|
|
17
|
-
autoload :ValuesInBackgroundBatches, "pack_api/
|
|
12
|
+
autoload :InternalError, "pack_api/internal_error"
|
|
13
|
+
autoload :ValuesInBatches, "pack_api/values_in_batches"
|
|
14
|
+
autoload :ValuesInBackgroundBatches, "pack_api/values_in_background_batches"
|
|
18
15
|
|
|
19
16
|
module Mapping
|
|
20
|
-
autoload :AbstractTransformer, "pack_api/
|
|
21
|
-
autoload :AttributeHashTransformer, "pack_api/
|
|
22
|
-
autoload :NullTransformer, "pack_api/
|
|
23
|
-
autoload :APIToModelAttributesTransformer, "pack_api/
|
|
24
|
-
autoload :ModelToAPIAttributesTransformer, "pack_api/
|
|
25
|
-
autoload :ErrorHashToAPIAttributesTransformer, "pack_api/
|
|
26
|
-
autoload :NormalizedAPIAttribute, "pack_api/
|
|
27
|
-
autoload :AttributeMap, "pack_api/
|
|
28
|
-
autoload :AttributeMapRegistry, "pack_api/
|
|
29
|
-
autoload :FilterMap, "pack_api/
|
|
30
|
-
autoload :ValueObjectFactory, "pack_api/
|
|
17
|
+
autoload :AbstractTransformer, "pack_api/mapping/abstract_transformer"
|
|
18
|
+
autoload :AttributeHashTransformer, "pack_api/mapping/attribute_hash_transformer"
|
|
19
|
+
autoload :NullTransformer, "pack_api/mapping/null_transformer"
|
|
20
|
+
autoload :APIToModelAttributesTransformer, "pack_api/mapping/api_to_model_attributes_transformer"
|
|
21
|
+
autoload :ModelToAPIAttributesTransformer, "pack_api/mapping/model_to_api_attributes_transformer"
|
|
22
|
+
autoload :ErrorHashToAPIAttributesTransformer, "pack_api/mapping/error_hash_to_api_attributes_transformer"
|
|
23
|
+
autoload :NormalizedAPIAttribute, "pack_api/mapping/normalized_api_attribute"
|
|
24
|
+
autoload :AttributeMap, "pack_api/mapping/attribute_map"
|
|
25
|
+
autoload :AttributeMapRegistry, "pack_api/mapping/attribute_map_registry"
|
|
26
|
+
autoload :FilterMap, "pack_api/mapping/filter_map"
|
|
27
|
+
autoload :ValueObjectFactory, "pack_api/mapping/value_object_factory"
|
|
31
28
|
end
|
|
32
29
|
|
|
33
30
|
module Pagination
|
|
34
|
-
autoload :OpaqueTokenV2, "pack_api/
|
|
35
|
-
autoload :PaginatorCursor, "pack_api/
|
|
36
|
-
autoload :Paginator, "pack_api/
|
|
37
|
-
autoload :PaginatorBuilder, "pack_api/
|
|
38
|
-
autoload :SnapshotPaginator, "pack_api/
|
|
31
|
+
autoload :OpaqueTokenV2, "pack_api/pagination/opaque_token_v2"
|
|
32
|
+
autoload :PaginatorCursor, "pack_api/pagination/paginator_cursor"
|
|
33
|
+
autoload :Paginator, "pack_api/pagination/paginator"
|
|
34
|
+
autoload :PaginatorBuilder, "pack_api/pagination/paginator_builder"
|
|
35
|
+
autoload :SnapshotPaginator, "pack_api/pagination/snapshot_paginator"
|
|
39
36
|
end
|
|
40
37
|
|
|
41
38
|
module Querying
|
|
42
|
-
autoload :AbstractFilter, "pack_api/
|
|
43
|
-
autoload :AbstractBooleanFilter, "pack_api/
|
|
44
|
-
autoload :AbstractEnumFilter, "pack_api/
|
|
45
|
-
autoload :AbstractNumericFilter, "pack_api/
|
|
46
|
-
autoload :AbstractRangeFilter, "pack_api/
|
|
47
|
-
autoload :DefaultFilter, "pack_api/
|
|
48
|
-
autoload :DiscoverableFilter, "pack_api/
|
|
49
|
-
autoload :DynamicEnumFilter, "pack_api/
|
|
50
|
-
autoload :AttributeFilter, "pack_api/
|
|
51
|
-
autoload :AttributeFilterFactory, "pack_api/
|
|
52
|
-
autoload :FilterFactory, "pack_api/
|
|
53
|
-
autoload :ComposableQuery, "pack_api/
|
|
54
|
-
autoload :CollectionQuery, "pack_api/
|
|
55
|
-
autoload :SortHash, "pack_api/
|
|
39
|
+
autoload :AbstractFilter, "pack_api/querying/abstract_filter"
|
|
40
|
+
autoload :AbstractBooleanFilter, "pack_api/querying/abstract_boolean_filter"
|
|
41
|
+
autoload :AbstractEnumFilter, "pack_api/querying/abstract_enum_filter"
|
|
42
|
+
autoload :AbstractNumericFilter, "pack_api/querying/abstract_numeric_filter"
|
|
43
|
+
autoload :AbstractRangeFilter, "pack_api/querying/abstract_range_filter"
|
|
44
|
+
autoload :DefaultFilter, "pack_api/querying/default_filter"
|
|
45
|
+
autoload :DiscoverableFilter, "pack_api/querying/discoverable_filter"
|
|
46
|
+
autoload :DynamicEnumFilter, "pack_api/querying/dynamic_enum_filter"
|
|
47
|
+
autoload :AttributeFilter, "pack_api/querying/attribute_filter"
|
|
48
|
+
autoload :AttributeFilterFactory, "pack_api/querying/attribute_filter_factory"
|
|
49
|
+
autoload :FilterFactory, "pack_api/querying/filter_factory"
|
|
50
|
+
autoload :ComposableQuery, "pack_api/querying/composable_query"
|
|
51
|
+
autoload :CollectionQuery, "pack_api/querying/collection_query"
|
|
52
|
+
autoload :SortHash, "pack_api/querying/sort_hash"
|
|
56
53
|
end
|
|
57
54
|
|
|
58
55
|
module Types
|
|
59
|
-
autoload :BaseType, "pack_api/
|
|
60
|
-
autoload :AggregateType, "pack_api/
|
|
61
|
-
autoload :GloballyIdentifiable, "pack_api/
|
|
62
|
-
autoload :Result, "pack_api/
|
|
63
|
-
autoload :CollectionResultMetadata, "pack_api/
|
|
64
|
-
autoload :FilterOption, "pack_api/
|
|
65
|
-
autoload :SimpleFilterDefinition, "pack_api/
|
|
66
|
-
autoload :BooleanFilterDefinition, "pack_api/
|
|
67
|
-
autoload :EnumFilterDefinition, "pack_api/
|
|
68
|
-
autoload :NumericFilterDefinition, "pack_api/
|
|
69
|
-
autoload :RangeFilterDefinition, "pack_api/
|
|
70
|
-
autoload :CustomFilterDefinition, "pack_api/
|
|
56
|
+
autoload :BaseType, "pack_api/types/base_type"
|
|
57
|
+
autoload :AggregateType, "pack_api/types/aggregate_type"
|
|
58
|
+
autoload :GloballyIdentifiable, "pack_api/types/globally_identifiable"
|
|
59
|
+
autoload :Result, "pack_api/types/result"
|
|
60
|
+
autoload :CollectionResultMetadata, "pack_api/types/collection_result_metadata"
|
|
61
|
+
autoload :FilterOption, "pack_api/types/filter_option"
|
|
62
|
+
autoload :SimpleFilterDefinition, "pack_api/types/simple_filter_definition"
|
|
63
|
+
autoload :BooleanFilterDefinition, "pack_api/types/boolean_filter_definition"
|
|
64
|
+
autoload :EnumFilterDefinition, "pack_api/types/enum_filter_definition"
|
|
65
|
+
autoload :NumericFilterDefinition, "pack_api/types/numeric_filter_definition"
|
|
66
|
+
autoload :RangeFilterDefinition, "pack_api/types/range_filter_definition"
|
|
67
|
+
autoload :CustomFilterDefinition, "pack_api/types/custom_filter_definition"
|
|
71
68
|
end
|
|
72
|
-
end
|
|
69
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pack_api
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Flytedesk
|
|
@@ -185,13 +185,10 @@ dependencies:
|
|
|
185
185
|
- - "<"
|
|
186
186
|
- !ruby/object:Gem::Version
|
|
187
187
|
version: '9.0'
|
|
188
|
-
description:
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
- elements for building the mapping between domain models and API models
|
|
193
|
-
- elements for building the query endpoints themselves, based on user inputs (sort, filter, pagination)
|
|
194
|
-
- elements for retrieving multiple pages of data from other query endpoints
|
|
188
|
+
description: "Building blocks used to implement an API around a domain pack. Includes
|
|
189
|
+
tools for data transformation, \ndiscoverable filters, building ActiveRecord queries
|
|
190
|
+
from API arguments, breaking query results across pages, and\nfetching data from
|
|
191
|
+
API endpoints.\n"
|
|
195
192
|
email:
|
|
196
193
|
- dev@flytedesk.com
|
|
197
194
|
executables: []
|
|
@@ -202,52 +199,53 @@ files:
|
|
|
202
199
|
- LICENSE.txt
|
|
203
200
|
- README.md
|
|
204
201
|
- lib/pack_api.rb
|
|
205
|
-
- lib/pack_api/
|
|
206
|
-
- lib/pack_api/
|
|
207
|
-
- lib/pack_api/
|
|
208
|
-
- lib/pack_api/
|
|
209
|
-
- lib/pack_api/
|
|
210
|
-
- lib/pack_api/
|
|
211
|
-
- lib/pack_api/
|
|
212
|
-
- lib/pack_api/
|
|
213
|
-
- lib/pack_api/
|
|
214
|
-
- lib/pack_api/
|
|
215
|
-
- lib/pack_api/
|
|
216
|
-
- lib/pack_api/
|
|
217
|
-
- lib/pack_api/
|
|
218
|
-
- lib/pack_api/
|
|
219
|
-
- lib/pack_api/
|
|
220
|
-
- lib/pack_api/
|
|
221
|
-
- lib/pack_api/
|
|
222
|
-
- lib/pack_api/
|
|
223
|
-
- lib/pack_api/
|
|
224
|
-
- lib/pack_api/
|
|
225
|
-
- lib/pack_api/
|
|
226
|
-
- lib/pack_api/
|
|
227
|
-
- lib/pack_api/
|
|
228
|
-
- lib/pack_api/
|
|
229
|
-
- lib/pack_api/
|
|
230
|
-
- lib/pack_api/
|
|
231
|
-
- lib/pack_api/
|
|
232
|
-
- lib/pack_api/
|
|
233
|
-
- lib/pack_api/
|
|
234
|
-
- lib/pack_api/
|
|
235
|
-
- lib/pack_api/
|
|
236
|
-
- lib/pack_api/
|
|
237
|
-
- lib/pack_api/
|
|
238
|
-
- lib/pack_api/
|
|
239
|
-
- lib/pack_api/
|
|
240
|
-
- lib/pack_api/
|
|
241
|
-
- lib/pack_api/
|
|
242
|
-
- lib/pack_api/
|
|
243
|
-
- lib/pack_api/
|
|
244
|
-
- lib/pack_api/
|
|
245
|
-
- lib/pack_api/
|
|
246
|
-
- lib/pack_api/
|
|
247
|
-
- lib/pack_api/
|
|
248
|
-
- lib/pack_api/
|
|
249
|
-
- lib/pack_api/
|
|
250
|
-
- lib/pack_api/
|
|
202
|
+
- lib/pack_api/internal_error.rb
|
|
203
|
+
- lib/pack_api/mapping/abstract_transformer.rb
|
|
204
|
+
- lib/pack_api/mapping/api_to_model_attributes_transformer.rb
|
|
205
|
+
- lib/pack_api/mapping/attribute_hash_transformer.rb
|
|
206
|
+
- lib/pack_api/mapping/attribute_map.rb
|
|
207
|
+
- lib/pack_api/mapping/attribute_map_registry.rb
|
|
208
|
+
- lib/pack_api/mapping/error_hash_to_api_attributes_transformer.rb
|
|
209
|
+
- lib/pack_api/mapping/filter_map.rb
|
|
210
|
+
- lib/pack_api/mapping/model_to_api_attributes_transformer.rb
|
|
211
|
+
- lib/pack_api/mapping/normalized_api_attribute.rb
|
|
212
|
+
- lib/pack_api/mapping/null_transformer.rb
|
|
213
|
+
- lib/pack_api/mapping/value_object_factory.rb
|
|
214
|
+
- lib/pack_api/pagination/opaque_token_v2.rb
|
|
215
|
+
- lib/pack_api/pagination/paginator.rb
|
|
216
|
+
- lib/pack_api/pagination/paginator_builder.rb
|
|
217
|
+
- lib/pack_api/pagination/paginator_cursor.rb
|
|
218
|
+
- lib/pack_api/pagination/snapshot_paginator.rb
|
|
219
|
+
- lib/pack_api/querying/abstract_boolean_filter.rb
|
|
220
|
+
- lib/pack_api/querying/abstract_enum_filter.rb
|
|
221
|
+
- lib/pack_api/querying/abstract_filter.rb
|
|
222
|
+
- lib/pack_api/querying/abstract_numeric_filter.rb
|
|
223
|
+
- lib/pack_api/querying/abstract_range_filter.rb
|
|
224
|
+
- lib/pack_api/querying/attribute_filter.rb
|
|
225
|
+
- lib/pack_api/querying/attribute_filter_factory.rb
|
|
226
|
+
- lib/pack_api/querying/collection_query.rb
|
|
227
|
+
- lib/pack_api/querying/composable_query.rb
|
|
228
|
+
- lib/pack_api/querying/default_filter.rb
|
|
229
|
+
- lib/pack_api/querying/discoverable_filter.rb
|
|
230
|
+
- lib/pack_api/querying/dynamic_enum_filter.rb
|
|
231
|
+
- lib/pack_api/querying/filter_factory.rb
|
|
232
|
+
- lib/pack_api/querying/sort_hash.rb
|
|
233
|
+
- lib/pack_api/rspec/shared_examples_for_api_query_methods.rb
|
|
234
|
+
- lib/pack_api/rspec/shared_examples_for_paginated_results.rb
|
|
235
|
+
- lib/pack_api/types/aggregate_type.rb
|
|
236
|
+
- lib/pack_api/types/base_type.rb
|
|
237
|
+
- lib/pack_api/types/boolean_filter_definition.rb
|
|
238
|
+
- lib/pack_api/types/collection_result_metadata.rb
|
|
239
|
+
- lib/pack_api/types/custom_filter_definition.rb
|
|
240
|
+
- lib/pack_api/types/enum_filter_definition.rb
|
|
241
|
+
- lib/pack_api/types/filter_option.rb
|
|
242
|
+
- lib/pack_api/types/globally_identifiable.rb
|
|
243
|
+
- lib/pack_api/types/numeric_filter_definition.rb
|
|
244
|
+
- lib/pack_api/types/range_filter_definition.rb
|
|
245
|
+
- lib/pack_api/types/result.rb
|
|
246
|
+
- lib/pack_api/types/simple_filter_definition.rb
|
|
247
|
+
- lib/pack_api/values_in_background_batches.rb
|
|
248
|
+
- lib/pack_api/values_in_batches.rb
|
|
251
249
|
- lib/pack_api/version.rb
|
|
252
250
|
- lib/types.rb
|
|
253
251
|
homepage: https://github.com/flytedesk/pack_api
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
require_relative '../../types'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/data/lib/pack_api/{models/values_in_background_batches.rb → values_in_background_batches.rb}
RENAMED
|
File without changes
|
|
File without changes
|