graphql_rails 0.8.0 → 1.2.2

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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/.hound.yml +1 -0
  3. data/.rubocop.yml +3 -3
  4. data/.ruby-version +1 -1
  5. data/.travis.yml +2 -2
  6. data/CHANGELOG.md +31 -0
  7. data/Gemfile +3 -2
  8. data/Gemfile.lock +147 -124
  9. data/docs/README.md +24 -8
  10. data/docs/_sidebar.md +3 -0
  11. data/docs/components/controller.md +194 -21
  12. data/docs/components/model.md +193 -5
  13. data/docs/components/routes.md +28 -0
  14. data/docs/getting_started/quick_start.md +10 -3
  15. data/docs/index.html +1 -1
  16. data/docs/other_tools/query_runner.md +49 -0
  17. data/docs/other_tools/schema_dump.md +29 -0
  18. data/docs/testing/testing.md +3 -1
  19. data/graphql_rails.gemspec +5 -5
  20. data/lib/generators/graphql_rails/install_generator.rb +50 -0
  21. data/lib/generators/graphql_rails/templates/example_users_controller.erb +19 -0
  22. data/lib/generators/graphql_rails/templates/graphql_application_controller.erb +8 -0
  23. data/lib/generators/graphql_rails/templates/graphql_controller.erb +20 -0
  24. data/lib/generators/graphql_rails/templates/graphql_router.erb +19 -0
  25. data/lib/generators/graphql_rails/templates/graphql_router_spec.erb +21 -0
  26. data/lib/graphql_rails.rb +2 -0
  27. data/lib/graphql_rails/attributes/attributable.rb +20 -21
  28. data/lib/graphql_rails/attributes/attribute.rb +41 -4
  29. data/lib/graphql_rails/attributes/attribute_name_parser.rb +4 -4
  30. data/lib/graphql_rails/attributes/input_attribute.rb +24 -10
  31. data/lib/graphql_rails/attributes/input_type_parser.rb +24 -46
  32. data/lib/graphql_rails/attributes/type_parseable.rb +132 -0
  33. data/lib/graphql_rails/attributes/type_parser.rb +58 -54
  34. data/lib/graphql_rails/concerns/service.rb +19 -0
  35. data/lib/graphql_rails/controller.rb +26 -22
  36. data/lib/graphql_rails/controller/action.rb +12 -67
  37. data/lib/graphql_rails/controller/action_configuration.rb +70 -34
  38. data/lib/graphql_rails/controller/build_controller_action_resolver.rb +52 -0
  39. data/lib/graphql_rails/controller/build_controller_action_resolver/controller_action_resolver.rb +28 -0
  40. data/lib/graphql_rails/controller/configuration.rb +56 -5
  41. data/lib/graphql_rails/controller/log_controller_action.rb +11 -6
  42. data/lib/graphql_rails/controller/request.rb +29 -8
  43. data/lib/graphql_rails/controller/request/format_errors.rb +58 -0
  44. data/lib/graphql_rails/decorator/relation_decorator.rb +1 -5
  45. data/lib/graphql_rails/errors/custom_execution_error.rb +22 -0
  46. data/lib/graphql_rails/errors/execution_error.rb +6 -7
  47. data/lib/graphql_rails/errors/system_error.rb +14 -0
  48. data/lib/graphql_rails/errors/validation_error.rb +1 -5
  49. data/lib/graphql_rails/input_configurable.rb +47 -0
  50. data/lib/graphql_rails/model.rb +19 -4
  51. data/lib/graphql_rails/model/add_fields_to_graphql_type.rb +45 -0
  52. data/lib/graphql_rails/model/build_connection_type.rb +52 -0
  53. data/lib/graphql_rails/model/{configuration → build_connection_type}/count_items.rb +5 -5
  54. data/lib/graphql_rails/model/build_enum_type.rb +39 -10
  55. data/lib/graphql_rails/model/build_graphql_input_type.rb +8 -4
  56. data/lib/graphql_rails/model/call_graphql_model_method.rb +72 -0
  57. data/lib/graphql_rails/model/configurable.rb +6 -2
  58. data/lib/graphql_rails/model/configuration.rb +11 -10
  59. data/lib/graphql_rails/model/find_or_build_graphql_type.rb +64 -0
  60. data/lib/graphql_rails/model/find_or_build_graphql_type_class.rb +46 -0
  61. data/lib/graphql_rails/model/input.rb +10 -6
  62. data/lib/graphql_rails/query_runner.rb +68 -0
  63. data/lib/graphql_rails/railtie.rb +10 -0
  64. data/lib/graphql_rails/router.rb +40 -13
  65. data/lib/graphql_rails/router/resource_routes_builder.rb +10 -9
  66. data/lib/graphql_rails/router/route.rb +21 -6
  67. data/lib/graphql_rails/router/schema_builder.rb +30 -11
  68. data/lib/graphql_rails/rspec_controller_helpers.rb +6 -4
  69. data/lib/graphql_rails/tasks/dump_graphql_schema.rb +57 -0
  70. data/lib/graphql_rails/tasks/schema.rake +14 -0
  71. data/lib/graphql_rails/version.rb +1 -1
  72. metadata +48 -21
  73. data/lib/graphql_rails/controller/controller_function.rb +0 -50
  74. data/lib/graphql_rails/controller/format_results.rb +0 -36
  75. data/lib/graphql_rails/model/build_graphql_type.rb +0 -37
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlRails
4
+ module Model
5
+ # Initializes class to define graphql type and fields.
6
+ class FindOrBuildGraphqlTypeClass
7
+ require 'graphql_rails/concerns/service'
8
+
9
+ include ::GraphqlRails::Service
10
+
11
+ def initialize(name:, type_name:, description: nil)
12
+ @name = name
13
+ @type_name = type_name
14
+ @description = description
15
+ @new_class = false
16
+ end
17
+
18
+ def klass
19
+ @klass ||= Object.const_defined?(type_name) && Object.const_get(type_name) || build_graphql_type_klass
20
+ end
21
+
22
+ def new_class?
23
+ new_class
24
+ end
25
+
26
+ private
27
+
28
+ attr_accessor :new_class
29
+ attr_reader :name, :type_name, :description
30
+
31
+ def build_graphql_type_klass
32
+ graphql_type_name = name
33
+ graphql_type_description = description
34
+
35
+ graphql_type_klass = Class.new(GraphQL::Schema::Object) do
36
+ graphql_name(graphql_type_name)
37
+ description(graphql_type_description)
38
+ end
39
+
40
+ self.new_class = true
41
+
42
+ Object.const_set(type_name, graphql_type_klass)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -14,6 +14,11 @@ module GraphqlRails
14
14
  @input_name_suffix = input_name_suffix
15
15
  end
16
16
 
17
+ def initialize_copy(other)
18
+ super
19
+ @attributes = other.instance_variable_get(:@attributes)&.transform_values(&:dup)
20
+ end
21
+
17
22
  def graphql_input_type
18
23
  @graphql_input_type ||= BuildGraphqlInputType.call(
19
24
  name: name, description: description, attributes: attributes
@@ -21,12 +26,11 @@ module GraphqlRails
21
26
  end
22
27
 
23
28
  def attribute(attribute_name, type: nil, enum: nil, **attribute_options)
24
- attributes[attribute_name.to_s] = \
25
- GraphqlRails::Attributes::InputAttribute.new(
26
- attribute_name,
27
- attribute_type(attribute_name, type: type, enum: enum, **attribute_options),
28
- attribute_options
29
- )
29
+ input_type = attribute_type(attribute_name, type: type, enum: enum, **attribute_options)
30
+
31
+ attributes[attribute_name.to_s] = Attributes::InputAttribute.new(
32
+ attribute_name, type: input_type, **attribute_options
33
+ )
30
34
  end
31
35
 
32
36
  private
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlRails
4
+ # executes GraphQL queries and returns json
5
+ class QueryRunner
6
+ require 'graphql_rails/router'
7
+ require 'graphql_rails/concerns/service'
8
+
9
+ include ::GraphqlRails::Service
10
+
11
+ def initialize(group: nil, params:, schema: nil, router: nil, **schema_options)
12
+ @group = group
13
+ @graphql_schema = schema
14
+ @params = params
15
+ @schema_options = schema_options
16
+ @router = router
17
+ end
18
+
19
+ def call
20
+ graphql_schema.execute(
21
+ params[:query],
22
+ variables: variables,
23
+ operation_name: params[:operationName],
24
+ **schema_options
25
+ )
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :schema_options, :params, :group
31
+
32
+ def variables
33
+ ensure_hash(params[:variables])
34
+ end
35
+
36
+ def graphql_schema
37
+ @graphql_schema ||= router_schema
38
+ end
39
+
40
+ def router
41
+ @router ||= ::GraphqlRouter
42
+ end
43
+
44
+ def router_schema
45
+ router.graphql_schema(group)
46
+ end
47
+
48
+ def ensure_hash(ambiguous_param)
49
+ if ambiguous_param.blank?
50
+ {}
51
+ elsif ambiguous_param.is_a?(String)
52
+ ensure_hash(JSON.parse(ambiguous_param))
53
+ elsif kind_of_hash?(ambiguous_param)
54
+ ambiguous_param
55
+ else
56
+ raise ArgumentError, "Unexpected parameter: #{ambiguous_param.inspect}"
57
+ end
58
+ end
59
+
60
+ def kind_of_hash?(object)
61
+ return true if object.is_a?(Hash)
62
+
63
+ defined?(ActionController) &&
64
+ defined?(ActionController::Parameters) &&
65
+ object.is_a?(ActionController::Parameters)
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlRails
4
+ # Used to load rake tasks in RoR app
5
+ class Railtie < Rails::Railtie
6
+ rake_tasks do
7
+ load 'graphql_rails/tasks/schema.rake'
8
+ end
9
+ end
10
+ end
@@ -11,32 +11,39 @@ module GraphqlRails
11
11
  # graphql router that mimics Rails.application.routes
12
12
  class Router
13
13
  RAW_ACTION_NAMES = %i[
14
- rescue_from query_analyzer instrument cursor_encoder default_max_page_size
14
+ use rescue_from query_analyzer instrument cursor_encoder default_max_page_size
15
15
  ].freeze
16
16
 
17
17
  def self.draw(&block)
18
- router = new
19
- router.instance_eval(&block)
20
- router.graphql_schema
18
+ new.tap do |router|
19
+ router.instance_eval(&block)
20
+ end
21
21
  end
22
22
 
23
23
  attr_reader :routes, :namespace_name, :raw_graphql_actions
24
24
 
25
- def initialize(module_name: '')
25
+ def initialize(module_name: '', group_names: [])
26
26
  @module_name = module_name
27
+ @group_names = group_names
27
28
  @routes ||= Set.new
28
29
  @raw_graphql_actions ||= []
29
30
  end
30
31
 
32
+ def group(*group_names, &block)
33
+ scoped_router = router_with(group_names: group_names)
34
+ scoped_router.instance_eval(&block)
35
+ routes.merge(scoped_router.routes)
36
+ end
37
+
31
38
  def scope(**options, &block)
32
39
  full_module_name = [module_name, options[:module]].reject(&:empty?).join('/')
33
- scoped_router = self.class.new(module_name: full_module_name)
40
+ scoped_router = router_with(module_name: full_module_name)
34
41
  scoped_router.instance_eval(&block)
35
42
  routes.merge(scoped_router.routes)
36
43
  end
37
44
 
38
45
  def resources(name, **options, &block)
39
- builder_options = default_route_options.merge(options)
46
+ builder_options = full_route_options(options)
40
47
  routes_builder = ResourceRoutesBuilder.new(name, **builder_options)
41
48
  routes_builder.instance_eval(&block) if block
42
49
  routes.merge(routes_builder.routes)
@@ -56,25 +63,45 @@ module GraphqlRails
56
63
  end
57
64
  end
58
65
 
59
- def graphql_schema
60
- SchemaBuilder.new(
66
+ def graphql_schema(group = nil)
67
+ @graphql_schema ||= {}
68
+ @graphql_schema[group&.to_sym] ||= SchemaBuilder.new(
61
69
  queries: routes.select(&:query?),
62
70
  mutations: routes.select(&:mutation?),
63
- raw_actions: raw_graphql_actions
71
+ raw_actions: raw_graphql_actions,
72
+ group: group
64
73
  ).call
65
74
  end
66
75
 
76
+ def reload_schema
77
+ @graphql_schema = nil
78
+ end
79
+
67
80
  private
68
81
 
69
- attr_reader :module_name
82
+ attr_reader :module_name, :group_names
83
+
84
+ def router_with(new_router_options = {})
85
+ default_options = { module_name: module_name, group_names: group_names }
86
+ full_options = default_options.merge(new_router_options)
87
+
88
+ self.class.new(**full_options)
89
+ end
70
90
 
71
91
  def add_raw_action(name, *args, &block)
72
92
  raw_graphql_actions << { name: name, args: args, block: block }
73
93
  end
74
94
 
75
95
  def build_route(route_builder, name, **options)
76
- route_options = default_route_options.merge(options)
77
- route_builder.new(name, route_options)
96
+ route_builder.new(name, **full_route_options(options))
97
+ end
98
+
99
+ def full_route_options(extra_options)
100
+ extra_groups = Array(extra_options[:group]) + Array(extra_options[:groups])
101
+ extra_options = extra_options.except(:group, :groups)
102
+ groups = (group_names + extra_groups).uniq
103
+
104
+ default_route_options.merge(extra_options).merge(groups: groups)
78
105
  end
79
106
 
80
107
  def default_route_options
@@ -20,12 +20,12 @@ module GraphqlRails
20
20
  @routes ||= initial_routes
21
21
  end
22
22
 
23
- def query(*args)
24
- routes << build_query(*args)
23
+ def query(*args, **kwargs)
24
+ routes << build_query(*args, **kwargs)
25
25
  end
26
26
 
27
- def mutation(*args)
28
- routes << build_mutation(*args)
27
+ def mutation(*args, **kwargs)
28
+ routes << build_mutation(*args, **kwargs)
29
29
  end
30
30
 
31
31
  private
@@ -54,12 +54,12 @@ module GraphqlRails
54
54
  routes
55
55
  end
56
56
 
57
- def build_mutation(*args)
58
- build_route(MutationRoute, *args)
57
+ def build_mutation(*args, **kwargs)
58
+ build_route(MutationRoute, *args, **kwargs)
59
59
  end
60
60
 
61
- def build_query(*args)
62
- build_route(QueryRoute, *args)
61
+ def build_query(*args, **kwargs)
62
+ build_route(QueryRoute, *args, **kwargs)
63
63
  end
64
64
 
65
65
  # rubocop:disable Metrics/ParameterLists
@@ -70,8 +70,9 @@ module GraphqlRails
70
70
  end
71
71
 
72
72
  action_options = options.merge(custom_options).merge(on: on)
73
+ controller_method_name = action.to_s.underscore
73
74
  action_name = [prefix, resource_name(on), suffix_name].map(&:to_s).reject(&:empty?).join('_')
74
- builder.new(action_name, to: "#{name}##{action}", **action_options)
75
+ builder.new(action_name, to: "#{name}##{controller_method_name}", **action_options)
75
76
  end
76
77
  # rubocop:enable Metrics/ParameterLists
77
78
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../controller/controller_function'
3
+ require_relative '../controller/build_controller_action_resolver'
4
4
 
5
5
  module GraphqlRails
6
6
  class Router
@@ -8,10 +8,11 @@ module GraphqlRails
8
8
  class Route
9
9
  attr_reader :name, :module_name, :on, :relative_path
10
10
 
11
- def initialize(name, to: '', on:, **options)
11
+ def initialize(name, to: '', on:, groups: nil, **options)
12
12
  @name = name.to_s.camelize(:lower)
13
13
  @module_name = options[:module].to_s
14
14
  @function = options[:function]
15
+ @groups = groups
15
16
  @relative_path = to
16
17
  @on = on.to_sym
17
18
  end
@@ -26,12 +27,26 @@ module GraphqlRails
26
27
  on == :collection
27
28
  end
28
29
 
29
- def member?
30
- on == :member
30
+ def show_in_group?(group_name)
31
+ return true if groups.nil? || groups.empty?
32
+
33
+ groups.include?(group_name&.to_sym)
34
+ end
35
+
36
+ def field_options
37
+ if function
38
+ { function: function }
39
+ else
40
+ { resolver: resolver }
41
+ end
31
42
  end
32
43
 
33
- def function
34
- @function ||= Controller::ControllerFunction.from_route(self)
44
+ private
45
+
46
+ attr_reader :function, :groups
47
+
48
+ def resolver
49
+ @resolver ||= Controller::BuildControllerActionResolver.call(route: self)
35
50
  end
36
51
  end
37
52
  end
@@ -8,34 +8,53 @@ module GraphqlRails
8
8
 
9
9
  attr_reader :queries, :mutations, :raw_actions
10
10
 
11
- def initialize(queries:, mutations:, raw_actions:)
11
+ def initialize(queries:, mutations:, raw_actions:, group: nil)
12
12
  @queries = queries
13
13
  @mutations = mutations
14
14
  @raw_actions = raw_actions
15
+ @group = group
15
16
  end
16
17
 
17
18
  def call
18
- query_type = build_type('Query', queries)
19
- mutation_type = build_type('Mutation', mutations)
19
+ query_type = build_group_type('Query', queries)
20
+ mutation_type = build_group_type('Mutation', mutations)
20
21
  raw = raw_actions
21
22
 
22
- GraphQL::Schema.define do
23
+ Class.new(GraphQL::Schema) do
24
+ connections.add(
25
+ GraphqlRails::Decorator::RelationDecorator,
26
+ GraphQL::Pagination::ActiveRecordRelationConnection
27
+ )
23
28
  cursor_encoder(Router::PlainCursorEncoder)
24
29
  raw.each { |action| send(action[:name], *action[:args], &action[:block]) }
25
30
 
26
- query(query_type)
27
- mutation(mutation_type)
31
+ query(query_type) if query_type
32
+ mutation(mutation_type) if mutation_type
28
33
  end
29
34
  end
30
35
 
31
36
  private
32
37
 
33
- def build_type(type_name, routes)
34
- GraphQL::ObjectType.define do
35
- name type_name
38
+ attr_reader :group
36
39
 
37
- routes.each do |route|
38
- field route.name, function: route.function
40
+ def build_group_type(type_name, routes)
41
+ group_name = group
42
+ group_routes = routes.select { |route| route.show_in_group?(group_name) }
43
+ return if group_routes.empty?
44
+
45
+ build_type(type_name, group_routes)
46
+ end
47
+
48
+ def build_type(type_name, group_routes)
49
+ Class.new(GraphQL::Schema::Object) do
50
+ graphql_name(type_name)
51
+
52
+ group_routes.each do |route|
53
+ field(*route.name, **route.field_options)
54
+ end
55
+
56
+ def self.inspect
57
+ "#{GraphQL::Schema::Object}(#{graphql_name})"
39
58
  end
40
59
  end
41
60
  end
@@ -82,11 +82,13 @@ module GraphqlRails
82
82
  action_by_name = config.action_by_name
83
83
  controller_path = controller.name.underscore.sub(/_controller\Z/, '')
84
84
 
85
- Router.draw do
85
+ router = Router.draw do
86
86
  action_by_name.keys.each do |action_name|
87
- query("#{action_name}_test", to: "#{controller_path}##{action_name}")
87
+ query("#{action_name}_test", to: "#{controller_path}##{action_name}", group: :graphql_rspec_helpers)
88
88
  end
89
89
  end
90
+
91
+ router.graphql_schema(:graphql_rspec_helpers)
90
92
  end
91
93
  end
92
94
 
@@ -107,8 +109,8 @@ module GraphqlRails
107
109
  @response
108
110
  end
109
111
 
110
- def mutation(*args)
111
- query(*args)
112
+ def mutation(*args, **kwargs)
113
+ query(*args, **kwargs)
112
114
  end
113
115
 
114
116
  def response