active_graphql 0.2.1
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/.gitignore +15 -0
- data/.hound.yml +4 -0
- data/.rspec +3 -0
- data/.rubocop.yml +48 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +25 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +134 -0
- data/LICENSE.txt +21 -0
- data/Rakefile +6 -0
- data/active_graphql.gemspec +49 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/docs/.nojekyll +0 -0
- data/docs/README.md +95 -0
- data/docs/_sidebar.md +4 -0
- data/docs/client.md +69 -0
- data/docs/index.html +70 -0
- data/docs/model.md +464 -0
- data/lib/active_graphql.rb +10 -0
- data/lib/active_graphql/client.rb +38 -0
- data/lib/active_graphql/client/actions.rb +15 -0
- data/lib/active_graphql/client/actions/action.rb +116 -0
- data/lib/active_graphql/client/actions/action/format_inputs.rb +80 -0
- data/lib/active_graphql/client/actions/action/format_outputs.rb +40 -0
- data/lib/active_graphql/client/actions/mutation_action.rb +29 -0
- data/lib/active_graphql/client/actions/query_action.rb +23 -0
- data/lib/active_graphql/client/adapters.rb +10 -0
- data/lib/active_graphql/client/adapters/graphlient_adapter.rb +32 -0
- data/lib/active_graphql/client/response.rb +47 -0
- data/lib/active_graphql/errors.rb +11 -0
- data/lib/active_graphql/model.rb +174 -0
- data/lib/active_graphql/model/action_formatter.rb +96 -0
- data/lib/active_graphql/model/build_or_relation.rb +66 -0
- data/lib/active_graphql/model/configuration.rb +83 -0
- data/lib/active_graphql/model/find_in_batches.rb +54 -0
- data/lib/active_graphql/model/relation_proxy.rb +321 -0
- data/lib/active_graphql/version.rb +5 -0
- metadata +254 -0
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveGraphql
|
4
|
+
# GraphQL client which can be used to make requests to graphql endpoint
|
5
|
+
#
|
6
|
+
# Example usage:
|
7
|
+
# client = Client.new(url: 'http://example.com/graphql', headers: { 'Authorization' => 'secret'})
|
8
|
+
# client.query(:users).select(:name).result
|
9
|
+
class Client
|
10
|
+
require 'active_graphql/client/actions'
|
11
|
+
require 'active_graphql/client/adapters'
|
12
|
+
require 'active_graphql/client/response'
|
13
|
+
|
14
|
+
def initialize(config)
|
15
|
+
@config = config.dup
|
16
|
+
@adapter_class = @config.delete(:adapter)
|
17
|
+
end
|
18
|
+
|
19
|
+
def query(name)
|
20
|
+
Actions::QueryAction.new(name: name, client: adapter)
|
21
|
+
end
|
22
|
+
|
23
|
+
def mutation(name)
|
24
|
+
Actions::MutationAction.new(name: name, client: adapter)
|
25
|
+
end
|
26
|
+
|
27
|
+
def adapter
|
28
|
+
@adapter ||= begin
|
29
|
+
adapter_builder = @adapter_class || Adapters::GraphlientAdapter
|
30
|
+
adapter_builder.new(config)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
attr_reader :config
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveGraphql
|
4
|
+
class Client
|
5
|
+
# nodoc
|
6
|
+
module Actions
|
7
|
+
require 'active_graphql/errors'
|
8
|
+
require 'active_graphql/client/actions/action'
|
9
|
+
require 'active_graphql/client/actions/query_action'
|
10
|
+
require 'active_graphql/client/actions/mutation_action'
|
11
|
+
|
12
|
+
class WrongTypeError < ActiveGraphql::Errors::Error; end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveGraphql
|
4
|
+
class Client
|
5
|
+
module Actions
|
6
|
+
# Base class for query/mutation action objects
|
7
|
+
class Action
|
8
|
+
class InvalidActionError < StandardError; end
|
9
|
+
|
10
|
+
require 'active_graphql/client/actions/action/format_outputs'
|
11
|
+
require 'active_graphql/client/actions/action/format_inputs'
|
12
|
+
|
13
|
+
attr_reader :name, :type, :output_values, :client, :input_attributes, :meta_attributes
|
14
|
+
|
15
|
+
delegate :result, :result!, to: :response
|
16
|
+
|
17
|
+
def initialize(name:, client:, output_values: [], input_attributes: {}, meta_attributes: {})
|
18
|
+
@name = name
|
19
|
+
@output_values = output_values
|
20
|
+
@input_attributes = input_attributes
|
21
|
+
@meta_attributes = meta_attributes
|
22
|
+
@client = client
|
23
|
+
end
|
24
|
+
|
25
|
+
def inspect
|
26
|
+
"#<#{self.class} " \
|
27
|
+
"name: #{name.inspect}, " \
|
28
|
+
"input: #{input_attributes.inspect}, " \
|
29
|
+
"output: #{output_values.inspect}, " \
|
30
|
+
"meta: #{meta_attributes.inspect}" \
|
31
|
+
'>'
|
32
|
+
end
|
33
|
+
|
34
|
+
def rewhere(**input_attributes)
|
35
|
+
chain(input_attributes: input_attributes)
|
36
|
+
end
|
37
|
+
|
38
|
+
def where(**extra_input_attributes)
|
39
|
+
rewhere(**input_attributes, **extra_input_attributes)
|
40
|
+
end
|
41
|
+
alias input where
|
42
|
+
|
43
|
+
def response
|
44
|
+
client.post(self)
|
45
|
+
end
|
46
|
+
|
47
|
+
def meta(new_attributes)
|
48
|
+
chain(meta_attributes: meta_attributes.merge(new_attributes))
|
49
|
+
end
|
50
|
+
|
51
|
+
def reselect(*array_outputs, **hash_outputs)
|
52
|
+
outputs = join_array_and_hash(*array_outputs, **hash_outputs)
|
53
|
+
chain(output_values: outputs)
|
54
|
+
end
|
55
|
+
|
56
|
+
def select(*array_outputs, **hash_outputs)
|
57
|
+
full_array_outputs = (output_values + array_outputs).uniq
|
58
|
+
reselect(*full_array_outputs, **hash_outputs)
|
59
|
+
end
|
60
|
+
alias output select
|
61
|
+
|
62
|
+
def to_graphql
|
63
|
+
assert_format
|
64
|
+
|
65
|
+
<<~TXT
|
66
|
+
#{type} {
|
67
|
+
#{name}#{wrapped_header formatted_inputs} {
|
68
|
+
#{formatted_outputs}
|
69
|
+
}
|
70
|
+
}
|
71
|
+
TXT
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def join_array_and_hash(*array, **hash)
|
77
|
+
array + hash.map { |k, v| { k => v } }
|
78
|
+
end
|
79
|
+
|
80
|
+
def formatted_inputs
|
81
|
+
FormatInputs.new(input_attributes).call
|
82
|
+
end
|
83
|
+
|
84
|
+
def formatted_outputs
|
85
|
+
FormatOutputs.new(output_values).call
|
86
|
+
end
|
87
|
+
|
88
|
+
def assert_format
|
89
|
+
return unless output_values.empty?
|
90
|
+
|
91
|
+
raise(
|
92
|
+
InvalidActionError,
|
93
|
+
'at least one return value must be set. Do `query.select(:fields_to_return)` to do so'
|
94
|
+
)
|
95
|
+
end
|
96
|
+
|
97
|
+
def chain(**new_values)
|
98
|
+
self.class.new(
|
99
|
+
name: name,
|
100
|
+
output_values: output_values,
|
101
|
+
input_attributes: input_attributes,
|
102
|
+
meta_attributes: meta_attributes,
|
103
|
+
client: client,
|
104
|
+
**new_values
|
105
|
+
)
|
106
|
+
end
|
107
|
+
|
108
|
+
def wrapped_header(header_text)
|
109
|
+
return '' if header_text.empty?
|
110
|
+
|
111
|
+
"(#{header_text})"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveGraphql
|
4
|
+
class Client
|
5
|
+
module Actions
|
6
|
+
class Action
|
7
|
+
# converts ruby object in to grapqhl input string
|
8
|
+
class FormatInputs
|
9
|
+
def initialize(inputs)
|
10
|
+
@inputs = inputs
|
11
|
+
end
|
12
|
+
|
13
|
+
def call
|
14
|
+
return '' if inputs.empty?
|
15
|
+
|
16
|
+
formatted(inputs)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
attr_reader :inputs
|
22
|
+
|
23
|
+
def formatted(attributes)
|
24
|
+
if attributes.is_a?(Hash)
|
25
|
+
formatted_attributes(attributes)
|
26
|
+
else
|
27
|
+
raise(
|
28
|
+
ActiveGraphql::Client::Actions::WrongTypeError,
|
29
|
+
"Unsupported attribute type: #{attributes.inspect}:#{attributes.class}"
|
30
|
+
)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def formatted_attributes(attributes)
|
35
|
+
attributes = attributes.dup
|
36
|
+
keyword_fields = (attributes.delete(:__keyword_attributes) || []).map(&:to_s)
|
37
|
+
|
38
|
+
formatted_attributes = attributes.map do |key, val|
|
39
|
+
if keyword_fields.include?(key.to_s)
|
40
|
+
formatted_key_and_keyword(key, val)
|
41
|
+
else
|
42
|
+
formatted_key_and_value(key, val)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
formatted_attributes.join(', ')
|
47
|
+
end
|
48
|
+
|
49
|
+
def formatted_key_and_value(key, value)
|
50
|
+
"#{key}: #{formatted_value(value)}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def formatted_key_and_keyword(key, value)
|
54
|
+
if value.is_a?(String) || value.is_a?(Symbol)
|
55
|
+
"#{key}: #{value}"
|
56
|
+
else
|
57
|
+
"#{key}: #{formatted_value(value)}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def formatted_value(value) # rubocop:disable Metrics/MethodLength
|
62
|
+
case value
|
63
|
+
when Hash
|
64
|
+
"{ #{formatted(value)} }"
|
65
|
+
when Array
|
66
|
+
formatted_values = value.map { |it| formatted_value(it) }
|
67
|
+
"[#{formatted_values.join(', ')}]"
|
68
|
+
when nil
|
69
|
+
'null'
|
70
|
+
when Symbol
|
71
|
+
value.to_s.inspect
|
72
|
+
else
|
73
|
+
value.inspect
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveGraphql
|
4
|
+
class Client
|
5
|
+
module Actions
|
6
|
+
class Action
|
7
|
+
# converts ruby object in to grapqhl output string
|
8
|
+
class FormatOutputs
|
9
|
+
def initialize(outputs)
|
10
|
+
@outputs = outputs
|
11
|
+
end
|
12
|
+
|
13
|
+
def call
|
14
|
+
Array.wrap(outputs).map { |it| formatted(it) }.join(', ')
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :outputs
|
20
|
+
|
21
|
+
def formatted(attribute) # rubocop:disable Metrics/MethodLength
|
22
|
+
case attribute
|
23
|
+
when Hash
|
24
|
+
attribute.map { |key, value| "#{key} { #{formatted(value)} }" }.join(', ')
|
25
|
+
when Symbol, String
|
26
|
+
attribute.to_s
|
27
|
+
when Array
|
28
|
+
attribute.map { |it| formatted(it) }.join(', ')
|
29
|
+
else
|
30
|
+
raise(
|
31
|
+
ActiveGraphql::Client::Actions::WrongTypeError,
|
32
|
+
"Unsupported attribute type: #{attribute.inspect}:#{attribute.class}"
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveGraphql
|
4
|
+
class Client
|
5
|
+
module Actions
|
6
|
+
# handles all action details which are specific for mutation type request
|
7
|
+
class MutationAction < Action
|
8
|
+
require 'active_graphql/errors'
|
9
|
+
|
10
|
+
class UnsuccessfullRequestError < ActiveGraphql::Errors::Error; end
|
11
|
+
|
12
|
+
def type
|
13
|
+
:mutation
|
14
|
+
end
|
15
|
+
|
16
|
+
def update(inputs)
|
17
|
+
where(inputs).response
|
18
|
+
end
|
19
|
+
|
20
|
+
def update!(inputs)
|
21
|
+
response = where(inputs).response
|
22
|
+
return response.result if response.success?
|
23
|
+
|
24
|
+
raise UnsuccessfullRequestError, response.errors.first
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveGraphql
|
4
|
+
class Client
|
5
|
+
module Actions
|
6
|
+
# handles all action details which are specific for query type request
|
7
|
+
class QueryAction < Action
|
8
|
+
def type
|
9
|
+
:query
|
10
|
+
end
|
11
|
+
|
12
|
+
def find_by(inputs)
|
13
|
+
where(inputs).result
|
14
|
+
end
|
15
|
+
|
16
|
+
def select_paginated(*array_outputs, **hash_outputs)
|
17
|
+
outputs = join_array_and_hash(*array_outputs, **hash_outputs)
|
18
|
+
select(edges: { node: outputs })
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveGraphql
|
4
|
+
class Client
|
5
|
+
module Adapters
|
6
|
+
# Client which makes raw API requests to GraphQL server
|
7
|
+
class GraphlientAdapter
|
8
|
+
require 'graphlient'
|
9
|
+
|
10
|
+
def initialize(config)
|
11
|
+
@url = config[:url]
|
12
|
+
@adapter_config = config.except(:url)
|
13
|
+
end
|
14
|
+
|
15
|
+
def post(action)
|
16
|
+
raw_response = graphql_client.query(action.to_graphql)
|
17
|
+
Response.new(raw_response.data)
|
18
|
+
rescue Graphlient::Errors::GraphQLError => e
|
19
|
+
Response.new(nil, e)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :url, :adapter_config
|
25
|
+
|
26
|
+
def graphql_client
|
27
|
+
@graphql_client ||= Graphlient::Client.new(url, **adapter_config)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveGraphql
|
4
|
+
class Client
|
5
|
+
# graphql response wrapper
|
6
|
+
class Response
|
7
|
+
attr_reader :graphql_object
|
8
|
+
|
9
|
+
def initialize(graphql_object, error = nil)
|
10
|
+
if graphql_object
|
11
|
+
root_field = graphql_object.to_h.keys.first.to_s.underscore
|
12
|
+
@graphql_object = graphql_object.public_send(root_field)
|
13
|
+
end
|
14
|
+
|
15
|
+
@graphql_error = error if error
|
16
|
+
end
|
17
|
+
|
18
|
+
def result
|
19
|
+
graphql_object
|
20
|
+
end
|
21
|
+
|
22
|
+
def result!
|
23
|
+
raise ResponseError, errors.first if errors.any?
|
24
|
+
|
25
|
+
graphql_object
|
26
|
+
end
|
27
|
+
|
28
|
+
def success?
|
29
|
+
graphql_error.nil?
|
30
|
+
end
|
31
|
+
|
32
|
+
def detailed_errors
|
33
|
+
return [] if graphql_error.blank?
|
34
|
+
|
35
|
+
graphql_error.errors.details['data'].map(&:with_indifferent_access)
|
36
|
+
end
|
37
|
+
|
38
|
+
def errors
|
39
|
+
detailed_errors.map { |error| error[:message] }
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
attr_reader :graphql_error
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|