graphql_rails 0.7.0 → 1.2.1

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 (82) 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 +35 -0
  7. data/Gemfile +3 -2
  8. data/Gemfile.lock +181 -71
  9. data/docs/README.md +40 -8
  10. data/docs/_sidebar.md +5 -0
  11. data/docs/components/controller.md +295 -9
  12. data/docs/components/decorator.md +69 -0
  13. data/docs/components/model.md +267 -6
  14. data/docs/components/routes.md +28 -0
  15. data/docs/getting_started/quick_start.md +10 -3
  16. data/docs/index.html +1 -1
  17. data/docs/logging_and_monitoring/logging_and_monitoring.md +35 -0
  18. data/docs/other_tools/query_runner.md +49 -0
  19. data/docs/other_tools/schema_dump.md +29 -0
  20. data/docs/testing/testing.md +3 -1
  21. data/graphql_rails.gemspec +5 -4
  22. data/lib/generators/graphql_rails/install_generator.rb +50 -0
  23. data/lib/generators/graphql_rails/templates/example_users_controller.erb +19 -0
  24. data/lib/generators/graphql_rails/templates/graphql_application_controller.erb +8 -0
  25. data/lib/generators/graphql_rails/templates/graphql_controller.erb +20 -0
  26. data/lib/generators/graphql_rails/templates/graphql_router.erb +19 -0
  27. data/lib/generators/graphql_rails/templates/graphql_router_spec.erb +21 -0
  28. data/lib/graphql_rails.rb +6 -0
  29. data/lib/graphql_rails/attributes/attributable.rb +22 -17
  30. data/lib/graphql_rails/attributes/attribute.rb +67 -3
  31. data/lib/graphql_rails/attributes/attribute_name_parser.rb +4 -4
  32. data/lib/graphql_rails/attributes/input_attribute.rb +33 -15
  33. data/lib/graphql_rails/attributes/input_type_parser.rb +62 -0
  34. data/lib/graphql_rails/attributes/type_name_info.rb +38 -0
  35. data/lib/graphql_rails/attributes/type_parseable.rb +132 -0
  36. data/lib/graphql_rails/attributes/type_parser.rb +59 -53
  37. data/lib/graphql_rails/concerns/service.rb +19 -0
  38. data/lib/graphql_rails/controller.rb +42 -21
  39. data/lib/graphql_rails/controller/action.rb +12 -67
  40. data/lib/graphql_rails/controller/action_configuration.rb +70 -28
  41. data/lib/graphql_rails/controller/build_controller_action_resolver.rb +52 -0
  42. data/lib/graphql_rails/controller/build_controller_action_resolver/controller_action_resolver.rb +28 -0
  43. data/lib/graphql_rails/controller/configuration.rb +56 -3
  44. data/lib/graphql_rails/controller/log_controller_action.rb +71 -0
  45. data/lib/graphql_rails/controller/request.rb +29 -8
  46. data/lib/graphql_rails/controller/request/format_errors.rb +58 -0
  47. data/lib/graphql_rails/decorator.rb +41 -0
  48. data/lib/graphql_rails/decorator/relation_decorator.rb +75 -0
  49. data/lib/graphql_rails/errors/custom_execution_error.rb +22 -0
  50. data/lib/graphql_rails/errors/execution_error.rb +6 -7
  51. data/lib/graphql_rails/errors/system_error.rb +14 -0
  52. data/lib/graphql_rails/errors/validation_error.rb +1 -5
  53. data/lib/graphql_rails/input_configurable.rb +47 -0
  54. data/lib/graphql_rails/integrations.rb +19 -0
  55. data/lib/graphql_rails/integrations/lograge.rb +39 -0
  56. data/lib/graphql_rails/integrations/sentry.rb +34 -0
  57. data/lib/graphql_rails/model.rb +26 -4
  58. data/lib/graphql_rails/model/add_fields_to_graphql_type.rb +45 -0
  59. data/lib/graphql_rails/model/build_connection_type.rb +52 -0
  60. data/lib/graphql_rails/model/{configuration → build_connection_type}/count_items.rb +5 -5
  61. data/lib/graphql_rails/model/build_enum_type.rb +39 -10
  62. data/lib/graphql_rails/model/build_graphql_input_type.rb +8 -4
  63. data/lib/graphql_rails/model/call_graphql_model_method.rb +72 -0
  64. data/lib/graphql_rails/model/configurable.rb +6 -2
  65. data/lib/graphql_rails/model/configuration.rb +30 -16
  66. data/lib/graphql_rails/model/find_or_build_graphql_type.rb +64 -0
  67. data/lib/graphql_rails/model/find_or_build_graphql_type_class.rb +46 -0
  68. data/lib/graphql_rails/model/input.rb +11 -7
  69. data/lib/graphql_rails/query_runner.rb +68 -0
  70. data/lib/graphql_rails/railtie.rb +10 -0
  71. data/lib/graphql_rails/router.rb +40 -13
  72. data/lib/graphql_rails/router/resource_routes_builder.rb +10 -9
  73. data/lib/graphql_rails/router/route.rb +21 -6
  74. data/lib/graphql_rails/router/schema_builder.rb +30 -11
  75. data/lib/graphql_rails/rspec_controller_helpers.rb +6 -4
  76. data/lib/graphql_rails/tasks/dump_graphql_schema.rb +57 -0
  77. data/lib/graphql_rails/tasks/schema.rake +14 -0
  78. data/lib/graphql_rails/version.rb +1 -1
  79. metadata +70 -19
  80. data/lib/graphql_rails/controller/controller_function.rb +0 -50
  81. data/lib/graphql_rails/controller/format_results.rb +0 -36
  82. 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,35 @@
1
+ # Logging and monitoring
2
+
3
+ GraphqlRails behaves similar as Ruby on Rails. This allows to use existing monitoring and logging tools. Here we will add some examples on how to setup various tools for GraphqlRails
4
+
5
+ ## Integrating GraphqlRails with other tools
6
+
7
+ In order to make GraphqlRails work with tools such as lograge or sentry, you need to enable them. In Ruby on Rails, you can add initializer:
8
+
9
+ ```ruby
10
+ # config/initializers/graphql_rails.rb
11
+ GraphqlRails::Integrations.enable(:lograge, :sentry)
12
+ ```
13
+
14
+ At the moment, GraphqlRails supports following integrations:
15
+
16
+ * lograge
17
+ * sentry
18
+
19
+ ## Instrumentation
20
+
21
+ GraphqlRails uses same instrumentation tool (`ActiveSupport::Notifications`) as Ruby on Rails. At the moment there are two notification types:
22
+
23
+ * `process_action.graphql_action_controller`
24
+ * `start_action.graphql_action_controller`
25
+
26
+ you can watch those actions using with `ActiveSupport::Notifications#subscribe` like this:
27
+
28
+ ```ruby
29
+ key = 'process_action.graphql_action_controller'
30
+ ActiveSupport::Notifications.subscribe(key) do |*_, payload|
31
+ YourLogger.do_something(payload)
32
+ end
33
+ ```
34
+
35
+ or you can do the same with `ActiveSupport::LogSubscriber`. More details about it [here](https://api.rubyonrails.org/classes/ActiveSupport/LogSubscriber.html).
@@ -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,12 +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', '~> 6'
31
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
@@ -1,11 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_support/core_ext/module/delegation'
4
+
3
5
  require 'graphql_rails/version'
4
6
  require 'graphql_rails/model'
5
7
  require 'graphql_rails/router'
6
8
  require 'graphql_rails/controller'
7
9
  require 'graphql_rails/attributes'
10
+ require 'graphql_rails/decorator'
11
+ require 'graphql_rails/query_runner'
12
+ require 'graphql_rails/railtie' if defined?(Rails)
8
13
 
9
14
  # wonders starts here
10
15
  module GraphqlRails
16
+ autoload :Integrations, 'graphql_rails/integrations'
11
17
  end
@@ -21,37 +21,42 @@ module GraphqlRails
21
21
  end
22
22
 
23
23
  def required?
24
- attribute_name_parser.required? || !initial_type.to_s[/!$/].nil?
24
+ if @required.nil?
25
+ attribute_name_parser.required? || !initial_type.to_s[/!$/].nil?
26
+ else
27
+ @required
28
+ end
25
29
  end
26
30
 
27
- def graphql_model
28
- type_parser.graphql_model
31
+ def required
32
+ @required = true
33
+ self
29
34
  end
30
35
 
31
- def graphql_field_type
32
- @graphql_field_type ||= \
33
- if required?
34
- nullable_type.to_non_null_type
35
- else
36
- nullable_type
37
- end
36
+ def optional
37
+ @required = false
38
+ self
38
39
  end
39
40
 
40
- protected
41
+ def graphql_model
42
+ type_parser.graphql_model
43
+ end
41
44
 
42
- def options
43
- {}
45
+ def optional?
46
+ !required?
44
47
  end
45
48
 
46
- def nullable_type
47
- type = type_parser.graphql_type
48
- type.non_null? ? type.of_type : type
49
+ def scalar_type?
50
+ type_parser.raw_graphql_type? || type_parser.core_scalar_type?
49
51
  end
50
52
 
51
53
  private
52
54
 
53
55
  def type_parser
54
- @type_parser ||= TypeParser.new(initial_type || attribute_name_parser.graphql_type)
56
+ @type_parser ||= begin
57
+ type_for_parser = initial_type || attribute_name_parser.graphql_type
58
+ TypeParser.new(type_for_parser, paginated: paginated?)
59
+ end
55
60
  end
56
61
 
57
62
  def attribute_name_parser