graphql_rails 0.8.0 → 1.0.0

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