fulfil_api 0.1.5 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|