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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.hound.yml +4 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +48 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +7 -0
  8. data/CHANGELOG.md +25 -0
  9. data/CODE_OF_CONDUCT.md +74 -0
  10. data/Gemfile +15 -0
  11. data/Gemfile.lock +134 -0
  12. data/LICENSE.txt +21 -0
  13. data/Rakefile +6 -0
  14. data/active_graphql.gemspec +49 -0
  15. data/bin/console +14 -0
  16. data/bin/setup +8 -0
  17. data/docs/.nojekyll +0 -0
  18. data/docs/README.md +95 -0
  19. data/docs/_sidebar.md +4 -0
  20. data/docs/client.md +69 -0
  21. data/docs/index.html +70 -0
  22. data/docs/model.md +464 -0
  23. data/lib/active_graphql.rb +10 -0
  24. data/lib/active_graphql/client.rb +38 -0
  25. data/lib/active_graphql/client/actions.rb +15 -0
  26. data/lib/active_graphql/client/actions/action.rb +116 -0
  27. data/lib/active_graphql/client/actions/action/format_inputs.rb +80 -0
  28. data/lib/active_graphql/client/actions/action/format_outputs.rb +40 -0
  29. data/lib/active_graphql/client/actions/mutation_action.rb +29 -0
  30. data/lib/active_graphql/client/actions/query_action.rb +23 -0
  31. data/lib/active_graphql/client/adapters.rb +10 -0
  32. data/lib/active_graphql/client/adapters/graphlient_adapter.rb +32 -0
  33. data/lib/active_graphql/client/response.rb +47 -0
  34. data/lib/active_graphql/errors.rb +11 -0
  35. data/lib/active_graphql/model.rb +174 -0
  36. data/lib/active_graphql/model/action_formatter.rb +96 -0
  37. data/lib/active_graphql/model/build_or_relation.rb +66 -0
  38. data/lib/active_graphql/model/configuration.rb +83 -0
  39. data/lib/active_graphql/model/find_in_batches.rb +54 -0
  40. data/lib/active_graphql/model/relation_proxy.rb +321 -0
  41. data/lib/active_graphql/version.rb +5 -0
  42. metadata +254 -0
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_graphql/version'
4
+
5
+ # nodoc
6
+ module ActiveGraphql
7
+ require 'active_support/core_ext/module/delegation'
8
+ require 'active_graphql/client'
9
+ require 'active_graphql/model'
10
+ end
@@ -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,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveGraphql
4
+ class Client
5
+ # nodoc
6
+ module Adapters
7
+ autoload :GraphlientAdapter, 'active_graphql/client/adapters/graphlient_adapter'
8
+ end
9
+ end
10
+ 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