graphql_rails 0.8.0 → 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
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