e_plat 0.6.0 → 0.7.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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +84 -54
  3. data/lib/e_plat/client/default_request_args.rb +1 -1
  4. data/lib/e_plat/client.rb +34 -8
  5. data/lib/e_plat/mapping/bigcommerce/v_3/product/option.rb +58 -0
  6. data/lib/e_plat/mapping/bigcommerce/v_3/product/variant/option_value.rb +42 -0
  7. data/lib/e_plat/mapping/shopify/v_2024_07/metafield.rb +26 -0
  8. data/lib/e_plat/mapping/shopify/v_2024_07/order/billing_address.rb +14 -0
  9. data/lib/e_plat/mapping/shopify/v_2024_07/order/shipping_address.rb +30 -0
  10. data/lib/e_plat/mapping/shopify/v_2024_07/order.rb +26 -0
  11. data/lib/e_plat/mapping/shopify/v_2024_07/product/image.rb +52 -0
  12. data/lib/e_plat/mapping/shopify/v_2024_07/product/option.rb +45 -0
  13. data/lib/e_plat/mapping/shopify/v_2024_07/product/variant/option_value.rb +58 -0
  14. data/lib/e_plat/mapping/shopify/v_2024_07/product/variant.rb +180 -0
  15. data/lib/e_plat/mapping/shopify/v_2024_07/product.rb +100 -0
  16. data/lib/e_plat/mapping/shopify/v_2024_07/script_tag.rb +26 -0
  17. data/lib/e_plat/mapping/shopify/v_2024_07/shop.rb +26 -0
  18. data/lib/e_plat/mapping/shopify/v_2024_07/webhook.rb +29 -0
  19. data/lib/e_plat/mapping/virtual_collection/shopify/product/variant/option_value.rb +272 -0
  20. data/lib/e_plat/resource/base.rb +24 -2
  21. data/lib/e_plat/resource/collection.rb +45 -16
  22. data/lib/e_plat/resource/concerns/aliases.rb +39 -19
  23. data/lib/e_plat/resource/concerns/dirty.rb +12 -8
  24. data/lib/e_plat/resource/concerns/graph_q_lable.rb +253 -0
  25. data/lib/e_plat/resource/concerns/overwrite_instance_methods.rb +4 -4
  26. data/lib/e_plat/resource/concerns/overwrite_request_methods.rb +1 -1
  27. data/lib/e_plat/resource/countable.rb +37 -10
  28. data/lib/e_plat/resource/platform_specific/bigcommerce/product/variant/option_value.rb +2 -0
  29. data/lib/e_plat/resource/platform_specific/shopify/graph_q_l/v_2024_07/input/product/variant.rb +47 -0
  30. data/lib/e_plat/resource/platform_specific/shopify/graph_q_l/v_2024_07/input/product/variants_bulk.rb +47 -0
  31. data/lib/e_plat/resource/platform_specific/shopify/graph_q_l/v_2024_07/input/product.rb +23 -0
  32. data/lib/e_plat/resource/platform_specific/shopify/graph_q_l/v_2024_07/input.rb +98 -0
  33. data/lib/e_plat/resource/platform_specific/shopify/graph_q_l/v_2024_07/mutation/product/variant.rb +78 -0
  34. data/lib/e_plat/resource/platform_specific/shopify/graph_q_l/v_2024_07/mutation/product.rb +55 -0
  35. data/lib/e_plat/resource/platform_specific/shopify/graph_q_l/v_2024_07/mutation.rb +5 -0
  36. data/lib/e_plat/resource/platform_specific/shopify/graph_q_l/v_2024_07/query/product/variant.rb +44 -0
  37. data/lib/e_plat/resource/platform_specific/shopify/graph_q_l/v_2024_07/query/product.rb +58 -0
  38. data/lib/e_plat/resource/platform_specific/shopify/graph_q_l/v_2024_07/query.rb +5 -0
  39. data/lib/e_plat/resource/platform_specific/shopify/product/image.rb +13 -1
  40. data/lib/e_plat/resource/platform_specific/shopify/product/option.rb +13 -1
  41. data/lib/e_plat/resource/platform_specific/shopify/product/variant/option_value.rb +14 -0
  42. data/lib/e_plat/resource/platform_specific/shopify/product/variant.rb +65 -1
  43. data/lib/e_plat/resource/platform_specific/shopify/product.rb +77 -2
  44. data/lib/e_plat/resource/product/image.rb +8 -5
  45. data/lib/e_plat/resource/product/option.rb +2 -3
  46. data/lib/e_plat/resource/product/variant/option_value.rb +17 -0
  47. data/lib/e_plat/resource/product/variant.rb +4 -10
  48. data/lib/e_plat/resource/product.rb +23 -11
  49. data/lib/e_plat/session.rb +3 -2
  50. data/lib/e_plat/type_coercer.rb +3 -1
  51. data/lib/e_plat/version.rb +1 -1
  52. data/lib/e_plat.rb +12 -3
  53. data/lib/hash.rb +39 -0
  54. metadata +130 -27
@@ -2,41 +2,52 @@
2
2
 
3
3
  module EPlat
4
4
  class Collection < ActiveResource::Collection
5
+ SHOPIFY_GRAPHQL_PAGINATABLE_CLASSES = %w(
6
+ EPlat::Product
7
+ EPlat::Product::Variant
8
+ EPlat::Shopify::Product
9
+ EPlat::Shopify::Product::Variant
10
+ )
5
11
 
6
12
  def initialize(parsed = {})
7
13
  super parsed
8
14
  end
9
15
 
10
16
  def next_page?
11
- return unless pagination_available?
12
- @next_url.present?
17
+ return false unless pagination_available?
18
+ @next.present?
13
19
  end
14
20
 
15
21
  def previous_page?
16
- return unless pagination_available?
17
- @previous_url.present?
22
+ return false unless pagination_available?
23
+ @previous.present?
18
24
  end
19
25
 
20
26
  def next_page_info
21
27
  return unless pagination_available?
22
- extract_page_info(@next_url)
28
+ extract_page_info(@next)
23
29
  end
24
30
 
25
31
  def previous_page_info
26
32
  return unless pagination_available?
27
- extract_page_info(@previous_url)
33
+ extract_page_info(@previous)
28
34
  end
29
35
 
30
36
  def fetch_next_page
31
37
  return unless pagination_available?
32
- fetch_page(@next_url)
38
+ fetch_page(@next)
33
39
  end
34
40
 
35
41
  def fetch_previous_page
36
42
  return unless pagination_available?
37
- fetch_page(@previous_url)
43
+ fetch_page(@previous)
38
44
  end
45
+
46
+ def paginates_via_graphql?
47
+ return unless SHOPIFY_GRAPHQL_PAGINATABLE_CLASSES.include? resource_class.to_s
39
48
 
49
+ client.shopify? && client.api_version != "2024_01"
50
+ end
40
51
 
41
52
  private
42
53
 
@@ -44,10 +55,17 @@ module EPlat
44
55
  return [] unless url.present?
45
56
  url = "#{ resource_class.collection_path }#{ url }" if client.bigcommerce?
46
57
 
47
- resource_class.all(from: url)
58
+ if paginates_via_graphql?
59
+ arg_name = (url == @next) ? "after" : "before"
60
+ resource_class.find(:all, params: {arg_name => url})
61
+ else
62
+ resource_class.all(from: url)
63
+ end
48
64
  end
49
65
 
50
66
  def pagination_links
67
+ return pagination_cursors if paginates_via_graphql?
68
+
51
69
  case client.platform
52
70
  when :shopify
53
71
  @pagination_links ||= EPlat::Paginated::LinkHeaders.new(
@@ -67,17 +85,28 @@ module EPlat
67
85
  end
68
86
  end
69
87
 
70
- def pagination_available?
71
- @next_url = pagination_links&.next_link&.url&.to_s
72
- @previous_url = pagination_links&.previous_link&.url&.to_s
88
+ def pagination_cursors
89
+ response_body = JSON.parse(full_response.body)
90
+ page_info = response_body.dig("data", resource_class.graphql_collection_name, "pageInfo") || {}
91
+
92
+ earliest_cursor = page_info["hasPreviousPage"] ? page_info["startCursor"] : nil
93
+ latest_cursor = page_info["hasNextPage"] ? page_info["endCursor"] : nil
73
94
 
74
- @next_url.present? or @previous_url.present?
95
+ [earliest_cursor, latest_cursor]
96
+ end
97
+
98
+ def pagination_available?
99
+ @previous = (paginates_via_graphql?) ? pagination_cursors[0] : pagination_links&.previous_link&.url&.to_s
100
+ @next = (paginates_via_graphql?) ? pagination_cursors[1] : pagination_links&.next_link&.url&.to_s
101
+
102
+ @next.present? or @previous.present?
75
103
  end
76
104
 
77
- def extract_page_info(url)
78
- return unless url.present?
105
+ def extract_page_info(request_data)
106
+ return unless request_data.present?
107
+ return request_data if paginates_via_graphql?
79
108
 
80
- query_params = URI.decode_www_form(URI(url).query).to_h
109
+ query_params = URI.decode_www_form(URI(request_data).query).to_h
81
110
  query_params[client.pagination_param]
82
111
  end
83
112
 
@@ -8,33 +8,35 @@ module EPlat
8
8
 
9
9
  def add_aliases!(aliases, type_schema)
10
10
  @type_coercer = EPlat::TypeCoercer.new(type_schema)
11
- processed = []
11
+ processed = { getter: [], setter: [] }
12
12
 
13
13
  aliases.each do |action|
14
- action_name = action.keys.first
15
- args = action.values.first
16
- e_plat_key = args[:e_plat_key]
17
- native_key = args[:native_key]
18
- @is_virtual = args[:virtual_collection]
14
+ action_name, args = action.keys.first, action.values.first
15
+ e_plat_key, native_key = args[:e_plat_key], args[:native_key]
16
+ @is_virtual = !!args[:virtual_collection]
19
17
 
20
18
  case action_name
21
19
  when :alias_attribute
22
20
  add_to_instance! alias_mapped_getter(e_plat_key, native_key, proc: args[:custom_e_plat_getter])
23
21
  add_to_instance! alias_mapped_setter(e_plat_key, native_key, proc: args[:custom_native_setter])
24
- # puts alias_mapped_setter(e_plat_key, native_key, proc: args[:custom_native_setter]) if e_plat_key == 'variant_id'
25
- processed << e_plat_key
22
+ processed[:getter] << e_plat_key
23
+ processed[:setter] << e_plat_key
24
+ when :existing_entry
25
+ add_to_instance! mapped_getter(e_plat_key, proc: args[:custom_e_plat_getter])
26
+ add_to_instance! native_setter(native_key, proc: args[:custom_native_setter])
27
+ processed[:getter] << e_plat_key
28
+ processed[:setter] << native_key
26
29
  end
27
30
  end
28
31
 
29
32
  type_schema.each do |e_plat_key, type|
30
- next if processed.include?(e_plat_key)
33
+ next if processed[:getter].include?(e_plat_key)
31
34
  add_to_instance! mapped_getter(e_plat_key), @is_virtual
32
35
  end
33
36
 
34
37
  native_keys.each do |native_key|
35
- next if processed.include?(native_key)
36
-
37
- add_to_instance! native_setter(native_key), @is_virtual
38
+ add_to_instance!(mapped_getter(native_key), @is_virtual) unless processed[:getter].include?(native_key)
39
+ add_to_instance!(native_setter(native_key), @is_virtual) unless processed[:setter].include?(native_key)
38
40
  end
39
41
  end
40
42
 
@@ -42,7 +44,7 @@ module EPlat
42
44
  private
43
45
 
44
46
 
45
- def alias_mapped_getter(e_plat_key, native_key, proc: nil)
47
+ def alias_mapped_getter(e_plat_key, native_key, proc: '->{_1}')
46
48
  proc_line = (proc) ? "current_value = #{ proc.strip }.call(current_value)" : nil
47
49
 
48
50
  <<-STRING
@@ -54,7 +56,7 @@ module EPlat
54
56
  STRING
55
57
  end
56
58
 
57
- def alias_mapped_setter(e_plat_key, native_key, proc: nil)
59
+ def alias_mapped_setter(e_plat_key, native_key, proc: '->{_1}')
58
60
  proc_line = (proc) ? "new_value = #{ proc.strip }.call(new_value)" : nil
59
61
 
60
62
  <<-STRING
@@ -71,19 +73,28 @@ module EPlat
71
73
  STRING
72
74
  end
73
75
 
74
- def mapped_getter(e_plat_key)
76
+ def mapped_getter(e_plat_key, proc: '->{_1}')
77
+ proc_line = (proc) ? "value = #{ proc.strip }.call(value)" : nil
78
+
75
79
  <<-STRING
76
80
  def #{ e_plat_key }
77
- type_coercer.type_value!('#{ e_plat_key }', self.attributes['#{ e_plat_key }'])
81
+ value = self.attributes['#{ e_plat_key }']
82
+ #{ proc_line }
83
+ type_coercer.type_value!('#{ e_plat_key }', value)
78
84
  end
79
85
  STRING
80
86
  end
81
87
 
82
- def native_setter(native_key)
88
+ # we force the native_setter result into changed_attributes, incase the key is the same as the e_plat key
89
+ # we dont want the eplat version of the value in change_attributes, we want the native version.
90
+ def native_setter(native_key, proc: '->{_1}')
91
+ proc_line = (proc) ? "value = #{ proc.strip }.call(value)" : nil
92
+
83
93
  <<-STRING
84
94
  def #{native_key}=(value)
95
+ #{proc_line}
85
96
  super
86
- attribute_will_change!('#{native_key}')
97
+ attribute_will_change!('#{native_key}', force_value: value)
87
98
  end
88
99
  STRING
89
100
  end
@@ -136,8 +147,17 @@ module EPlat
136
147
  change_statements << change_statement
137
148
  end
138
149
 
150
+ method_chain_array = method_chain.split(".").compact_blank
151
+ will_change_statement =
152
+ if method_chain_array.one? or @is_virtual
153
+ "attribute_will_change!('#{method_chain_array.last}')"
154
+ elsif method_chain_array.many?
155
+ method_chain_array[-1] = "attribute_will_change!('#{method_chain_array.last}')"
156
+ "#{method_chain_array.join('.')}"
157
+ end
158
+
139
159
  <<-STRING
140
- attribute_will_change!('#{parts.last}')
160
+ #{ will_change_statement }
141
161
  #{change_statements.join("\n")}
142
162
  STRING
143
163
  end
@@ -11,16 +11,20 @@ module EPlat
11
11
  @changed_attributes ||= {}
12
12
  end
13
13
 
14
- def attribute_will_change!(attr)
14
+ # attribute will change adds to changed_attributes.
15
+ # We support a force_value option, so we can bipass the getter if the e_plat key is the same as the native key
16
+ # this lets us pass in the native_setter result directly from native_attribute_will_change!
17
+ def attribute_will_change!(attr, force_value: nil)
15
18
  changed_attributes[attr] = read_attribute(attr)
16
-
17
- native_keys.include?(attr) ? e_plat_attribute_will_change!(attr) : native_attribute_will_change!(attr)
19
+
20
+ native_keys.include?(attr) ? e_plat_attribute_will_change!(attr, force_value:) : native_attribute_will_change!(attr, force_value:)
18
21
  end
19
22
 
20
23
  def attribute_changed?(attr)
21
24
  changed_attributes.include?(attr)
22
25
  end
23
26
 
27
+
24
28
  def read_attribute(attr)
25
29
  self.send(attr)
26
30
  end
@@ -33,18 +37,18 @@ module EPlat
33
37
 
34
38
  private
35
39
 
36
- def native_attribute_will_change!(eplat_attr)
40
+ def native_attribute_will_change!(eplat_attr, force_value: nil)
37
41
  key = self.mapping.native_key_to(eplat_attr)
38
42
  return unless key && self.respond_to?(key)
39
43
 
40
- changed_attributes[key] = send(key)
44
+ changed_attributes[key] = force_value || send(key)
41
45
  end
42
46
 
43
- def e_plat_attribute_will_change!(native_attr)
47
+ def e_plat_attribute_will_change!(native_attr, force_value: nil)
44
48
  key = self.mapping.e_plat_key_to(native_attr)
45
49
  return unless key && self.respond_to?(key)
46
-
47
- changed_attributes[key] = send(key)
50
+
51
+ changed_attributes[key] = force_value || send(key)
48
52
  end
49
53
  end
50
54
 
@@ -0,0 +1,253 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EPlat
4
+ module Concerns::GraphQLable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ class_attribute :before_graphql_callbacks, default: {}
9
+ end
10
+
11
+ FILTER_ARGS = [:id, :after, :before, :last, :first].freeze
12
+ QUERY_ARG_ARGS = [:available_for_sale, :created_at, :product_type, :product_id, :tag, :tag_not, :title, :updated_at, :vendor].freeze
13
+
14
+ class_methods do
15
+
16
+ def requests_via_graphql(with: {}, **options)
17
+ return if (options[:unless] && options[:unless].call)
18
+
19
+ with.each do |action, graphql_proc|
20
+ graphql_proc = graphql_proc.gsub("{API_VERSION}", EPlat.shopify_graphql_version)
21
+
22
+ if graphql_proc.to_s.include?("::Query")
23
+ define_singleton_graphql_method(action, graphql_proc)
24
+ elsif graphql_proc.to_s.include?("::Mutation")
25
+ define_instance_graphql_method(action, graphql_proc)
26
+ else
27
+ raise EPlat::Error.new("Invalid graphql request type")
28
+ end
29
+ end
30
+ end
31
+
32
+ def add_to_graphql_requests(action, graphql_proc)
33
+ before_graphql_callbacks[action] = graphql_proc
34
+ end
35
+
36
+ def define_singleton_graphql_method(action, graphql_proc)
37
+ define_singleton_method(action) do |*args|
38
+ initialize_singleton!
39
+ query_graphql(action, graphql_proc, *args)
40
+ end
41
+ end
42
+
43
+ def define_instance_graphql_method(action, graphql_proc)
44
+ define_method(action) do |*args|
45
+ self.class.initialize_singleton!
46
+ mutate_graphql(action, graphql_proc, *args)
47
+ end
48
+ end
49
+
50
+ def query_graphql(action, graphql_proc, *args)
51
+ graphql = eval graphql_proc
52
+ args = scope_with_options_hash(args, action)
53
+
54
+ response = execute_graphql_request graphql_query_with_args(graphql, args)
55
+
56
+ if !response.is_a?(Hash)
57
+ raise EPlat::GraphqlError.new(response.first["message"])
58
+ elsif response["errors"]
59
+ raise EPlat::GraphqlError.new(response["errors"].first["message"])
60
+ else
61
+ case action
62
+ when :find_one, :find_single
63
+ instantiate_record resources_parsed_from(response)
64
+ else
65
+ instantiate_collection resources_parsed_from(response)
66
+ end
67
+ end
68
+ rescue StandardError => e
69
+ raise EPlat::GraphqlError.new(e.message)
70
+ end
71
+
72
+ def execute_graphql_request(graphql_string)
73
+ puts graphql_string if EPlat.config.print_graphql_requests
74
+
75
+ format.decode connection.post(
76
+ client.graphql_request_url,
77
+ { query: graphql_string }.to_json,
78
+ headers
79
+ ).body
80
+ end
81
+
82
+ #utils
83
+
84
+ def scope_with_options_hash(args, action)
85
+ if args.length > 1
86
+ scope, options = args.slice!(0), args.slice!(0)
87
+ elsif args.first.is_a?(Hash)
88
+ scope, options = nil, args.slice!(0) || {}
89
+ else
90
+ scope, options = args.slice!(0), {}
91
+ end
92
+
93
+ scope_arg =
94
+ case action
95
+ when :find_single
96
+ {id: formatted_id(scope)}
97
+ when :find_one
98
+ {first: 1}
99
+ when :find_every
100
+ {first: scope || 200}
101
+ end
102
+
103
+ (options[:params] || {}).with_defaults(scope_arg)
104
+ end
105
+
106
+ def graphql_query_with_args(graphql, args)
107
+ params = filter_graphql_args(**args).merge(
108
+ query: graphql_query_arg(**args)
109
+ ).compact_blank
110
+
111
+ graphql.call(params.to_graphql_args)
112
+ end
113
+
114
+ def filter_graphql_args(**args)
115
+ arguments = sanitize_filter_args args.with_indifferent_access
116
+
117
+ arguments.slice(*FILTER_ARGS).deep_stringify_keys
118
+ end
119
+
120
+ def sanitize_filter_args(args)
121
+ args.transform_keys!{|key| (key == "limit") ? "first" : key}
122
+ args.transform_keys!{|key| (key == "first") ? "last" : key} if args.include?("before")
123
+ args.delete("last") if args.include?("first") && args.include?("last")
124
+
125
+ args
126
+ end
127
+
128
+ def graphql_query_arg(**args)
129
+ args.with_indifferent_access.slice(*QUERY_ARG_ARGS).to_shopify_graphql_query_string
130
+ end
131
+
132
+ def resources_parsed_from(response)
133
+ remove_nodes_from(response).then{remove_root_from _1}.then{ snake_case_keys _1}
134
+ end
135
+
136
+ def remove_nodes_from(data)
137
+ return data unless data.is_a?(Hash) or data.is_a?(Array)
138
+
139
+ if data.is_a?(Array)
140
+ return data.map { |item| remove_nodes_from(item) }
141
+ end
142
+
143
+ data.transform_values do |value|
144
+ if value.is_a?(Hash) && value.key?('nodes')
145
+ remove_nodes_from(value['nodes'])
146
+ elsif value.is_a?(Array)
147
+ value.map { |item| remove_nodes_from(item) }
148
+ else
149
+ remove_nodes_from(value)
150
+ end
151
+ end
152
+ end
153
+
154
+ def remove_root_from(data)
155
+ return data unless data.is_a?(Hash)
156
+
157
+ if data.has_key?(graphql_element_name)
158
+ data[graphql_element_name]
159
+ elsif data.has_key?(graphql_collection_name)
160
+ data[graphql_collection_name]
161
+ else
162
+ data
163
+ end
164
+ end
165
+
166
+ def snake_case_keys(data)
167
+ return data unless data.is_a?(Hash) or data.is_a?(Array)
168
+
169
+ if data.is_a?(Array)
170
+ return data.map { |item| snake_case_keys(item) }
171
+ end
172
+
173
+ data.deep_transform_keys(&:underscore)
174
+ end
175
+
176
+ def graphql_element_name
177
+ case element_name
178
+ when "variant"
179
+ "productVariant"
180
+ else
181
+ element_name
182
+ end
183
+ end
184
+
185
+ def graphql_collection_name
186
+ case collection_name
187
+ when "variants"
188
+ "productVariants"
189
+ else
190
+ collection_name
191
+ end
192
+ end
193
+
194
+ def formatted_id(id)
195
+ return id unless client.shopify? && client.api_version != "2024_01"
196
+ return if id.to_s.include?("gid://")
197
+
198
+ case element_name
199
+ when "product"
200
+ "gid://shopify/Product/#{id}"
201
+ when "variant"
202
+ "gid://shopify/ProductVariant/#{id}"
203
+ when "image"
204
+ "gid://shopify/ProductImage/#{id}"
205
+ else
206
+ raise EPlat::Errors.new("Unsupported element name")
207
+ end
208
+ end
209
+
210
+ end
211
+
212
+ def mutate_graphql(action, graphql_proc_constant, *args)
213
+ callback_name = (action == :delete) ? :destroy : action
214
+
215
+ run_callbacks callback_name.to_sym do
216
+ response = self.class.execute_graphql_request graphql_mutation_string(graphql_proc_constant, action)
217
+
218
+ if !response.is_a?(Hash)
219
+ raise EPlat::GraphqlError.new(response.first["message"])
220
+ elsif response["errors"]
221
+ raise EPlat::GraphqlError.new(response["errors"].first["message"])
222
+ else
223
+ response = remove_mutation_root_from(response, graphql_proc_constant)
224
+ load self.class.resources_parsed_from(response), true, true
225
+
226
+ @persisted = true
227
+ changed_attributes.clear
228
+ end
229
+ end
230
+ rescue StandardError => e
231
+ raise EPlat::GraphqlError.new(e.message)
232
+ end
233
+
234
+ def graphql_mutation_string(graphql_proc_constant, action)
235
+ mutation_proc = eval graphql_proc_constant
236
+ additional_graphql = self.class.before_graphql_callbacks[action.to_sym]&.call(self)
237
+ input =
238
+ if action == :delete
239
+ self.is_a?(EPlat::Product::Variant) ? graphql_input.new(id: formatted_id(id)) : graphql_input.new({id: formatted_id(id)})
240
+ else
241
+ graphql_input.new mapping.via_native_attributes_where_possible(self.class.remove_root_from as_json)
242
+ end
243
+
244
+ mutation_proc.call(input.to_graphql_args, additional_graphql:)
245
+ end
246
+
247
+ def remove_mutation_root_from(data, graphql_proc_constant)
248
+ mutation_root = graphql_proc_constant.split(".").last.camelcase(:lower)
249
+ (mutation_root && data[mutation_root]) ? data[mutation_root] : data
250
+ end
251
+
252
+ end
253
+ end
@@ -84,14 +84,14 @@ module EPlat
84
84
  end
85
85
 
86
86
  def arrayOfPresentHashes?(value)
87
- value.is_a?(Array) && value.first.is_a?(Hash) && value.first.present?
87
+ value.is_a?(Array) && value.first.is_a?(Hash) && value.any?(&:present?)
88
88
  end
89
89
 
90
- def unrepresented_collection_ids(key, arrayOfHashes)
90
+ def unrepresented_collection_ids(key, array_of_hashes)
91
91
  return [] unless client.can_update_nested_resources?
92
92
 
93
- all_instance_ids = try(key)&.map(&:id) || []
94
- represented_instance_ids = arrayOfHashes.map{|item| item["id"] }
93
+ all_instance_ids = send(key).map{|resource| resource.formatted_id resource.id} || []
94
+ represented_instance_ids = array_of_hashes.map{|item| item["id"] }
95
95
 
96
96
  all_instance_ids.compact - represented_instance_ids.compact
97
97
  end
@@ -15,7 +15,7 @@ module EPlat
15
15
  else
16
16
  initialize_singleton!
17
17
 
18
- if arguments.second && arguments.second[:params]
18
+ if arguments.second && arguments.second.is_a?(Hash) && arguments.second[:params]
19
19
  arguments.second[:params].merge!(client.try("#{element_name}_default_request_args") || {})
20
20
  arguments.second[:params] = mapping.via_native_attributes_where_possible(arguments.second[:params])
21
21
  else
@@ -6,16 +6,19 @@ module EPlat
6
6
  def count(options = {})
7
7
  initialize_singleton!
8
8
 
9
- if counts_via_meta_data?
10
- raw_data = connection.get(collection_path, headers)
11
- data_hash = ActiveSupport::JSON.decode(raw_data.body)
12
- data = data_hash.dig("meta", "pagination", "total")
13
- elsif count_key_next_to_root?
14
- raw = get(:count, options)
15
- data = JSON.parse raw.full_response.body
16
- else
17
- data = get(:count, options)
18
- end
9
+ data =
10
+ if counts_via_graphql?
11
+ graphql_count
12
+ elsif counts_via_meta_data?
13
+ raw_data = connection.get(collection_path, headers)
14
+ data_hash = ActiveSupport::JSON.decode(raw_data.body)
15
+ data_hash.dig("meta", "pagination", "total")
16
+ elsif count_key_next_to_root?
17
+ raw = get(:count, options)
18
+ JSON.parse(raw.full_response.body)
19
+ else
20
+ get(:count, options)
21
+ end
19
22
 
20
23
  count =
21
24
  case data
@@ -29,6 +32,13 @@ module EPlat
29
32
 
30
33
  private
31
34
 
35
+ def counts_via_graphql?
36
+ return unless client.shopify?
37
+ return if client.api_version == "2024_01"
38
+
39
+ self == EPlat::Product or self == EPlat::Shopify::Product
40
+ end
41
+
32
42
  def counts_via_meta_data?
33
43
  return false unless client.bigcommerce?
34
44
 
@@ -39,5 +49,22 @@ module EPlat
39
49
  client.bigcommerce? && self == EPlat::Order
40
50
  end
41
51
 
52
+ def graphql_count_query
53
+ "EPlat::Shopify::GraphQL::#{EPlat.shopify_graphql_version}::Query.products_count"
54
+ end
55
+
56
+ def graphql_count
57
+ graphql_query = eval graphql_count_query
58
+ response = execute_graphql_request(graphql_query.call(""))
59
+
60
+ if !response.is_a?(Hash)
61
+ raise EPlat::GraphqlError.new(response.first["message"])
62
+ elsif response["errors"]
63
+ raise EPlat::GraphqlError.new(response["errors"].first["message"])
64
+ else
65
+ response["productsCount"]
66
+ end
67
+ end
68
+
42
69
  end
43
70
  end
@@ -0,0 +1,2 @@
1
+
2
+ class EPlat::Bigcommerce::Product::Variant::OptionValue < EPlat::Product::Variant::OptionValue; end
@@ -0,0 +1,47 @@
1
+ class EPlat::Shopify::GraphQL::V202407::Input::Product::Variant < EPlat::Shopify::GraphQL::V202407::Input
2
+ SUPPORTED_FIELDS = %w[
3
+ barcode
4
+ compareAtPrice
5
+ id
6
+ inventoryItem
7
+ inventoryPolicy
8
+ inventoryQuantities
9
+ mediaId
10
+ mediaSrc
11
+ metafields
12
+ optionValues
13
+ price
14
+ taxCode
15
+ taxable
16
+ ].freeze
17
+
18
+ SUPPORTED_NAMED_ARGUMENT_FIELDS = {
19
+ variant: %w[
20
+ barcode
21
+ compareAtPrice
22
+ id
23
+ inventoryItem
24
+ inventoryPolicy
25
+ inventoryQuantities
26
+ mediaId
27
+ mediaSrc
28
+ metafields
29
+ optionValues
30
+ price
31
+ taxCode
32
+ taxable
33
+ ],
34
+ inventory_item: %w[
35
+ cost
36
+ countryCodeOfOrigin
37
+ countryHarmonizedSystemCodes
38
+ harmonizedSystemCode
39
+ measurement
40
+ provinceCodeOfOrigin
41
+ requiresShipping
42
+ sku
43
+ tracked
44
+ ]
45
+ }.with_indifferent_access.freeze
46
+
47
+ end