fulfil_api 0.1.5 → 0.2.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/lib/fulfil_api/relation/batchable.rb +68 -0
- data/lib/fulfil_api/relation/countable.rb +45 -0
- data/lib/fulfil_api/relation/loadable.rb +59 -0
- data/lib/fulfil_api/relation/naming.rb +24 -0
- data/lib/fulfil_api/relation/query_methods.rb +100 -0
- data/lib/fulfil_api/relation.rb +67 -0
- data/lib/fulfil_api/resource.rb +10 -4
- data/lib/fulfil_api/test_helper.rb +2 -0
- data/lib/fulfil_api/version.rb +1 -1
- metadata +8 -6
- data/lib/fulfil_api/resource/relation/loadable.rb +0 -57
- data/lib/fulfil_api/resource/relation/naming.rb +0 -32
- data/lib/fulfil_api/resource/relation/query_methods.rb +0 -89
- data/lib/fulfil_api/resource/relation.rb +0 -62
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dc3d42e014ce01e2bca045883fcbe4b43a2f701fafd6c4252c7b06c1a9314acf
|
4
|
+
data.tar.gz: e6e79edf8b831515f3a706358c5884384b1f925b150443b1627ae33905ead5c6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8694733643edbf5caa327f42fb5121b4557869358afaf916c4cdfe30e2a7854ba70b189b24d8f1886b41054f1eb36aa6ffc1bafbc4c378e0b2276fa4513aaebc
|
7
|
+
data.tar.gz: ef3818c50ac4a8e09660a690a7d3a97e0775111ccbd1f35825969ab66ee1cb4003e5c2aec2f2b26ef5902a0e1ad07a5fb90bda99ad4b6c94b7bff4a8b57dbcc3
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FulfilApi
|
4
|
+
class Relation
|
5
|
+
# The {FulfilApi::Relation::Batchable} module includes a set of
|
6
|
+
# helper/query methods that queries resources in Fulfil in batches.
|
7
|
+
module Batchable
|
8
|
+
# The {#find_each} is a shorthand for iterating over individual API resources
|
9
|
+
# in a memory effective way.
|
10
|
+
#
|
11
|
+
# Under the hood, it uses the {#in_batches} to find API resources in batches
|
12
|
+
# and process them efficiently.
|
13
|
+
#
|
14
|
+
# @example find all resources
|
15
|
+
#
|
16
|
+
# FulfilApi::Resource.set(model_name: "sale.sale").find_each do |sales_order|
|
17
|
+
# process_sales_order(sales_order)
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# @param batch_size [Integer] The default batch forwarded to the {#in_batches} method.
|
21
|
+
# @yield [FulfilApi::Resource] An individual API resource.
|
22
|
+
# @return [FulfilApi::Relation]
|
23
|
+
def find_each(batch_size: 500, &block)
|
24
|
+
in_batches(of: batch_size) do |batch|
|
25
|
+
batch.each(&block)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Finds API resources in batches. Defaults to the maximum number of resources
|
30
|
+
# Fulfil's API endpoints will return (500 resources).
|
31
|
+
#
|
32
|
+
# @example find resources in batches of 10.
|
33
|
+
#
|
34
|
+
# FulfilApi::Resource.set(model_name: "sale.sale").in_batches(of: 10) do |batch|
|
35
|
+
# batch.each do |sales_order|
|
36
|
+
# process_sales_order(sales_order)
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# @note the {#in_batches} automatically retries when it encounters a 429
|
41
|
+
# (TooManyRequests) HTTP error to ensure the lookup can be completed.
|
42
|
+
#
|
43
|
+
# @param of [Integer] The maximum number of resources in a batch.
|
44
|
+
# @yield [FulfilApi::Relation] Yields FulfilApi::Relation
|
45
|
+
# objects to work with a batch of records.
|
46
|
+
# @return [FulfilApi::Relation]
|
47
|
+
def in_batches(of: 500) # rubocop:disable Metrics/MethodLength
|
48
|
+
current_offset = request_offset.presence || 0
|
49
|
+
batch_size = of
|
50
|
+
|
51
|
+
loop do
|
52
|
+
batch_relation = dup.offset(current_offset * batch_size).limit(batch_size)
|
53
|
+
batch_relation.load
|
54
|
+
|
55
|
+
yield(batch_relation)
|
56
|
+
|
57
|
+
break unless batch_relation.size == batch_size
|
58
|
+
|
59
|
+
current_offset += 1
|
60
|
+
rescue FulfilApi::Error => e
|
61
|
+
retry if e.details[:response_status] == 429
|
62
|
+
end
|
63
|
+
|
64
|
+
self
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FulfilApi
|
4
|
+
class Relation
|
5
|
+
# The {FulfilApi::Relation::Countable} extends the relation by
|
6
|
+
# adding a method to count the records within a given context.
|
7
|
+
module Countable
|
8
|
+
# Finds the exact number of API resources in Fulfil.
|
9
|
+
#
|
10
|
+
# @note It takes into account the query conditions and can be used to find
|
11
|
+
# the count of a subset of resources.
|
12
|
+
#
|
13
|
+
# @note The {#count} directly triggers an HTTP request to Fulfil but the
|
14
|
+
# return value is cached. When you want to recount, use {#recount}.
|
15
|
+
#
|
16
|
+
# @return [Integer]
|
17
|
+
def count
|
18
|
+
raise FulfilApi::Resource::ModelNameMissing if model_name.nil?
|
19
|
+
|
20
|
+
@count ||= FulfilApi.client.put(
|
21
|
+
"/model/#{model_name}/search_count",
|
22
|
+
body: { filters: conditions }.compact_blank
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Checks if the relation has already been counted.
|
27
|
+
#
|
28
|
+
# @return [true, false]
|
29
|
+
def counted?
|
30
|
+
@count
|
31
|
+
end
|
32
|
+
|
33
|
+
# Recounts the exact number of API resources in Fulfil.
|
34
|
+
#
|
35
|
+
# @note Under the hood, it uses the {#count} method by resetting the cached
|
36
|
+
# value and calling {#count} again.
|
37
|
+
#
|
38
|
+
# @return [Integer]
|
39
|
+
def recount
|
40
|
+
@count = nil
|
41
|
+
count
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FulfilApi
|
4
|
+
class Relation
|
5
|
+
# The {FulfilApi::Relation::Loadable} extends the relation by
|
6
|
+
# adding methods to load, reload and identify loaded resources from Fulfil's
|
7
|
+
# API endpoints.
|
8
|
+
#
|
9
|
+
# By default, all HTTP requests to Fulfil are delayed until they're directly
|
10
|
+
# or indirectly requested by the user of the gem. This way, we ensure that
|
11
|
+
# we only request data when we need to.
|
12
|
+
module Loadable
|
13
|
+
# Loads resources from Fulfil's API based on the current filters, fields,
|
14
|
+
# offset, and limits if they haven't been loaded yet.
|
15
|
+
#
|
16
|
+
# Requires that {#model_name} is set; raises an exception if it's not.
|
17
|
+
#
|
18
|
+
# @return [true, false] True if the resources were loaded successfully.
|
19
|
+
def load # rubocop:disable Metrics/MethodLength
|
20
|
+
return true if loaded?
|
21
|
+
raise FulfilApi::Resource::ModelNameMissing if model_name.nil?
|
22
|
+
|
23
|
+
response = FulfilApi.client.put(
|
24
|
+
"/model/#{model_name}/search_read",
|
25
|
+
body: {
|
26
|
+
filters: conditions,
|
27
|
+
fields: fields,
|
28
|
+
limit: request_limit,
|
29
|
+
offset: request_offset
|
30
|
+
}.compact_blank
|
31
|
+
)
|
32
|
+
|
33
|
+
# NOTE: The /search_read endpoint will always be an array. Therefore, we're
|
34
|
+
# always looping over the response values.
|
35
|
+
@resources = response.map do |attributes|
|
36
|
+
@resource_klass.new(attributes.merge(model_name: model_name))
|
37
|
+
end
|
38
|
+
|
39
|
+
@loaded = true
|
40
|
+
end
|
41
|
+
|
42
|
+
# Checks whether the resources have been loaded to avoid repeated API calls when
|
43
|
+
# using enumerable methods.
|
44
|
+
#
|
45
|
+
# @return [true, false] True if the resources are already loaded.
|
46
|
+
def loaded?
|
47
|
+
@loaded
|
48
|
+
end
|
49
|
+
|
50
|
+
# Reloads the resources from Fulfil's API by resetting the {@loaded} flag.
|
51
|
+
#
|
52
|
+
# @return [true, false] True if the resources were successfully reloaded.
|
53
|
+
def reload
|
54
|
+
@loaded = false
|
55
|
+
load
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FulfilApi
|
4
|
+
class Relation
|
5
|
+
# The {FulfilApi::Relation::Naming} extends the relation by
|
6
|
+
# adding methods to it that allow us to identify the type of resource that
|
7
|
+
# is being requested.
|
8
|
+
module Naming
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
# Sets the name of the resource model to be queried.
|
12
|
+
#
|
13
|
+
# @todo In the future, derive the {#name} from the @resource_klass automatically.
|
14
|
+
#
|
15
|
+
# @param model_name [String] The name of the resource model in Fulfil.
|
16
|
+
# @return [FulfilApi::Relation] A new {Relation} instance with the model name set.
|
17
|
+
def set(model_name:)
|
18
|
+
clone.tap do |relation|
|
19
|
+
relation.model_name = model_name
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FulfilApi
|
4
|
+
class Relation
|
5
|
+
# The {FulfilApi::Relation::QueryMethods} extends the relation by
|
6
|
+
# adding query methods to it.
|
7
|
+
module QueryMethods
|
8
|
+
# Finds the first resource that matches the given conditions.
|
9
|
+
#
|
10
|
+
# It constructs a query using the `where` method, limits the result to one record,
|
11
|
+
# and then returns the first result.
|
12
|
+
#
|
13
|
+
# @note Unlike the other methods in this module, `#find_by` will immediately trigger an
|
14
|
+
# HTTP request to retrieve the resource, rather than allowing for lazy evaluation.
|
15
|
+
#
|
16
|
+
# @param conditions [Array<String, String, String>] The filter conditions as required by Fulfil.
|
17
|
+
# @return [FulfilApi::Resource, nil] The first resource that matches the conditions,
|
18
|
+
# or nil if no match is found.
|
19
|
+
def find_by(conditions)
|
20
|
+
where(conditions).limit(1).first
|
21
|
+
end
|
22
|
+
|
23
|
+
# Finds the first resource that matches the given conditions and raises
|
24
|
+
# when no resource is found.
|
25
|
+
#
|
26
|
+
# @see .find_by
|
27
|
+
#
|
28
|
+
# @param conditions [Array<String, String, String>] The filter conditions as required by Fulfil.
|
29
|
+
# @return [FulfilApi::Resource] The first resource that matches the conditions
|
30
|
+
# @raise [FulfilApi::Resource::NotFound]
|
31
|
+
def find_by!(conditions)
|
32
|
+
find_by(conditions) || raise(FulfilApi::Resource::NotFound, "Unable to find #{model_name} where #{conditions}")
|
33
|
+
end
|
34
|
+
|
35
|
+
# Limits the number of resources returned by Fulfil's API. This is useful when only
|
36
|
+
# a specific number of resources are needed.
|
37
|
+
#
|
38
|
+
# @note If not specified, Fulfil will assume a request limit of 500.
|
39
|
+
#
|
40
|
+
# @param value [Integer] The maximum number of resources to return.
|
41
|
+
# @return [FulfilApi::Relation] A new {Relation} instance with the limit applied.
|
42
|
+
def limit(value)
|
43
|
+
clone.tap do |relation|
|
44
|
+
relation.request_limit = value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Applies an offset to the API resources returned by Fulfil's API.
|
49
|
+
# This is useful when paginating over larger lists of API resources.
|
50
|
+
#
|
51
|
+
# @note If not specified, Fulfil will assume a request offset of 0.
|
52
|
+
#
|
53
|
+
# @param value [Integer] The page offset for the API request.
|
54
|
+
# @return [FulfilApi::Relation] A new {Relation} instance with the offset applied.
|
55
|
+
def offset(value)
|
56
|
+
clone.tap do |relation|
|
57
|
+
relation.request_offset = value
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Specifies the fields to include in the response from Fulfil's API. By default, only
|
62
|
+
# the ID is returned.
|
63
|
+
#
|
64
|
+
# Supports dot notation for nested data fields, though not all nested data may be available
|
65
|
+
# depending on the API's limitations.
|
66
|
+
#
|
67
|
+
# @example Requesting nested data fields
|
68
|
+
# FulfilApi::Resource.set(model_name: "sale.line").select("sale.reference").find_by(["id", "=", 10])
|
69
|
+
#
|
70
|
+
# @example Requesting additional fields
|
71
|
+
# FulfilApi::Resource.set(model_name: "sale.sale").select(:reference).find_by(["id", "=", 10])
|
72
|
+
#
|
73
|
+
# @param fields [Array<Symbol, String>] The fields to include in the response.
|
74
|
+
# @return [FulfilApi::Relation] A new {Relation} instance with the selected fields.
|
75
|
+
def select(*fields)
|
76
|
+
clone.tap do |relation|
|
77
|
+
relation.fields.concat(fields.map(&:to_s))
|
78
|
+
relation.fields.uniq!
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Adds filter conditions for querying Fulfil's API. Conditions should be formatted
|
83
|
+
# as arrays according to the Fulfil API documentation.
|
84
|
+
#
|
85
|
+
# @example Simple querying with conditions
|
86
|
+
# FulfilApi::Resource.set(model_name: "sale.line").where(["sale.reference", "=", "ORDER-123"])
|
87
|
+
#
|
88
|
+
# @todo Enhance the {#where} method to allow more natural and flexible queries.
|
89
|
+
#
|
90
|
+
# @param conditions [Array<String, String, String>] The filter conditions as required by Fulfil.
|
91
|
+
# @return [FulfilApi::Relation] A new {Relation} instance with the conditions applied.
|
92
|
+
def where(conditions)
|
93
|
+
clone.tap do |relation|
|
94
|
+
relation.conditions << conditions
|
95
|
+
relation.conditions.uniq!
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FulfilApi
|
4
|
+
# The {FulfilApi::Relation} class provides an abstraction for chaining multiple API operations.
|
5
|
+
#
|
6
|
+
# It allows handling a set of API resources in a uniform way, similar to
|
7
|
+
# ActiveRecord's query interface, enabling the user to build complex queries
|
8
|
+
# in a clean and reusable manner.
|
9
|
+
class Relation
|
10
|
+
include Enumerable
|
11
|
+
|
12
|
+
# Insert the {FulfilApi::Relation} modules after the inclusion of
|
13
|
+
# standard Ruby module extensions. This ensures our modules win when there
|
14
|
+
# is any conflicting method. An example of this is the {#count} method.
|
15
|
+
include Batchable
|
16
|
+
include Countable
|
17
|
+
include Loadable
|
18
|
+
include Naming
|
19
|
+
include QueryMethods
|
20
|
+
|
21
|
+
attr_accessor :conditions, :fields, :model_name, :request_limit, :request_offset
|
22
|
+
|
23
|
+
delegate_missing_to :all
|
24
|
+
|
25
|
+
# @param resource_klass [FulfilApi::Resource] The resource data model class.
|
26
|
+
def initialize(resource_klass)
|
27
|
+
@resource_klass = resource_klass
|
28
|
+
|
29
|
+
@loaded = false
|
30
|
+
@resources = []
|
31
|
+
|
32
|
+
reset
|
33
|
+
end
|
34
|
+
|
35
|
+
# Loads and returns all resources from Fulfil's API. This method functions as a proxy,
|
36
|
+
# deferring the loading of resources until they are required, thus avoiding unnecessary
|
37
|
+
# HTTP requests.
|
38
|
+
#
|
39
|
+
# @return [Array<FulfilApi::Resource>] An array of loaded resource objects.
|
40
|
+
def all
|
41
|
+
load
|
42
|
+
@resources
|
43
|
+
end
|
44
|
+
|
45
|
+
# The {#each} method allows iteration over the resources. If no block is given,
|
46
|
+
# it returns an Enumerator, enabling lazy evaluation and allowing for chaining
|
47
|
+
# without immediately triggering an API request.
|
48
|
+
#
|
49
|
+
# @yield [resource] Yields each resource object to the given block.
|
50
|
+
# @return [Enumerator, self] Returns an Enumerator if no block is given; otherwise, returns self.
|
51
|
+
def each(&block)
|
52
|
+
all.each(&block)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Resets any of the previously provided query conditions.
|
56
|
+
#
|
57
|
+
# @return [FulfilApi::Relation] The relation with cleared query conditions.
|
58
|
+
def reset
|
59
|
+
@conditions = []
|
60
|
+
@fields = %w[id]
|
61
|
+
@request_limit = nil
|
62
|
+
@request_offset = nil
|
63
|
+
|
64
|
+
self
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/fulfil_api/resource.rb
CHANGED
@@ -7,15 +7,21 @@ module FulfilApi
|
|
7
7
|
include AttributeAssignable
|
8
8
|
include Persistable
|
9
9
|
|
10
|
-
|
10
|
+
# The model name is required to be able to build the API endpoint to
|
11
|
+
# perform the search/read/count HTTP requests.
|
12
|
+
class ModelNameMissing < Error
|
13
|
+
def initialize
|
14
|
+
super("The model name is missing. Use #set to define it.")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
11
18
|
class NotFound < Error; end
|
12
19
|
|
13
20
|
def initialize(attributes = {})
|
14
21
|
attributes.deep_stringify_keys!
|
15
22
|
|
16
23
|
@attributes = {}.with_indifferent_access
|
17
|
-
@model_name = attributes.delete("model_name").presence ||
|
18
|
-
raise(ModelNameMissing, "The model name is missing. Use the :model_name attribute to define it.")
|
24
|
+
@model_name = attributes.delete("model_name").presence || raise(ModelNameMissing)
|
19
25
|
|
20
26
|
assign_attributes(attributes)
|
21
27
|
end
|
@@ -34,7 +40,7 @@ module FulfilApi
|
|
34
40
|
# @example forwarding of the .where class method
|
35
41
|
# FulfilApi::Resource.set(model_name: "sale.sale").find_by(["id", "=", 100])
|
36
42
|
#
|
37
|
-
# @return [FulfilApi::
|
43
|
+
# @return [FulfilApi::Relation]
|
38
44
|
def relation
|
39
45
|
Relation.new(self)
|
40
46
|
end
|
@@ -59,6 +59,8 @@ module FulfilApi
|
|
59
59
|
# stubbed_request_for(:get, model: "product.product", id: "123")
|
60
60
|
def stubbed_request_for(method, **options)
|
61
61
|
case options.transform_keys(&:to_sym)
|
62
|
+
in { model:, suffix: }
|
63
|
+
stub_request(method.to_sym, %r{fulfil.io/api/v\d+/(?:model/)?#{model}/#{suffix}}i)
|
62
64
|
in { model:, id: }
|
63
65
|
stub_request(method.to_sym, %r{fulfil.io/api/v\d+/(?:model/)?#{model}/#{id}(.*)}i)
|
64
66
|
in { model: }
|
data/lib/fulfil_api/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fulfil_api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stefan Vermaas
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-12-
|
11
|
+
date: 2024-12-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -86,15 +86,17 @@ files:
|
|
86
86
|
- lib/fulfil_api/configuration.rb
|
87
87
|
- lib/fulfil_api/customer_shipment.rb
|
88
88
|
- lib/fulfil_api/error.rb
|
89
|
+
- lib/fulfil_api/relation.rb
|
90
|
+
- lib/fulfil_api/relation/batchable.rb
|
91
|
+
- lib/fulfil_api/relation/countable.rb
|
92
|
+
- lib/fulfil_api/relation/loadable.rb
|
93
|
+
- lib/fulfil_api/relation/naming.rb
|
94
|
+
- lib/fulfil_api/relation/query_methods.rb
|
89
95
|
- lib/fulfil_api/resource.rb
|
90
96
|
- lib/fulfil_api/resource/attribute_assignable.rb
|
91
97
|
- lib/fulfil_api/resource/attribute_type.rb
|
92
98
|
- lib/fulfil_api/resource/errors.rb
|
93
99
|
- lib/fulfil_api/resource/persistable.rb
|
94
|
-
- lib/fulfil_api/resource/relation.rb
|
95
|
-
- lib/fulfil_api/resource/relation/loadable.rb
|
96
|
-
- lib/fulfil_api/resource/relation/naming.rb
|
97
|
-
- lib/fulfil_api/resource/relation/query_methods.rb
|
98
100
|
- lib/fulfil_api/test_helper.rb
|
99
101
|
- lib/fulfil_api/version.rb
|
100
102
|
- sig/fulfil_api.rbs
|
@@ -1,57 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module FulfilApi
|
4
|
-
class Resource
|
5
|
-
class Relation
|
6
|
-
# The {FulfilApi::Resource::Relation::Loadable} extends the relation by
|
7
|
-
# adding methods to load, reload and identify loaded resources from Fulfil's
|
8
|
-
# API endpoints.
|
9
|
-
#
|
10
|
-
# By default, all HTTP requests to Fulfil are delayed until they're directly
|
11
|
-
# or indirectly requested by the user of the gem. This way, we ensure that
|
12
|
-
# we only request data when we need to.
|
13
|
-
module Loadable
|
14
|
-
# Loads resources from Fulfil's API based on the current filters, fields, and limits
|
15
|
-
# if they haven't been loaded yet.
|
16
|
-
#
|
17
|
-
# Requires that {#model_name} is set; raises an exception if it's not.
|
18
|
-
#
|
19
|
-
# @return [true, false] True if the resources were loaded successfully.
|
20
|
-
def load # rubocop:disable Metrics/MethodLength
|
21
|
-
return true if loaded?
|
22
|
-
|
23
|
-
if model_name.nil?
|
24
|
-
raise FulfilApi::Resource::Relation::ModelNameMissing, "The model name is missing. Use #set to define it."
|
25
|
-
end
|
26
|
-
|
27
|
-
response = FulfilApi.client.put(
|
28
|
-
"/model/#{model_name}/search_read",
|
29
|
-
body: { filters: conditions, fields: fields, limit: request_limit }.compact_blank
|
30
|
-
)
|
31
|
-
|
32
|
-
@resources = response.map do |attributes|
|
33
|
-
@resource_klass.new(attributes.merge(model_name: model_name))
|
34
|
-
end
|
35
|
-
|
36
|
-
@loaded = true
|
37
|
-
end
|
38
|
-
|
39
|
-
# Checks whether the resources have been loaded to avoid repeated API calls when
|
40
|
-
# using enumerable methods.
|
41
|
-
#
|
42
|
-
# @return [true, false] True if the resources are already loaded.
|
43
|
-
def loaded?
|
44
|
-
@loaded
|
45
|
-
end
|
46
|
-
|
47
|
-
# Reloads the resources from Fulfil's API by resetting the {@loaded} flag.
|
48
|
-
#
|
49
|
-
# @return [true, false] True if the resources were successfully reloaded.
|
50
|
-
def reload
|
51
|
-
@loaded = false
|
52
|
-
load
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
@@ -1,32 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module FulfilApi
|
4
|
-
class Resource
|
5
|
-
class Relation
|
6
|
-
# The {FulfilApi::Resource::Relation::Naming} extends the relation by
|
7
|
-
# adding methods to it that allow us to identify the type of resource that
|
8
|
-
# is being requested.
|
9
|
-
module Naming
|
10
|
-
extend ActiveSupport::Concern
|
11
|
-
|
12
|
-
included do
|
13
|
-
# Custom error class for missing model name. The model name is required to be
|
14
|
-
# able to build the API endpoint to perform the search/read HTTP request.
|
15
|
-
class ModelNameMissing < Error; end # rubocop:disable Lint/ConstantDefinitionInBlock
|
16
|
-
end
|
17
|
-
|
18
|
-
# Sets the name of the resource model to be queried.
|
19
|
-
#
|
20
|
-
# @todo In the future, derive the {#name} from the @resource_klass automatically.
|
21
|
-
#
|
22
|
-
# @param model_name [String] The name of the resource model in Fulfil.
|
23
|
-
# @return [FulfilApi::Resource::Relation] A new {Relation} instance with the model name set.
|
24
|
-
def set(model_name:)
|
25
|
-
clone.tap do |relation|
|
26
|
-
relation.model_name = model_name
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
@@ -1,89 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module FulfilApi
|
4
|
-
class Resource
|
5
|
-
class Relation
|
6
|
-
# The {FulfilApi::Resource::Relation::QueryMethods} extends the relation by
|
7
|
-
# adding query methods to it.
|
8
|
-
module QueryMethods
|
9
|
-
# Finds the first resource that matches the given conditions.
|
10
|
-
#
|
11
|
-
# It constructs a query using the `where` method, limits the result to one record,
|
12
|
-
# and then returns the first result.
|
13
|
-
#
|
14
|
-
# @note Unlike the other methods in this module, `#find_by` will immediately trigger an
|
15
|
-
# HTTP request to retrieve the resource, rather than allowing for lazy evaluation.
|
16
|
-
#
|
17
|
-
# @param conditions [Array<String, String, String>] The filter conditions as required by Fulfil.
|
18
|
-
# @return [FulfilApi::Resource, nil] The first resource that matches the conditions,
|
19
|
-
# or nil if no match is found.
|
20
|
-
def find_by(conditions)
|
21
|
-
where(conditions).limit(1).first
|
22
|
-
end
|
23
|
-
|
24
|
-
# Finds the first resource that matches the given conditions and raises
|
25
|
-
# when no resource is found.
|
26
|
-
#
|
27
|
-
# @see .find_by
|
28
|
-
#
|
29
|
-
# @param conditions [Array<String, String, String>] The filter conditions as required by Fulfil.
|
30
|
-
# @return [FulfilApi::Resource] The first resource that matches the conditions
|
31
|
-
# @raise [FulfilApi::Resource::NotFound]
|
32
|
-
def find_by!(conditions)
|
33
|
-
find_by(conditions) || raise(NotFound, "Unable to find #{model_name} where #{conditions}")
|
34
|
-
end
|
35
|
-
|
36
|
-
# Limits the number of resources returned by Fulfil's API. This is useful when only
|
37
|
-
# a specific number of resources are needed.
|
38
|
-
#
|
39
|
-
# @note If not specified, Fulfil's API defaults to returning up to 500 resources per call.
|
40
|
-
#
|
41
|
-
# @param value [Integer] The maximum number of resources to return.
|
42
|
-
# @return [FulfilApi::Resource::Relation] A new {Relation} instance with the limit applied.
|
43
|
-
def limit(value)
|
44
|
-
clone.tap do |relation|
|
45
|
-
relation.request_limit = value
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
# Specifies the fields to include in the response from Fulfil's API. By default, only
|
50
|
-
# the ID is returned.
|
51
|
-
#
|
52
|
-
# Supports dot notation for nested data fields, though not all nested data may be available
|
53
|
-
# depending on the API's limitations.
|
54
|
-
#
|
55
|
-
# @example Requesting nested data fields
|
56
|
-
# FulfilApi::Resource.set(model_name: "sale.line").select("sale.reference").find_by(["id", "=", 10])
|
57
|
-
#
|
58
|
-
# @example Requesting additional fields
|
59
|
-
# FulfilApi::Resource.set(model_name: "sale.sale").select(:reference).find_by(["id", "=", 10])
|
60
|
-
#
|
61
|
-
# @param fields [Array<Symbol, String>] The fields to include in the response.
|
62
|
-
# @return [FulfilApi::Resource::Relation] A new {Relation} instance with the selected fields.
|
63
|
-
def select(*fields)
|
64
|
-
clone.tap do |relation|
|
65
|
-
relation.fields.concat(fields.map(&:to_s))
|
66
|
-
relation.fields.uniq!
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
# Adds filter conditions for querying Fulfil's API. Conditions should be formatted
|
71
|
-
# as arrays according to the Fulfil API documentation.
|
72
|
-
#
|
73
|
-
# @example Simple querying with conditions
|
74
|
-
# FulfilApi::Resource.set(model_name: "sale.line").where(["sale.reference", "=", "ORDER-123"])
|
75
|
-
#
|
76
|
-
# @todo Enhance the {#where} method to allow more natural and flexible queries.
|
77
|
-
#
|
78
|
-
# @param conditions [Array<String, String, String>] The filter conditions as required by Fulfil.
|
79
|
-
# @return [FulfilApi::Resource::Relation] A new {Relation} instance with the conditions applied.
|
80
|
-
def where(conditions)
|
81
|
-
clone.tap do |relation|
|
82
|
-
relation.conditions << conditions
|
83
|
-
relation.conditions.uniq!
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
@@ -1,62 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module FulfilApi
|
4
|
-
class Resource
|
5
|
-
# The {FulfilApi::Resource::Relation} class provides an abstraction for chaining multiple API operations.
|
6
|
-
#
|
7
|
-
# It allows handling a set of API resources in a uniform way, similar to
|
8
|
-
# ActiveRecord's query interface, enabling the user to build complex queries
|
9
|
-
# in a clean and reusable manner.
|
10
|
-
class Relation
|
11
|
-
include Enumerable
|
12
|
-
include Loadable
|
13
|
-
include Naming
|
14
|
-
include QueryMethods
|
15
|
-
|
16
|
-
attr_accessor :conditions, :fields, :model_name, :request_limit
|
17
|
-
|
18
|
-
delegate_missing_to :all
|
19
|
-
|
20
|
-
# @param resource_klass [FulfilApi::Resource] The resource data model class.
|
21
|
-
def initialize(resource_klass)
|
22
|
-
@resource_klass = resource_klass
|
23
|
-
|
24
|
-
@loaded = false
|
25
|
-
@resources = []
|
26
|
-
|
27
|
-
reset
|
28
|
-
end
|
29
|
-
|
30
|
-
# Loads and returns all resources from Fulfil's API. This method functions as a proxy,
|
31
|
-
# deferring the loading of resources until they are required, thus avoiding unnecessary
|
32
|
-
# HTTP requests.
|
33
|
-
#
|
34
|
-
# @return [Array<FulfilApi::Resource>] An array of loaded resource objects.
|
35
|
-
def all
|
36
|
-
load
|
37
|
-
@resources
|
38
|
-
end
|
39
|
-
|
40
|
-
# The {#each} method allows iteration over the resources. If no block is given,
|
41
|
-
# it returns an Enumerator, enabling lazy evaluation and allowing for chaining
|
42
|
-
# without immediately triggering an API request.
|
43
|
-
#
|
44
|
-
# @yield [resource] Yields each resource object to the given block.
|
45
|
-
# @return [Enumerator, self] Returns an Enumerator if no block is given; otherwise, returns self.
|
46
|
-
def each(&block)
|
47
|
-
all.each(&block)
|
48
|
-
end
|
49
|
-
|
50
|
-
# Resets any of the previously provided query conditions.
|
51
|
-
#
|
52
|
-
# @return [FulfilApi::Resource::Relation] The relation with cleared query conditions.
|
53
|
-
def reset
|
54
|
-
@conditions = []
|
55
|
-
@fields = %w[id]
|
56
|
-
@limit = nil
|
57
|
-
|
58
|
-
self
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|