graphql_rails 0.8.0 → 1.0.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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/Gemfile.lock +20 -23
  4. data/docs/README.md +21 -5
  5. data/docs/_sidebar.md +3 -0
  6. data/docs/components/controller.md +174 -17
  7. data/docs/components/model.md +151 -3
  8. data/docs/components/routes.md +28 -0
  9. data/docs/getting_started/quick_start.md +9 -2
  10. data/docs/other_tools/query_runner.md +49 -0
  11. data/docs/other_tools/schema_dump.md +29 -0
  12. data/docs/testing/testing.md +3 -1
  13. data/graphql_rails.gemspec +1 -1
  14. data/lib/generators/graphql_rails/install_generator.rb +50 -0
  15. data/lib/generators/graphql_rails/templates/example_users_controller.erb +19 -0
  16. data/lib/generators/graphql_rails/templates/graphql_application_controller.erb +8 -0
  17. data/lib/generators/graphql_rails/templates/graphql_controller.erb +20 -0
  18. data/lib/generators/graphql_rails/templates/graphql_router.erb +19 -0
  19. data/lib/generators/graphql_rails/templates/graphql_router_spec.erb +18 -0
  20. data/lib/graphql_rails.rb +2 -0
  21. data/lib/graphql_rails/attributes/attributable.rb +16 -13
  22. data/lib/graphql_rails/attributes/attribute.rb +20 -3
  23. data/lib/graphql_rails/attributes/input_attribute.rb +20 -10
  24. data/lib/graphql_rails/attributes/input_type_parser.rb +24 -46
  25. data/lib/graphql_rails/attributes/type_parseable.rb +128 -0
  26. data/lib/graphql_rails/attributes/type_parser.rb +58 -54
  27. data/lib/graphql_rails/concerns/service.rb +15 -0
  28. data/lib/graphql_rails/controller.rb +20 -16
  29. data/lib/graphql_rails/controller/action.rb +10 -69
  30. data/lib/graphql_rails/controller/action_configuration.rb +70 -34
  31. data/lib/graphql_rails/controller/build_controller_action_resolver.rb +52 -0
  32. data/lib/graphql_rails/controller/build_controller_action_resolver/controller_action_resolver.rb +28 -0
  33. data/lib/graphql_rails/controller/configuration.rb +56 -5
  34. data/lib/graphql_rails/controller/log_controller_action.rb +4 -4
  35. data/lib/graphql_rails/controller/request.rb +29 -8
  36. data/lib/graphql_rails/controller/request/format_errors.rb +58 -0
  37. data/lib/graphql_rails/decorator/relation_decorator.rb +1 -1
  38. data/lib/graphql_rails/errors/custom_execution_error.rb +22 -0
  39. data/lib/graphql_rails/errors/execution_error.rb +6 -7
  40. data/lib/graphql_rails/errors/system_error.rb +14 -0
  41. data/lib/graphql_rails/errors/validation_error.rb +1 -5
  42. data/lib/graphql_rails/input_configurable.rb +47 -0
  43. data/lib/graphql_rails/model.rb +19 -4
  44. data/lib/graphql_rails/model/build_connection_type.rb +48 -0
  45. data/lib/graphql_rails/model/{configuration → build_connection_type}/count_items.rb +4 -4
  46. data/lib/graphql_rails/model/build_enum_type.rb +39 -10
  47. data/lib/graphql_rails/model/build_graphql_input_type.rb +7 -3
  48. data/lib/graphql_rails/model/build_graphql_type.rb +23 -7
  49. data/lib/graphql_rails/model/call_graphql_model_method.rb +59 -0
  50. data/lib/graphql_rails/model/configuration.rb +2 -6
  51. data/lib/graphql_rails/model/input.rb +10 -6
  52. data/lib/graphql_rails/query_runner.rb +68 -0
  53. data/lib/graphql_rails/railtie.rb +10 -0
  54. data/lib/graphql_rails/router.rb +40 -13
  55. data/lib/graphql_rails/router/resource_routes_builder.rb +2 -1
  56. data/lib/graphql_rails/router/route.rb +25 -6
  57. data/lib/graphql_rails/router/schema_builder.rb +26 -11
  58. data/lib/graphql_rails/rspec_controller_helpers.rb +4 -2
  59. data/lib/graphql_rails/tasks/dump_graphql_schema.rb +57 -0
  60. data/lib/graphql_rails/tasks/schema.rake +14 -0
  61. data/lib/graphql_rails/version.rb +1 -1
  62. metadata +29 -9
  63. data/lib/graphql_rails/controller/controller_function.rb +0 -50
  64. data/lib/graphql_rails/controller/format_results.rb +0 -36
@@ -99,3 +99,31 @@ MyGraphqlSchema = GraphqlRails::Router.draw do
99
99
  mutation :logIn, to: 'sessions#login' # this will trigger ::SessionsController
100
100
  end
101
101
  ```
102
+
103
+ ## _group_
104
+
105
+ You can have multiple routers / schemas. In order to add resources or query only to specific schema, you need wrap it with `group` method, like this:
106
+
107
+ ```ruby
108
+ GraphqlRouter = GraphqlRails::Router.draw do
109
+ resources :users # goes to all routers
110
+
111
+ group :mobile, :internal do
112
+ resources :admin_users # goes to `mobile` and `internal` schemas
113
+ end
114
+
115
+ query :runTesting, to: 'testing#run', group: :testing # goes to `testing` schema
116
+ end
117
+ ```
118
+
119
+ In order to call specific schema you can call it using `QueryRunner` in your RoR controller:
120
+
121
+ ```ruby
122
+ class InternalController < ApplicationController
123
+ def execute
124
+ GraphqlRails::QueryRunner.new(group: :internal, params: params)
125
+ end
126
+ end
127
+ ```
128
+
129
+ If you want to access raw graphql schema, you can call `GraphqlRouter.graphql_schema(:mobile)`
@@ -1,9 +1,16 @@
1
1
  # Quick Start
2
2
 
3
+ ## Generate initial code
4
+
5
+ ```bash
6
+ bundle exec rails g graphql_rails:install
7
+ ```
8
+
3
9
  ## Define GraphQL schema as RoR routes
4
10
 
5
11
  ```ruby
6
- MyGraphqlSchema = GraphqlRails::Router.draw do
12
+ # config/graphql/routes.rb
13
+ GraphqlRails::Router.draw do
7
14
  # will create createUser, updateUser, deleteUser mutations and user, users queries.
8
15
  # expects that UsersController class exist
9
16
  resources :users
@@ -33,7 +40,7 @@ end
33
40
  ## Define controller
34
41
 
35
42
  ```ruby
36
- class UsersController < GraphqlRails::Controller
43
+ class UsersController < ApplicationGraphqlController
37
44
  # graphql requires to describe which attributes controller action accepts and which returns
38
45
  action(:change_user_password)
39
46
  .permit(:password!, :id!) # Bang (!) indicates that attribute is required
@@ -0,0 +1,49 @@
1
+ # Query Runner
2
+
3
+ `GraphqlRails::QueryRunner` is a helper class which let's you graphql queries in RoR controller without worrying about parsing details. Here is an example how to use it:
4
+
5
+ ```ruby
6
+ class MyRailsClass < ApplicationController
7
+ def execute
8
+ graphql_result = GraphqlRails::QueryRunner.call(
9
+ params: params, router: GraphqlRouter
10
+ )
11
+
12
+ render json: graphql_result
13
+ end
14
+ end
15
+ ```
16
+
17
+ ## Executing grouped schema
18
+
19
+ If you have multiple schemas (read [routes section]((components/routes) on how to do that) and you want to render group specific schema, you need to provide group name, like this:
20
+
21
+ ```ruby
22
+ class MyRailsClass < ApplicationController
23
+ def execute
24
+ graphql_result = GraphqlRails::QueryRunner.call(
25
+ group: :internal, # <- custom group name. Can by anything
26
+ params: params, router: GraphqlRouter
27
+ )
28
+
29
+ render json: graphql_result
30
+ end
31
+ end
32
+ ```
33
+
34
+ ## Providing graphql-ruby options
35
+
36
+ All graphql-ruby options are also supported, like [execution options](https://graphql-ruby.org/queries/executing_queries.html) or [visibility options](https://graphql-ruby.org/schema/limiting_visibility.html):
37
+
38
+ ```ruby
39
+ class MyRailsClass < ApplicationController
40
+ def execute
41
+ graphql_result = GraphqlRails::QueryRunner.call(
42
+ validate: true, # <- graphql-ruby option
43
+ params: params, router: GraphqlRouter
44
+ )
45
+
46
+ render json: graphql_result
47
+ end
48
+ end
49
+ ```
@@ -0,0 +1,29 @@
1
+ # Schema Dump
2
+
3
+ GraphqlRails includes rake task to allow creating schema snapshots easier:
4
+
5
+ ```bash
6
+ rake graphql_rails:schema:dump
7
+ ```
8
+
9
+ ## Dumping non default schema
10
+
11
+ You can have multiple graphql schemas. Read more about this in [routes section]((components/routes). In order to generate schema for one of groups, provide optional `name` argument:
12
+
13
+ ```bash
14
+ rake graphql_rails:schema:dump['your_group_name']
15
+ ```
16
+
17
+ or using env variable `SCHEMA_GROUP_NAME`:
18
+
19
+ ```bash
20
+ SCHEMA_GROUP_NAME=your_group_name rake graphql_rails:schema:dump
21
+ ```
22
+
23
+ ## Dumping schema in to non default path
24
+
25
+ By default schema will be dumped to `spec/fixtures/graphql_schema.graphql` path. If you want different schema path, add `GRAPHQL_SCHEMA_DUMP_PATH` env variable, like this:
26
+
27
+ ```bash
28
+ GRAPHQL_SCHEMA_DUMP_PATH='path/to/my/schema.graphql' rake graphql_rails:schema:dump
29
+ ```
@@ -28,11 +28,13 @@ There are 3 helper methods:
28
28
 
29
29
  ```ruby
30
30
  class MyGraphqlController
31
+ action(:create_user).permit(:full_name, :email).returns(User)
32
+ action(:index).returns('String')
33
+
31
34
  def index
32
35
  "Called from index: #{params[:message]}"
33
36
  end
34
37
 
35
- action(:create_user).permit(:full_name, :email)
36
38
  def create_user
37
39
  User.create!(params)
38
40
  end
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
20
20
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
21
  spec.require_paths = ['lib']
22
22
 
23
- spec.add_dependency 'graphql', '~> 1'
23
+ spec.add_dependency 'graphql', '>= 1.9.12'
24
24
  spec.add_dependency 'activesupport', '>= 4'
25
25
 
26
26
  spec.add_development_dependency 'bundler', '~> 1.16'
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlRails
4
+ # no-doc
5
+ module Generators
6
+ require 'rails/generators/base'
7
+
8
+ # Add GraphQL to a Rails app with `rails g graphql_rails:install`.
9
+ #
10
+ # Setup a folder structure for GraphQL:
11
+ #
12
+ # ```
13
+ # - app/
14
+ # - controllers
15
+ # - graphql_controller.rb
16
+ # - graphql
17
+ # - graphql_application_controller.rb
18
+ # - graphql
19
+ # - graphql_router.rb
20
+ # ```
21
+ class InstallGenerator < Rails::Generators::Base
22
+ desc 'Install GraphqlRails folder structure and boilerplate code'
23
+
24
+ source_root File.expand_path('../templates', __FILE__) # rubocop:disable Style/ExpandPathArguments
25
+
26
+ def create_folder_structure
27
+ empty_directory('app/controllers')
28
+ template('graphql_controller.erb', 'app/controllers/graphql_controller.rb')
29
+
30
+ empty_directory('app/controllers/graphql')
31
+ template('graphql_application_controller.erb', 'app/controllers/graphql/graphql_application_controller.rb')
32
+ template('example_users_controller.erb', 'app/controllers/graphql/example_users_controller.rb')
33
+
34
+ application do
35
+ "config.autoload_paths << 'app/graphql'"
36
+ end
37
+
38
+ empty_directory('app/graphql')
39
+ template('graphql_router.erb', 'app/graphql/graphql_router.rb')
40
+
41
+ route('post "/graphql", to: "graphql#execute"')
42
+
43
+ if File.directory?('spec') # rubocop:disable Style/GuardClause
44
+ empty_directory('spec/graphql')
45
+ template('graphql_router_spec.erb', 'spec/app/graphql/graphql_router_spec.rb')
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Graphql
4
+ # base class of all GraphqlControllers
5
+ class ExampleUsersController < GraphqlApplicationController
6
+ model('String')
7
+
8
+ action(:show).permit(id: :ID!).returns_single
9
+ action(:update).permit(id: :ID!).returns_single
10
+
11
+ def show
12
+ 'this is example show action. Remove it and write something real'
13
+ end
14
+
15
+ def update
16
+ 'this is example update action. Remove it and write something real'
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Graphql
4
+ # base class of all GraphqlControllers
5
+ class GraphqlApplicationController < GraphqlRails::Controller
6
+ # TODO: add app specific customizations here
7
+ end
8
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GraphqlController < ApplicationController
4
+ skip_before_action :verify_authenticity_token, only: :execute
5
+
6
+ def execute
7
+ render json: GraphqlRails::QueryRunner.call(
8
+ params: params,
9
+ context: graphql_context
10
+ )
11
+ end
12
+
13
+ private
14
+
15
+ # data defined here will be accessible via `grapqhl_request.context`
16
+ # in GraphqlRails::Controller instances
17
+ def graphql_context
18
+ {}
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ GraphqlRouter = GraphqlRails::Router.draw do
4
+ scope module: :graphql do
5
+ # this will create all CRUD actions for Graphql::UsersController:
6
+ # resources :users
7
+ #
8
+ # If you want non-CRUD custom actions, you can define them like this:
9
+ # query :find_something, to: 'controller_name#action_name'
10
+ # mutation :change_something, to: 'controller_name#action_name'
11
+ # or you can include them in resources part:
12
+ # resources :users do
13
+ # query :find_one, on: :member
14
+ # query :find_many, on: :collection
15
+ # end
16
+
17
+ resources :example_users, only: %i[show update]
18
+ end
19
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ RSpec.describe GraphqlRouter do
6
+ describe '#execute' do
7
+ it 'returns correct structure' do
8
+ # if you need to update saved_schema, run rake task:
9
+ # $ RAILS_ENV=test rake graphql_rails:schema:dump
10
+
11
+ schema_path = Rails.root.join('spec', 'fixtures', 'graphql_schema.graphql')
12
+ saved_schema = File.read(schema_path).strip
13
+ real_schema = described_class.to_definition.strip
14
+
15
+ expect(real_schema).to eq(saved_schema)
16
+ end
17
+ end
18
+ end
@@ -8,6 +8,8 @@ require 'graphql_rails/router'
8
8
  require 'graphql_rails/controller'
9
9
  require 'graphql_rails/attributes'
10
10
  require 'graphql_rails/decorator'
11
+ require 'graphql_rails/query_runner'
12
+ require 'graphql_rails/railtie' if defined?(Rails)
11
13
 
12
14
  # wonders starts here
13
15
  module GraphqlRails
@@ -28,17 +28,22 @@ module GraphqlRails
28
28
  end
29
29
  end
30
30
 
31
+ def required
32
+ @required = true
33
+ self
34
+ end
35
+
36
+ def optional
37
+ @required = false
38
+ self
39
+ end
40
+
31
41
  def graphql_model
32
42
  type_parser.graphql_model
33
43
  end
34
44
 
35
- def graphql_field_type
36
- @graphql_field_type ||= \
37
- if required?
38
- nullable_type.to_non_null_type
39
- else
40
- nullable_type
41
- end
45
+ def optional?
46
+ !required?
42
47
  end
43
48
 
44
49
  protected
@@ -47,15 +52,13 @@ module GraphqlRails
47
52
  {}
48
53
  end
49
54
 
50
- def nullable_type
51
- type = type_parser.graphql_type
52
- type.non_null? ? type.of_type : type
53
- end
54
-
55
55
  private
56
56
 
57
57
  def type_parser
58
- @type_parser ||= TypeParser.new(initial_type || attribute_name_parser.graphql_type)
58
+ @type_parser ||= begin
59
+ type_for_parser = initial_type || attribute_name_parser.graphql_type
60
+ TypeParser.new(type_for_parser, paginated: paginated?)
61
+ end
59
62
  end
60
63
 
61
64
  def attribute_name_parser
@@ -2,12 +2,16 @@
2
2
 
3
3
  require 'graphql'
4
4
  require 'graphql_rails/attributes/attributable'
5
+ require 'graphql_rails/input_configurable'
5
6
 
6
7
  module GraphqlRails
7
8
  module Attributes
8
9
  # contains info about single graphql attribute
9
10
  class Attribute
10
11
  include Attributable
12
+ include InputConfigurable
13
+
14
+ attr_reader :attributes
11
15
 
12
16
  def initialize(name, type = nil, description: nil, property: name, required: nil)
13
17
  @initial_type = type
@@ -15,6 +19,7 @@ module GraphqlRails
15
19
  @description = description
16
20
  @property = property.to_s
17
21
  @required = required
22
+ @attributes ||= {}
18
23
  end
19
24
 
20
25
  def type(new_type = nil)
@@ -41,10 +46,22 @@ module GraphqlRails
41
46
  def field_args
42
47
  [
43
48
  field_name,
44
- graphql_field_type,
49
+ type_parser.type_arg,
50
+ *description,
51
+ {
52
+ method: property.to_sym,
53
+ null: optional?
54
+ }
55
+ ]
56
+ end
57
+
58
+ def argument_args
59
+ [
60
+ field_name,
61
+ type_parser.type_arg,
45
62
  {
46
- property: property.to_sym,
47
- description: description
63
+ description: description,
64
+ required: required?
48
65
  }
49
66
  ]
50
67
  end
@@ -5,12 +5,13 @@ module GraphqlRails
5
5
  # contains info about single graphql input attribute
6
6
  class InputAttribute
7
7
  require_relative './input_type_parser'
8
+ require_relative './attribute_name_parser'
8
9
  include Attributable
9
10
 
10
11
  attr_reader :description
11
12
 
12
13
  # rubocop:disable Metrics/ParameterLists
13
- def initialize(name, type = nil, description: nil, subtype: nil, required: nil, options: {})
14
+ def initialize(name, type: nil, description: nil, subtype: nil, required: nil, options: {})
14
15
  @initial_name = name
15
16
  @initial_type = type
16
17
  @description = description
@@ -20,26 +21,35 @@ module GraphqlRails
20
21
  end
21
22
  # rubocop:enable Metrics/ParameterLists
22
23
 
23
- def function_argument_args
24
- [field_name, graphql_input_type, { description: description }]
25
- end
26
-
27
24
  def input_argument_args
28
- type = raw_input_type || input_type_parser.nullable_type || nullable_type
25
+ type = raw_input_type || input_type_parser.input_type_arg
29
26
 
30
- [field_name, type, { required: required?, description: description }]
27
+ [field_name, type, { required: required?, description: description, camelize: false }]
31
28
  end
32
29
 
33
- def graphql_input_type
34
- raw_input_type || input_type_parser.graphql_type || graphql_field_type
30
+ def paginated?
31
+ false
35
32
  end
36
33
 
37
34
  private
38
35
 
39
36
  attr_reader :initial_name, :initial_type, :options, :subtype
40
37
 
38
+ def attribute_name_parser
39
+ @attribute_name_parser ||= AttributeNameParser.new(
40
+ initial_name, options: attribute_naming_options
41
+ )
42
+ end
43
+
44
+ def attribute_naming_options
45
+ options.slice(:input_format)
46
+ end
47
+
41
48
  def input_type_parser
42
- @input_type_parser ||= InputTypeParser.new(initial_type, subtype: subtype)
49
+ @input_type_parser ||= begin
50
+ initial_parseable_type = initial_type || attribute_name_parser.graphql_type
51
+ InputTypeParser.new(initial_parseable_type, subtype: subtype)
52
+ end
43
53
  end
44
54
 
45
55
  def raw_input_type