active_shopify_graphql 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/workflows/lint.yml +35 -0
- data/.github/workflows/test.yml +35 -0
- data/.rubocop.yml +50 -0
- data/AGENTS.md +53 -0
- data/LICENSE.txt +21 -0
- data/README.md +544 -0
- data/Rakefile +8 -0
- data/lib/active_shopify_graphql/associations.rb +90 -0
- data/lib/active_shopify_graphql/attributes.rb +49 -0
- data/lib/active_shopify_graphql/base.rb +29 -0
- data/lib/active_shopify_graphql/configuration.rb +29 -0
- data/lib/active_shopify_graphql/connection_loader.rb +96 -0
- data/lib/active_shopify_graphql/connections/connection_proxy.rb +112 -0
- data/lib/active_shopify_graphql/connections.rb +170 -0
- data/lib/active_shopify_graphql/finder_methods.rb +154 -0
- data/lib/active_shopify_graphql/fragment_builder.rb +195 -0
- data/lib/active_shopify_graphql/gid_helper.rb +54 -0
- data/lib/active_shopify_graphql/graphql_type_resolver.rb +91 -0
- data/lib/active_shopify_graphql/loader.rb +183 -0
- data/lib/active_shopify_graphql/loader_context.rb +88 -0
- data/lib/active_shopify_graphql/loader_switchable.rb +121 -0
- data/lib/active_shopify_graphql/loaders/admin_api_loader.rb +32 -0
- data/lib/active_shopify_graphql/loaders/customer_account_api_loader.rb +71 -0
- data/lib/active_shopify_graphql/metafield_attributes.rb +61 -0
- data/lib/active_shopify_graphql/query_node.rb +160 -0
- data/lib/active_shopify_graphql/query_tree.rb +204 -0
- data/lib/active_shopify_graphql/response_mapper.rb +202 -0
- data/lib/active_shopify_graphql/search_query.rb +71 -0
- data/lib/active_shopify_graphql/version.rb +5 -0
- data/lib/active_shopify_graphql.rb +34 -0
- metadata +147 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveShopifyGraphQL
|
|
4
|
+
# Handles mapping GraphQL responses to model attributes.
|
|
5
|
+
# Refactored to use LoaderContext and unified mapping methods.
|
|
6
|
+
class ResponseMapper
|
|
7
|
+
attr_reader :context
|
|
8
|
+
|
|
9
|
+
def initialize(context)
|
|
10
|
+
@context = context
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Map GraphQL response to attributes using declared attribute metadata
|
|
14
|
+
# @param response_data [Hash] The full GraphQL response
|
|
15
|
+
# @param root_path [Array<String>] Path to the data root (e.g., ["data", "customer"])
|
|
16
|
+
# @return [Hash] Mapped attributes
|
|
17
|
+
def map_response(response_data, root_path: nil)
|
|
18
|
+
root_path ||= ["data", @context.query_name]
|
|
19
|
+
root_data = response_data.dig(*root_path)
|
|
20
|
+
return {} unless root_data
|
|
21
|
+
|
|
22
|
+
map_node_to_attributes(root_data)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Map a single node's data to attributes (used for both root and nested)
|
|
26
|
+
def map_node_to_attributes(node_data)
|
|
27
|
+
return {} unless node_data
|
|
28
|
+
|
|
29
|
+
result = {}
|
|
30
|
+
@context.defined_attributes.each do |attr_name, config|
|
|
31
|
+
value = extract_and_transform_value(node_data, config, attr_name)
|
|
32
|
+
result[attr_name] = value
|
|
33
|
+
end
|
|
34
|
+
result
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Extract connection data from GraphQL response for eager loading
|
|
38
|
+
def extract_connection_data(response_data, root_path: nil)
|
|
39
|
+
return {} if @context.included_connections.empty?
|
|
40
|
+
|
|
41
|
+
root_path ||= ["data", @context.query_name]
|
|
42
|
+
root_data = response_data.dig(*root_path)
|
|
43
|
+
return {} unless root_data
|
|
44
|
+
|
|
45
|
+
extract_connections_from_node(root_data)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Extract connections from a node (reusable for nested connections)
|
|
49
|
+
def extract_connections_from_node(node_data)
|
|
50
|
+
return {} if @context.included_connections.empty?
|
|
51
|
+
|
|
52
|
+
connections = @context.connections
|
|
53
|
+
return {} if connections.empty?
|
|
54
|
+
|
|
55
|
+
normalized_includes = FragmentBuilder.normalize_includes(@context.included_connections)
|
|
56
|
+
connection_cache = {}
|
|
57
|
+
|
|
58
|
+
normalized_includes.each do |connection_name, nested_includes|
|
|
59
|
+
connection_config = connections[connection_name]
|
|
60
|
+
next unless connection_config
|
|
61
|
+
|
|
62
|
+
records = extract_connection_records(node_data, connection_config, nested_includes)
|
|
63
|
+
connection_cache[connection_name] = records if records
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
connection_cache
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Map nested connection response (when loading via parent query)
|
|
70
|
+
def map_nested_connection_response(response_data, connection_field_name, parent, connection_config = nil)
|
|
71
|
+
parent_type = parent.class.graphql_type_for_loader(@context.loader_class)
|
|
72
|
+
parent_query_name = parent_type.downcase
|
|
73
|
+
connection_type = connection_config&.dig(:type) || :connection
|
|
74
|
+
|
|
75
|
+
if connection_type == :singular
|
|
76
|
+
node_data = response_data.dig("data", parent_query_name, connection_field_name)
|
|
77
|
+
return nil unless node_data
|
|
78
|
+
|
|
79
|
+
build_model_instance(node_data)
|
|
80
|
+
else
|
|
81
|
+
edges = response_data.dig("data", parent_query_name, connection_field_name, "edges")
|
|
82
|
+
return [] unless edges
|
|
83
|
+
|
|
84
|
+
edges.filter_map do |edge|
|
|
85
|
+
node_data = edge["node"]
|
|
86
|
+
build_model_instance(node_data) if node_data
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Map root connection response
|
|
92
|
+
def map_connection_response(response_data, query_name, connection_config = nil)
|
|
93
|
+
connection_type = connection_config&.dig(:type) || :connection
|
|
94
|
+
|
|
95
|
+
if connection_type == :singular
|
|
96
|
+
node_data = response_data.dig("data", query_name)
|
|
97
|
+
return nil unless node_data
|
|
98
|
+
|
|
99
|
+
build_model_instance(node_data)
|
|
100
|
+
else
|
|
101
|
+
edges = response_data.dig("data", query_name, "edges")
|
|
102
|
+
return [] unless edges
|
|
103
|
+
|
|
104
|
+
edges.filter_map do |edge|
|
|
105
|
+
node_data = edge["node"]
|
|
106
|
+
build_model_instance(node_data) if node_data
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
|
|
113
|
+
def extract_and_transform_value(node_data, config, attr_name)
|
|
114
|
+
path_parts = config[:path].split('.')
|
|
115
|
+
value = node_data.dig(*path_parts)
|
|
116
|
+
|
|
117
|
+
value = apply_defaults_and_transforms(value, config)
|
|
118
|
+
validate_null_constraint!(value, config, attr_name)
|
|
119
|
+
coerce_value(value, config[:type])
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def apply_defaults_and_transforms(value, config)
|
|
123
|
+
if value.nil?
|
|
124
|
+
return config[:default] unless config[:default].nil?
|
|
125
|
+
|
|
126
|
+
return config[:transform]&.call(value)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
config[:transform] ? config[:transform].call(value) : value
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def validate_null_constraint!(value, config, attr_name)
|
|
133
|
+
return unless !config[:null] && value.nil?
|
|
134
|
+
|
|
135
|
+
raise ArgumentError, "Attribute '#{attr_name}' (GraphQL path: '#{config[:path]}') cannot be null but received nil"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def coerce_value(value, type)
|
|
139
|
+
return nil if value.nil?
|
|
140
|
+
return value if value.is_a?(Array) # Preserve arrays
|
|
141
|
+
|
|
142
|
+
type_caster(type).cast(value)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def type_caster(type)
|
|
146
|
+
case type
|
|
147
|
+
when :string then ActiveModel::Type::String.new
|
|
148
|
+
when :integer then ActiveModel::Type::Integer.new
|
|
149
|
+
when :float then ActiveModel::Type::Float.new
|
|
150
|
+
when :boolean then ActiveModel::Type::Boolean.new
|
|
151
|
+
when :datetime then ActiveModel::Type::DateTime.new
|
|
152
|
+
else ActiveModel::Type::Value.new
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def extract_connection_records(node_data, connection_config, nested_includes)
|
|
157
|
+
query_name = connection_config[:query_name]
|
|
158
|
+
connection_type = connection_config[:type] || :connection
|
|
159
|
+
target_class = connection_config[:class_name].constantize
|
|
160
|
+
|
|
161
|
+
if connection_type == :singular
|
|
162
|
+
item_data = node_data[query_name]
|
|
163
|
+
return nil unless item_data
|
|
164
|
+
|
|
165
|
+
build_nested_model_instance(item_data, target_class, nested_includes)
|
|
166
|
+
else
|
|
167
|
+
edges = node_data.dig(query_name, "edges")
|
|
168
|
+
return nil unless edges
|
|
169
|
+
|
|
170
|
+
edges.filter_map do |edge|
|
|
171
|
+
item_data = edge["node"]
|
|
172
|
+
build_nested_model_instance(item_data, target_class, nested_includes) if item_data
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def build_model_instance(node_data)
|
|
178
|
+
return nil unless node_data
|
|
179
|
+
|
|
180
|
+
attributes = map_node_to_attributes(node_data)
|
|
181
|
+
@context.model_class.new(attributes)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def build_nested_model_instance(node_data, target_class, nested_includes)
|
|
185
|
+
nested_context = @context.for_model(target_class, new_connections: nested_includes)
|
|
186
|
+
nested_mapper = ResponseMapper.new(nested_context)
|
|
187
|
+
|
|
188
|
+
attributes = nested_mapper.map_node_to_attributes(node_data)
|
|
189
|
+
instance = target_class.new(attributes)
|
|
190
|
+
|
|
191
|
+
# Handle nested connections recursively
|
|
192
|
+
if nested_includes.any?
|
|
193
|
+
nested_data = nested_mapper.extract_connections_from_node(node_data)
|
|
194
|
+
nested_data.each do |nested_name, nested_records|
|
|
195
|
+
instance.send("#{nested_name}=", nested_records)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
instance
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveShopifyGraphQL
|
|
4
|
+
# Represents a Shopify search query, converting Ruby conditions into Shopify's search syntax
|
|
5
|
+
class SearchQuery
|
|
6
|
+
def initialize(conditions = {})
|
|
7
|
+
@conditions = conditions
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Converts conditions to Shopify search query string
|
|
11
|
+
# @return [String] The Shopify query string
|
|
12
|
+
def to_s
|
|
13
|
+
return "" if @conditions.empty?
|
|
14
|
+
|
|
15
|
+
query_parts = @conditions.map do |key, value|
|
|
16
|
+
format_condition(key.to_s, value)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
query_parts.join(" AND ")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
# Formats a single query condition into Shopify's query syntax
|
|
25
|
+
# @param key [String] The attribute name
|
|
26
|
+
# @param value [Object] The attribute value
|
|
27
|
+
# @return [String] The formatted query condition
|
|
28
|
+
def format_condition(key, value)
|
|
29
|
+
case value
|
|
30
|
+
when String
|
|
31
|
+
format_string_condition(key, value)
|
|
32
|
+
when Numeric, true, false
|
|
33
|
+
"#{key}:#{value}"
|
|
34
|
+
when Hash
|
|
35
|
+
format_range_condition(key, value)
|
|
36
|
+
else
|
|
37
|
+
"#{key}:#{value}"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Formats a string condition with proper quoting
|
|
42
|
+
def format_string_condition(key, value)
|
|
43
|
+
# Handle special string values and escape quotes
|
|
44
|
+
if value.include?(" ") && !value.start_with?('"')
|
|
45
|
+
# Multi-word values should be quoted
|
|
46
|
+
"#{key}:\"#{value.gsub('"', '\\"')}\""
|
|
47
|
+
else
|
|
48
|
+
"#{key}:#{value}"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Formats a range condition (e.g., { created_at: { gte: '2024-01-01' } })
|
|
53
|
+
def format_range_condition(key, value)
|
|
54
|
+
range_parts = value.map do |operator, range_value|
|
|
55
|
+
case operator.to_sym
|
|
56
|
+
when :gt, :>
|
|
57
|
+
"#{key}:>#{range_value}"
|
|
58
|
+
when :gte, :>=
|
|
59
|
+
"#{key}:>=#{range_value}"
|
|
60
|
+
when :lt, :<
|
|
61
|
+
"#{key}:<#{range_value}"
|
|
62
|
+
when :lte, :<=
|
|
63
|
+
"#{key}:<=#{range_value}"
|
|
64
|
+
else
|
|
65
|
+
raise ArgumentError, "Unsupported range operator: #{operator}"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
range_parts.join(" ")
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support'
|
|
4
|
+
require 'active_support/inflector'
|
|
5
|
+
require 'active_support/concern'
|
|
6
|
+
require 'active_support/core_ext/object/blank'
|
|
7
|
+
require 'active_model'
|
|
8
|
+
require 'globalid'
|
|
9
|
+
|
|
10
|
+
require_relative "active_shopify_graphql/version"
|
|
11
|
+
require_relative "active_shopify_graphql/configuration"
|
|
12
|
+
require_relative "active_shopify_graphql/gid_helper"
|
|
13
|
+
require_relative "active_shopify_graphql/graphql_type_resolver"
|
|
14
|
+
require_relative "active_shopify_graphql/loader_context"
|
|
15
|
+
require_relative "active_shopify_graphql/query_node"
|
|
16
|
+
require_relative "active_shopify_graphql/fragment_builder"
|
|
17
|
+
require_relative "active_shopify_graphql/query_tree"
|
|
18
|
+
require_relative "active_shopify_graphql/response_mapper"
|
|
19
|
+
require_relative "active_shopify_graphql/connection_loader"
|
|
20
|
+
require_relative "active_shopify_graphql/loader"
|
|
21
|
+
require_relative "active_shopify_graphql/loaders/admin_api_loader"
|
|
22
|
+
require_relative "active_shopify_graphql/loaders/customer_account_api_loader"
|
|
23
|
+
require_relative "active_shopify_graphql/loader_switchable"
|
|
24
|
+
require_relative "active_shopify_graphql/finder_methods"
|
|
25
|
+
require_relative "active_shopify_graphql/associations"
|
|
26
|
+
require_relative "active_shopify_graphql/connections"
|
|
27
|
+
require_relative "active_shopify_graphql/attributes"
|
|
28
|
+
require_relative "active_shopify_graphql/metafield_attributes"
|
|
29
|
+
require_relative "active_shopify_graphql/search_query"
|
|
30
|
+
require_relative "active_shopify_graphql/base"
|
|
31
|
+
|
|
32
|
+
module ActiveShopifyGraphQL
|
|
33
|
+
class Error < StandardError; end
|
|
34
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: active_shopify_graphql
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Nicolò Rebughini
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date:
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: activemodel
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '7.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '7.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: activesupport
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '7.0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '7.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: globalid
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '1.3'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '1.3'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rspec
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '3.0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '3.0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: rubocop
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '1.0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '1.0'
|
|
83
|
+
description: ActiveShopifyGraphQL provides an ActiveRecord-like interface for interacting
|
|
84
|
+
with Shopify's GraphQL APIs, supporting both Admin API and Customer Account API
|
|
85
|
+
with automatic query building and response mapping.
|
|
86
|
+
email:
|
|
87
|
+
- nicolorebughini@nebulab.com
|
|
88
|
+
executables: []
|
|
89
|
+
extensions: []
|
|
90
|
+
extra_rdoc_files: []
|
|
91
|
+
files:
|
|
92
|
+
- ".github/workflows/lint.yml"
|
|
93
|
+
- ".github/workflows/test.yml"
|
|
94
|
+
- ".rubocop.yml"
|
|
95
|
+
- AGENTS.md
|
|
96
|
+
- LICENSE.txt
|
|
97
|
+
- README.md
|
|
98
|
+
- Rakefile
|
|
99
|
+
- lib/active_shopify_graphql.rb
|
|
100
|
+
- lib/active_shopify_graphql/associations.rb
|
|
101
|
+
- lib/active_shopify_graphql/attributes.rb
|
|
102
|
+
- lib/active_shopify_graphql/base.rb
|
|
103
|
+
- lib/active_shopify_graphql/configuration.rb
|
|
104
|
+
- lib/active_shopify_graphql/connection_loader.rb
|
|
105
|
+
- lib/active_shopify_graphql/connections.rb
|
|
106
|
+
- lib/active_shopify_graphql/connections/connection_proxy.rb
|
|
107
|
+
- lib/active_shopify_graphql/finder_methods.rb
|
|
108
|
+
- lib/active_shopify_graphql/fragment_builder.rb
|
|
109
|
+
- lib/active_shopify_graphql/gid_helper.rb
|
|
110
|
+
- lib/active_shopify_graphql/graphql_type_resolver.rb
|
|
111
|
+
- lib/active_shopify_graphql/loader.rb
|
|
112
|
+
- lib/active_shopify_graphql/loader_context.rb
|
|
113
|
+
- lib/active_shopify_graphql/loader_switchable.rb
|
|
114
|
+
- lib/active_shopify_graphql/loaders/admin_api_loader.rb
|
|
115
|
+
- lib/active_shopify_graphql/loaders/customer_account_api_loader.rb
|
|
116
|
+
- lib/active_shopify_graphql/metafield_attributes.rb
|
|
117
|
+
- lib/active_shopify_graphql/query_node.rb
|
|
118
|
+
- lib/active_shopify_graphql/query_tree.rb
|
|
119
|
+
- lib/active_shopify_graphql/response_mapper.rb
|
|
120
|
+
- lib/active_shopify_graphql/search_query.rb
|
|
121
|
+
- lib/active_shopify_graphql/version.rb
|
|
122
|
+
homepage: https://github.com/nebulab/active_shopify_graphql
|
|
123
|
+
licenses:
|
|
124
|
+
- MIT
|
|
125
|
+
metadata:
|
|
126
|
+
homepage_uri: https://github.com/nebulab/active_shopify_graphql
|
|
127
|
+
source_code_uri: https://github.com/nebulab/active_shopify_graphql
|
|
128
|
+
post_install_message:
|
|
129
|
+
rdoc_options: []
|
|
130
|
+
require_paths:
|
|
131
|
+
- lib
|
|
132
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
133
|
+
requirements:
|
|
134
|
+
- - ">="
|
|
135
|
+
- !ruby/object:Gem::Version
|
|
136
|
+
version: 3.2.0
|
|
137
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
138
|
+
requirements:
|
|
139
|
+
- - ">="
|
|
140
|
+
- !ruby/object:Gem::Version
|
|
141
|
+
version: '0'
|
|
142
|
+
requirements: []
|
|
143
|
+
rubygems_version: 3.5.11
|
|
144
|
+
signing_key:
|
|
145
|
+
specification_version: 4
|
|
146
|
+
summary: An ActiveRecord-like interface for Shopify GraphQL APIs
|
|
147
|
+
test_files: []
|