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 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