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
@@ -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,10 +1,17 @@
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
7
- # will create createUser, updateUser, deleteUser mutations and user, users queries.
12
+ # config/graphql/routes.rb
13
+ GraphqlRails::Router.draw do
14
+ # will create createUser, updateUser, destroyUser mutations and user, users queries.
8
15
  # expects that UsersController class exist
9
16
  resources :users
10
17
 
@@ -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
data/docs/index.html CHANGED
@@ -2,7 +2,7 @@
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
- <title>Document</title>
5
+ <title>GraphqlRails</title>
6
6
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
7
7
  <meta name="description" content="Description">
8
8
  <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
@@ -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
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ['Povilas Jurčys']
10
10
  spec.email = ['po.jurcys@gmail.com']
11
11
 
12
- spec.summary = %q{GraphQL server and client for rails}
12
+ spec.summary = %q{Rails style structure for GraphQL API.}
13
13
  spec.homepage = 'https://github.com/samesystem/graphql_rails'
14
14
  spec.license = 'MIT'
15
15
 
@@ -20,13 +20,13 @@ 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.12', '>= 1.12.4'
24
24
  spec.add_dependency 'activesupport', '>= 4'
25
25
 
26
- spec.add_development_dependency 'bundler', '~> 1.16'
27
- spec.add_development_dependency 'rake', '~> 10.0'
26
+ spec.add_development_dependency 'bundler', '~> 2'
27
+ spec.add_development_dependency 'rake', '~> 13.0'
28
28
  spec.add_development_dependency 'rspec', '~> 3.0'
29
29
  spec.add_development_dependency 'activerecord'
30
30
  spec.add_development_dependency 'pry-byebug'
31
- spec.add_development_dependency 'rails', '~> 5'
31
+ spec.add_development_dependency 'rails', '~> 6'
32
32
  end
@@ -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,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ RSpec.describe GraphqlRouter do
6
+ subject(:schema) { described_class.graphql_schema }
7
+
8
+ describe '#to_definition' do
9
+ subject(:to_definition) { schema.to_definition.strip }
10
+
11
+ let(:schema_dump_path) { Rails.root.join('spec', 'fixtures', 'graphql_schema.graphql') }
12
+ let(:previous_definition) { File.read(schema_dump_path).strip }
13
+
14
+ it 'returns correct structure' do
15
+ # if you need to update saved_schema, run rake task:
16
+ # $ RAILS_ENV=test bin/rake graphql_rails:schema:dump
17
+
18
+ expect(to_definition).to eq(previous_definition)
19
+ end
20
+ end
21
+ end
data/lib/graphql_rails.rb CHANGED
@@ -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
@@ -21,41 +21,40 @@ module GraphqlRails
21
21
  end
22
22
 
23
23
  def required?
24
- if @required.nil?
25
- attribute_name_parser.required? || !initial_type.to_s[/!$/].nil?
26
- else
27
- @required
28
- end
24
+ return @required unless @required.nil?
25
+
26
+ attribute_name_parser.required? || !initial_type.to_s[/!$/].nil? || initial_type.is_a?(GraphQL::Schema::NonNull)
29
27
  end
30
28
 
31
- def graphql_model
32
- type_parser.graphql_model
29
+ def required
30
+ @required = true
31
+ self
33
32
  end
34
33
 
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
34
+ def optional
35
+ @required = false
36
+ self
42
37
  end
43
38
 
44
- protected
39
+ def graphql_model
40
+ type_parser.graphql_model
41
+ end
45
42
 
46
- def options
47
- {}
43
+ def optional?
44
+ !required?
48
45
  end
49
46
 
50
- def nullable_type
51
- type = type_parser.graphql_type
52
- type.non_null? ? type.of_type : type
47
+ def scalar_type?
48
+ type_parser.raw_graphql_type? || type_parser.core_scalar_type?
53
49
  end
54
50
 
55
51
  private
56
52
 
57
53
  def type_parser
58
- @type_parser ||= TypeParser.new(initial_type || attribute_name_parser.graphql_type)
54
+ @type_parser ||= begin
55
+ type_for_parser = initial_type || attribute_name_parser.graphql_type
56
+ TypeParser.new(type_for_parser, paginated: paginated?)
57
+ end
59
58
  end
60
59
 
61
60
  def attribute_name_parser
@@ -2,20 +2,28 @@
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
11
13
 
12
- def initialize(name, type = nil, description: nil, property: name, required: nil)
14
+ attr_reader :attributes
15
+
16
+ # rubocop:disable Metrics/ParameterLists
17
+ def initialize(name, type = nil, description: nil, property: name, required: nil, options: {})
13
18
  @initial_type = type
14
19
  @initial_name = name
20
+ @options = options
15
21
  @description = description
16
22
  @property = property.to_s
17
23
  @required = required
24
+ @attributes ||= {}
18
25
  end
26
+ # rubocop:enable Metrics/ParameterLists
19
27
 
20
28
  def type(new_type = nil)
21
29
  return @initial_type if new_type.nil?
@@ -38,13 +46,36 @@ module GraphqlRails
38
46
  self
39
47
  end
40
48
 
49
+ def options(new_options = {})
50
+ return @options if new_options.blank?
51
+
52
+ @options = new_options
53
+ self
54
+ end
55
+
41
56
  def field_args
42
57
  [
43
58
  field_name,
44
- graphql_field_type,
59
+ type_parser.type_arg,
60
+ *description
61
+ ]
62
+ end
63
+
64
+ def field_options
65
+ {
66
+ method: property.to_sym,
67
+ null: optional?,
68
+ camelize: camelize?
69
+ }
70
+ end
71
+
72
+ def argument_args
73
+ [
74
+ field_name,
75
+ type_parser.type_arg,
45
76
  {
46
- property: property.to_sym,
47
- description: description
77
+ description: description,
78
+ required: required?
48
79
  }
49
80
  ]
50
81
  end
@@ -52,6 +83,12 @@ module GraphqlRails
52
83
  protected
53
84
 
54
85
  attr_reader :initial_type, :initial_name
86
+
87
+ private
88
+
89
+ def camelize?
90
+ options[:input_format] != :original && options[:attribute_name_format] != :original
91
+ end
55
92
  end
56
93
  end
57
94
  end