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.
- checksums.yaml +4 -4
- data/README.md +84 -54
- data/lib/e_plat/client/default_request_args.rb +1 -1
- data/lib/e_plat/client.rb +34 -8
- data/lib/e_plat/mapping/bigcommerce/v_3/product/option.rb +58 -0
- data/lib/e_plat/mapping/bigcommerce/v_3/product/variant/option_value.rb +42 -0
- data/lib/e_plat/mapping/shopify/v_2024_07/metafield.rb +26 -0
- data/lib/e_plat/mapping/shopify/v_2024_07/order/billing_address.rb +14 -0
- data/lib/e_plat/mapping/shopify/v_2024_07/order/shipping_address.rb +30 -0
- data/lib/e_plat/mapping/shopify/v_2024_07/order.rb +26 -0
- data/lib/e_plat/mapping/shopify/v_2024_07/product/image.rb +52 -0
- data/lib/e_plat/mapping/shopify/v_2024_07/product/option.rb +45 -0
- data/lib/e_plat/mapping/shopify/v_2024_07/product/variant/option_value.rb +58 -0
- data/lib/e_plat/mapping/shopify/v_2024_07/product/variant.rb +180 -0
- data/lib/e_plat/mapping/shopify/v_2024_07/product.rb +100 -0
- data/lib/e_plat/mapping/shopify/v_2024_07/script_tag.rb +26 -0
- data/lib/e_plat/mapping/shopify/v_2024_07/shop.rb +26 -0
- data/lib/e_plat/mapping/shopify/v_2024_07/webhook.rb +29 -0
- data/lib/e_plat/mapping/virtual_collection/shopify/product/variant/option_value.rb +272 -0
- data/lib/e_plat/resource/base.rb +24 -2
- data/lib/e_plat/resource/collection.rb +45 -16
- data/lib/e_plat/resource/concerns/aliases.rb +39 -19
- data/lib/e_plat/resource/concerns/dirty.rb +12 -8
- data/lib/e_plat/resource/concerns/graph_q_lable.rb +253 -0
- data/lib/e_plat/resource/concerns/overwrite_instance_methods.rb +4 -4
- data/lib/e_plat/resource/concerns/overwrite_request_methods.rb +1 -1
- data/lib/e_plat/resource/countable.rb +37 -10
- data/lib/e_plat/resource/platform_specific/bigcommerce/product/variant/option_value.rb +2 -0
- data/lib/e_plat/resource/platform_specific/shopify/graph_q_l/v_2024_07/input/product/variant.rb +47 -0
- data/lib/e_plat/resource/platform_specific/shopify/graph_q_l/v_2024_07/input/product/variants_bulk.rb +47 -0
- data/lib/e_plat/resource/platform_specific/shopify/graph_q_l/v_2024_07/input/product.rb +23 -0
- data/lib/e_plat/resource/platform_specific/shopify/graph_q_l/v_2024_07/input.rb +98 -0
- data/lib/e_plat/resource/platform_specific/shopify/graph_q_l/v_2024_07/mutation/product/variant.rb +78 -0
- data/lib/e_plat/resource/platform_specific/shopify/graph_q_l/v_2024_07/mutation/product.rb +55 -0
- data/lib/e_plat/resource/platform_specific/shopify/graph_q_l/v_2024_07/mutation.rb +5 -0
- data/lib/e_plat/resource/platform_specific/shopify/graph_q_l/v_2024_07/query/product/variant.rb +44 -0
- data/lib/e_plat/resource/platform_specific/shopify/graph_q_l/v_2024_07/query/product.rb +58 -0
- data/lib/e_plat/resource/platform_specific/shopify/graph_q_l/v_2024_07/query.rb +5 -0
- data/lib/e_plat/resource/platform_specific/shopify/product/image.rb +13 -1
- data/lib/e_plat/resource/platform_specific/shopify/product/option.rb +13 -1
- data/lib/e_plat/resource/platform_specific/shopify/product/variant/option_value.rb +14 -0
- data/lib/e_plat/resource/platform_specific/shopify/product/variant.rb +65 -1
- data/lib/e_plat/resource/platform_specific/shopify/product.rb +77 -2
- data/lib/e_plat/resource/product/image.rb +8 -5
- data/lib/e_plat/resource/product/option.rb +2 -3
- data/lib/e_plat/resource/product/variant/option_value.rb +17 -0
- data/lib/e_plat/resource/product/variant.rb +4 -10
- data/lib/e_plat/resource/product.rb +23 -11
- data/lib/e_plat/session.rb +3 -2
- data/lib/e_plat/type_coercer.rb +3 -1
- data/lib/e_plat/version.rb +1 -1
- data/lib/e_plat.rb +12 -3
- data/lib/hash.rb +39 -0
- 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
|
-
@
|
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
|
-
@
|
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(@
|
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(@
|
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(@
|
38
|
+
fetch_page(@next)
|
33
39
|
end
|
34
40
|
|
35
41
|
def fetch_previous_page
|
36
42
|
return unless pagination_available?
|
37
|
-
fetch_page(@
|
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
|
-
|
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
|
71
|
-
|
72
|
-
|
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
|
-
|
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(
|
78
|
-
return unless
|
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(
|
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
|
-
|
16
|
-
|
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
|
-
|
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
|
-
|
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:
|
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:
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
87
|
+
value.is_a?(Array) && value.first.is_a?(Hash) && value.any?(&:present?)
|
88
88
|
end
|
89
89
|
|
90
|
-
def unrepresented_collection_ids(key,
|
90
|
+
def unrepresented_collection_ids(key, array_of_hashes)
|
91
91
|
return [] unless client.can_update_nested_resources?
|
92
92
|
|
93
|
-
all_instance_ids =
|
94
|
-
represented_instance_ids =
|
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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
data/lib/e_plat/resource/platform_specific/shopify/graph_q_l/v_2024_07/input/product/variant.rb
ADDED
@@ -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
|