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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cb30711ccea7942e991ecf77dcc3c25290752c09d83674c822c018d4d8c7fcde
4
- data.tar.gz: 82e58d3494b0b0a8d73d7ff3c4bd2d96fb79b599f3588bda3b1c4035fd22fcf0
3
+ metadata.gz: dc3d42e014ce01e2bca045883fcbe4b43a2f701fafd6c4252c7b06c1a9314acf
4
+ data.tar.gz: e6e79edf8b831515f3a706358c5884384b1f925b150443b1627ae33905ead5c6
5
5
  SHA512:
6
- metadata.gz: 4f2ac1b0f4d06d7d55cbf0a2f69cf5fd566ad514439a983270d0f14e1235c38a2c258b3f97dccd4589c81d4f1942309fa4eae5ce9e5c719b29ee104555e2692a
7
- data.tar.gz: ca9fd568544fb12b16d8a656667c7ca09d8c1583d8e29324fa8d9ed924a5d380fee2ff7fd7a435b987f744c561a278bea657f5f538331a461c8288265c7f4430
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
@@ -7,15 +7,21 @@ module FulfilApi
7
7
  include AttributeAssignable
8
8
  include Persistable
9
9
 
10
- class ModelNameMissing < Error; end
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::Resource::Relation]
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: }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FulfilApi
4
- VERSION = "0.1.5"
4
+ VERSION = "0.2.0"
5
5
  end
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.1.5
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-06 00:00:00.000000000 Z
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