e_plat 0.6.0 → 0.7.0

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