graphql 1.13.6 → 1.13.7

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.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +0 -7
  3. data/lib/generators/graphql/enum_generator.rb +4 -10
  4. data/lib/generators/graphql/field_extractor.rb +31 -0
  5. data/lib/generators/graphql/input_generator.rb +50 -0
  6. data/lib/generators/graphql/install/mutation_root_generator.rb +34 -0
  7. data/lib/generators/graphql/{templates → install/templates}/base_mutation.erb +0 -0
  8. data/lib/generators/graphql/{templates → install/templates}/mutation_type.erb +0 -0
  9. data/lib/generators/graphql/install_generator.rb +1 -1
  10. data/lib/generators/graphql/interface_generator.rb +7 -7
  11. data/lib/generators/graphql/mutation_create_generator.rb +22 -0
  12. data/lib/generators/graphql/mutation_delete_generator.rb +22 -0
  13. data/lib/generators/graphql/mutation_generator.rb +5 -30
  14. data/lib/generators/graphql/mutation_update_generator.rb +22 -0
  15. data/lib/generators/graphql/object_generator.rb +8 -37
  16. data/lib/generators/graphql/orm_mutations_base.rb +40 -0
  17. data/lib/generators/graphql/scalar_generator.rb +4 -2
  18. data/lib/generators/graphql/templates/enum.erb +5 -1
  19. data/lib/generators/graphql/templates/input.erb +9 -0
  20. data/lib/generators/graphql/templates/interface.erb +4 -2
  21. data/lib/generators/graphql/templates/mutation.erb +1 -1
  22. data/lib/generators/graphql/templates/mutation_create.erb +20 -0
  23. data/lib/generators/graphql/templates/mutation_delete.erb +20 -0
  24. data/lib/generators/graphql/templates/mutation_update.erb +21 -0
  25. data/lib/generators/graphql/templates/object.erb +4 -2
  26. data/lib/generators/graphql/templates/scalar.erb +3 -1
  27. data/lib/generators/graphql/templates/union.erb +4 -2
  28. data/lib/generators/graphql/type_generator.rb +46 -9
  29. data/lib/generators/graphql/union_generator.rb +5 -5
  30. data/lib/graphql/analysis/ast/visitor.rb +2 -1
  31. data/lib/graphql/dataloader/source.rb +2 -2
  32. data/lib/graphql/rubocop/graphql/default_required_true.rb +4 -4
  33. data/lib/graphql/schema/argument.rb +18 -1
  34. data/lib/graphql/schema/field.rb +13 -7
  35. data/lib/graphql/schema.rb +1 -5
  36. data/lib/graphql/types/iso_8601_date_time.rb +8 -1
  37. data/lib/graphql/types/relay/connection_behaviors.rb +2 -1
  38. data/lib/graphql/version.rb +1 -1
  39. data/lib/graphql.rb +11 -0
  40. metadata +15 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d4cb09369d39b11ac4ebb242db3da4bc008a850cdd78c88816143cdfd84addd9
4
- data.tar.gz: 4c5fc1be26724efda2dfc4e0f6ae0ad0ff9593b8708cf38ce3ec3a2ffae759d7
3
+ metadata.gz: 36daea28ec315c9949f4990d05993a15ea4568086f227d7dd7f151ce092747b6
4
+ data.tar.gz: 94d9e0598ff0756be7d7a4fde4c2c395ef47926003ad9d2718e9025ef0f7396a
5
5
  SHA512:
6
- metadata.gz: ea28714d8d623e7591325672a08d031f03877f3744fdc18554ac0be2f2e3149dc2f99ee209f3b91d2d3f82564617d71eee5675a32ea1f60d0d75cc7cdffcf1b3
7
- data.tar.gz: 492bd5f0312bb01a79175fdf87388e2ffd51a3bbe2d8a485a78e30f89634fd1a5ec28fbb1626ea775b38cd1984a3abfa2cc7ba58bd6189165fc91f923a668e30
6
+ metadata.gz: 00c1ba65cb41e6cbc9e4297a90b071f3915e9c8cc550a995f73d357d51ca299c5c478c24678efae9413d6f25fa69ec70b081ec31c120d03c71b93ba82ba291a3
7
+ data.tar.gz: 0b355f53d23bf5d2afeec35f0e33260a1565d79464a4d2dbc70f915c06964d116111211ba7eb070c3a7f3e86c5e9a31e318a30f5aa122f5ea1c608788f747e1e
@@ -25,13 +25,6 @@ module Graphql
25
25
  end
26
26
  end
27
27
 
28
- def create_mutation_root_type
29
- create_dir("#{options[:directory]}/mutations")
30
- template("base_mutation.erb", "#{options[:directory]}/mutations/base_mutation.rb", { skip: true })
31
- template("mutation_type.erb", "#{options[:directory]}/types/mutation_type.rb", { skip: true })
32
- insert_root_type('mutation', 'MutationType')
33
- end
34
-
35
28
  def schema_file_path
36
29
  "#{options[:directory]}/#{schema_name.underscore}.rb"
37
30
  end
@@ -13,20 +13,14 @@ module Graphql
13
13
  desc "Create a GraphQL::EnumType with the given name and values"
14
14
  source_root File.expand_path('../templates', __FILE__)
15
15
 
16
- argument :values,
17
- type: :array,
18
- default: [],
19
- banner: "value{:ruby_value} value{:ruby_value} ...",
20
- desc: "Values for this enum (if present, ruby_value will be inserted verbatim)"
16
+ private
21
17
 
22
- def create_type_file
23
- template "enum.erb", "#{options[:directory]}/types/#{type_file_name}.rb"
18
+ def graphql_type
19
+ "enum"
24
20
  end
25
21
 
26
- private
27
-
28
22
  def prepared_values
29
- values.map { |v| v.split(":", 2) }
23
+ custom_fields.map { |v| v.split(":", 2) }
30
24
  end
31
25
  end
32
26
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+ require 'rails/generators/base'
3
+
4
+ module Graphql
5
+ module Generators
6
+ module FieldExtractor
7
+ def fields
8
+ columns = []
9
+ columns += (klass&.columns&.map { |c| generate_column_string(c) } || [])
10
+ columns + custom_fields
11
+ end
12
+
13
+ def generate_column_string(column)
14
+ name = column.name
15
+ required = column.null ? "" : "!"
16
+ type = column_type_string(column)
17
+ "#{name}:#{required}#{type}"
18
+ end
19
+
20
+ def column_type_string(column)
21
+ column.name == "id" ? "ID" : column.type.to_s.camelize
22
+ end
23
+
24
+ def klass
25
+ @klass ||= Module.const_get(name.camelize)
26
+ rescue NameError
27
+ @klass = nil
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+ require 'generators/graphql/type_generator'
3
+ require 'generators/graphql/field_extractor'
4
+
5
+ module Graphql
6
+ module Generators
7
+ # Generate an input type by name,
8
+ # with the specified fields.
9
+ #
10
+ # ```
11
+ # rails g graphql:object PostType name:string!
12
+ # ```
13
+ class InputGenerator < TypeGeneratorBase
14
+ desc "Create a GraphQL::InputObjectType with the given name and fields"
15
+ source_root File.expand_path('../templates', __FILE__)
16
+ include FieldExtractor
17
+
18
+ def self.normalize_type_expression(type_expression, mode:, null: true)
19
+ case type_expression.camelize
20
+ when "Text", "Citext"
21
+ ["String", null]
22
+ when "Decimal"
23
+ ["Float", null]
24
+ when "DateTime", "Datetime"
25
+ ["GraphQL::Types::ISO8601DateTime", null]
26
+ when "Date"
27
+ ["GraphQL::Types::ISO8601Date", null]
28
+ when "Json", "Jsonb", "Hstore"
29
+ ["GraphQL::Types::JSON", null]
30
+ else
31
+ super
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def graphql_type
38
+ "input"
39
+ end
40
+
41
+ def type_ruby_name
42
+ super.gsub(/Type\z/, "InputType")
43
+ end
44
+
45
+ def type_file_name
46
+ super.gsub(/_type\z/, "_input_type")
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+ require_relative "../core"
5
+
6
+ module Graphql
7
+ module Generators
8
+ module Install
9
+ class MutationRootGenerator < Rails::Generators::Base
10
+ include Core
11
+
12
+ desc "Create mutation base type, mutation root tipe, and adds the latter to the schema"
13
+ source_root File.expand_path('../templates', __FILE__)
14
+
15
+ class_option :schema,
16
+ type: :string,
17
+ default: nil,
18
+ desc: "Name for the schema constant (default: {app_name}Schema)"
19
+
20
+ class_option :skip_keeps,
21
+ type: :boolean,
22
+ default: false,
23
+ desc: "Skip .keep files for source control"
24
+
25
+ def generate
26
+ create_dir("#{options[:directory]}/mutations")
27
+ template("base_mutation.erb", "#{options[:directory]}/mutations/base_mutation.rb", { skip: true })
28
+ template("mutation_type.erb", "#{options[:directory]}/types/mutation_type.rb", { skip: true })
29
+ insert_root_type('mutation', 'MutationType')
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -109,7 +109,7 @@ module Graphql
109
109
  template("query_type.erb", "#{options[:directory]}/types/query_type.rb")
110
110
  insert_root_type('query', 'QueryType')
111
111
 
112
- create_mutation_root_type unless options.skip_mutation_root_type?
112
+ invoke "graphql:install:mutation_root" unless options.skip_mutation_root_type?
113
113
 
114
114
  template("graphql_controller.erb", "app/controllers/graphql_controller.rb")
115
115
  route('post "/graphql", to: "graphql#execute"')
@@ -13,14 +13,14 @@ module Graphql
13
13
  desc "Create a GraphQL::InterfaceType with the given name and fields"
14
14
  source_root File.expand_path('../templates', __FILE__)
15
15
 
16
- argument :fields,
17
- type: :array,
18
- default: [],
19
- banner: "name:type name:type ...",
20
- desc: "Fields for this interface (type may be expressed as Ruby or GraphQL)"
16
+ private
21
17
 
22
- def create_type_file
23
- template "interface.erb", "#{options[:directory]}/types/#{type_file_name}.rb"
18
+ def graphql_type
19
+ "interface"
20
+ end
21
+
22
+ def fields
23
+ custom_fields
24
24
  end
25
25
  end
26
26
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'orm_mutations_base'
3
+
4
+ module Graphql
5
+ module Generators
6
+ # TODO: What other options should be supported?
7
+ #
8
+ # @example Generate a `GraphQL::Schema::RelayClassicMutation` by name
9
+ # rails g graphql:mutation CreatePostMutation
10
+ class MutationCreateGenerator < OrmMutationsBase
11
+
12
+ desc "Scaffold a Relay Classic ORM create mutation for the given model class"
13
+ source_root File.expand_path('../templates', __FILE__)
14
+
15
+ private
16
+
17
+ def operation_type
18
+ "create"
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'orm_mutations_base'
3
+
4
+ module Graphql
5
+ module Generators
6
+ # TODO: What other options should be supported?
7
+ #
8
+ # @example Generate a `GraphQL::Schema::RelayClassicMutation` by name
9
+ # rails g graphql:mutation CreatePostMutation
10
+ class MutationDeleteGenerator < OrmMutationsBase
11
+
12
+ desc "Scaffold a Relay Classic ORM delete mutation for the given model class"
13
+ source_root File.expand_path('../templates', __FILE__)
14
+
15
+ private
16
+
17
+ def operation_type
18
+ "delete"
19
+ end
20
+ end
21
+ end
22
+ end
@@ -9,47 +9,22 @@ module Graphql
9
9
  #
10
10
  # @example Generate a `GraphQL::Schema::RelayClassicMutation` by name
11
11
  # rails g graphql:mutation CreatePostMutation
12
- class MutationGenerator < Rails::Generators::Base
12
+ class MutationGenerator < Rails::Generators::NamedBase
13
13
  include Core
14
14
 
15
15
  desc "Create a Relay Classic mutation by name"
16
16
  source_root File.expand_path('../templates', __FILE__)
17
17
 
18
- argument :name, type: :string
19
-
20
- def initialize(args, *options) # :nodoc:
21
- # Unfreeze name in case it's given as a frozen string
22
- args[0] = args[0].dup if args[0].is_a?(String) && args[0].frozen?
23
- super
24
-
25
- assign_names!(name)
26
- end
27
-
28
- attr_reader :file_name, :mutation_name, :field_name
29
-
30
18
  def create_mutation_file
31
- unless @behavior == :revoke
32
- create_mutation_root_type
33
- else
34
- log :gsub, "#{options[:directory]}/types/mutation_type.rb"
35
- end
36
-
37
- template "mutation.erb", "#{options[:directory]}/mutations/#{file_name}.rb"
19
+ template "mutation.erb", File.join(options[:directory], "/mutations/", class_path, "#{file_name}.rb")
38
20
 
39
21
  sentinel = /class .*MutationType\s*<\s*[^\s]+?\n/m
40
22
  in_root do
41
- gsub_file "#{options[:directory]}/types/mutation_type.rb", / \# TODO\: Add Mutations as fields\s*\n/m, ""
42
- inject_into_file "#{options[:directory]}/types/mutation_type.rb", " field :#{field_name}, mutation: Mutations::#{mutation_name}\n", after: sentinel, verbose: false, force: false
23
+ path = "#{options[:directory]}/types/mutation_type.rb"
24
+ invoke "graphql:install:mutation_root" unless File.exist?(path)
25
+ inject_into_file "#{options[:directory]}/types/mutation_type.rb", " field :#{file_name}, mutation: Mutations::#{class_name}\n", after: sentinel, verbose: false, force: false
43
26
  end
44
27
  end
45
-
46
- private
47
-
48
- def assign_names!(name)
49
- @field_name = name.camelize.underscore
50
- @mutation_name = name.camelize(:upper)
51
- @file_name = name.camelize.underscore
52
- end
53
28
  end
54
29
  end
55
30
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'orm_mutations_base'
3
+
4
+ module Graphql
5
+ module Generators
6
+ # TODO: What other options should be supported?
7
+ #
8
+ # @example Generate a `GraphQL::Schema::RelayClassicMutation` by name
9
+ # rails g graphql:mutation CreatePostMutation
10
+ class MutationUpdateGenerator < OrmMutationsBase
11
+
12
+ desc "Scaffold a Relay Classic ORM update mutation for the given model class"
13
+ source_root File.expand_path('../templates', __FILE__)
14
+
15
+ private
16
+
17
+ def operation_type
18
+ "update"
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require 'generators/graphql/type_generator'
3
+ require 'generators/graphql/field_extractor'
3
4
 
4
5
  module Graphql
5
6
  module Generators
@@ -15,31 +16,16 @@ module Graphql
15
16
  desc "Create a GraphQL::ObjectType with the given name and fields." \
16
17
  "If the given type name matches an existing ActiveRecord model, the generated type will automatically include fields for the models database columns."
17
18
  source_root File.expand_path('../templates', __FILE__)
18
-
19
- argument :custom_fields,
20
- type: :array,
21
- default: [],
22
- banner: "name:type name:type ...",
23
- desc: "Fields for this object (type may be expressed as Ruby or GraphQL)"
19
+ include FieldExtractor
24
20
 
25
21
  class_option :node,
26
22
  type: :boolean,
27
23
  default: false,
28
24
  desc: "Include the Relay Node interface"
29
25
 
30
- def create_type_file
31
- template "object.erb", "#{options[:directory]}/types/#{type_file_name}.rb"
32
- end
33
-
34
- def fields
35
- columns = []
36
- columns += klass.columns.map { |c| generate_column_string(c) } if class_exists?
37
- columns + custom_fields
38
- end
39
-
40
26
  def self.normalize_type_expression(type_expression, mode:, null: true)
41
- case type_expression
42
- when "Text"
27
+ case type_expression.camelize
28
+ when "Text", "Citext"
43
29
  ["String", null]
44
30
  when "Decimal"
45
31
  ["Float", null]
@@ -47,6 +33,8 @@ module Graphql
47
33
  ["GraphQL::Types::ISO8601DateTime", null]
48
34
  when "Date"
49
35
  ["GraphQL::Types::ISO8601Date", null]
36
+ when "Json", "Jsonb", "Hstore"
37
+ ["GraphQL::Types::JSON", null]
50
38
  else
51
39
  super
52
40
  end
@@ -54,25 +42,8 @@ module Graphql
54
42
 
55
43
  private
56
44
 
57
- def generate_column_string(column)
58
- name = column.name
59
- required = column.null ? "" : "!"
60
- type = column_type_string(column)
61
- "#{name}:#{required}#{type}"
62
- end
63
-
64
- def column_type_string(column)
65
- column.name == "id" ? "ID" : column.type.to_s.camelize
66
- end
67
-
68
- def class_exists?
69
- klass.is_a?(Class) && klass.ancestors.include?(ActiveRecord::Base)
70
- rescue NameError
71
- return false
72
- end
73
-
74
- def klass
75
- @klass ||= Module.const_get(type_name.camelize)
45
+ def graphql_type
46
+ "object"
76
47
  end
77
48
  end
78
49
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+ require 'rails/generators'
3
+ require 'rails/generators/named_base'
4
+ require_relative 'core'
5
+
6
+ module Graphql
7
+ module Generators
8
+ # TODO: What other options should be supported?
9
+ #
10
+ # @example Generate a `GraphQL::Schema::RelayClassicMutation` by name
11
+ # rails g graphql:mutation CreatePostMutation
12
+ class OrmMutationsBase < Rails::Generators::NamedBase
13
+ include Core
14
+ include Rails::Generators::ResourceHelpers
15
+
16
+ desc "Create a Relay Classic mutation by name"
17
+
18
+ class_option :orm, banner: "NAME", type: :string, required: true,
19
+ desc: "ORM to generate the controller for"
20
+
21
+ class_option 'namespaced_types',
22
+ type: :boolean,
23
+ required: false,
24
+ default: false,
25
+ banner: "Namespaced",
26
+ desc: "If the generated types will be namespaced"
27
+
28
+ def create_mutation_file
29
+ template "mutation_#{operation_type}.erb", File.join(options[:directory], "/mutations/", class_path, "#{file_name}_#{operation_type}.rb")
30
+
31
+ sentinel = /class .*MutationType\s*<\s*[^\s]+?\n/m
32
+ in_root do
33
+ path = "#{options[:directory]}/types/mutation_type.rb"
34
+ invoke "graphql:install:mutation_root" unless File.exist?(path)
35
+ inject_into_file "#{options[:directory]}/types/mutation_type.rb", " field :#{file_name}_#{operation_type}, mutation: Mutations::#{class_name}#{operation_type.classify}\n", after: sentinel, verbose: false, force: false
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -12,8 +12,10 @@ module Graphql
12
12
  desc "Create a GraphQL::ScalarType with the given name"
13
13
  source_root File.expand_path('../templates', __FILE__)
14
14
 
15
- def create_type_file
16
- template "scalar.erb", "#{options[:directory]}/types/#{type_file_name}.rb"
15
+ private
16
+
17
+ def graphql_type
18
+ "scalar"
17
19
  end
18
20
  end
19
21
  end
@@ -1,6 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  <% module_namespacing_when_supported do -%>
2
4
  module Types
3
- class <%= type_ruby_name.split('::')[-1] %> < Types::BaseEnum
5
+ class <%= ruby_class_name %> < Types::BaseEnum
6
+ description "<%= human_name %> enum"
7
+
4
8
  <% prepared_values.each do |v| %> value "<%= v[0] %>"<%= v.length > 1 ? ", value: #{v[1]}" : "" %>
5
9
  <% end %> end
6
10
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ <% module_namespacing_when_supported do -%>
4
+ module Types
5
+ class <%= ruby_class_name %> < Types::BaseInputObject
6
+ <% normalized_fields.each do |f| %> <%= f.to_input_argument %>
7
+ <% end %> end
8
+ end
9
+ <% end -%>
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  <% module_namespacing_when_supported do -%>
2
4
  module Types
3
- module <%= type_ruby_name.split('::')[-1] %>
5
+ module <%= ruby_class_name %>
4
6
  include Types::BaseInterface
5
- <% normalized_fields.each do |f| %> <%= f.to_ruby %>
7
+ <% normalized_fields.each do |f| %> <%= f.to_object_field %>
6
8
  <% end %> end
7
9
  end
8
10
  <% end -%>
@@ -1,6 +1,6 @@
1
1
  <% module_namespacing_when_supported do -%>
2
2
  module Mutations
3
- class <%= mutation_name %> < BaseMutation
3
+ class <%= class_name %> < BaseMutation
4
4
  # TODO: define return fields
5
5
  # field :post, Types::PostType, null: false
6
6
 
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ <% module_namespacing_when_supported do -%>
4
+ module Mutations
5
+ class <%= class_name %>Create < BaseMutation
6
+ description "Creates a new <%= file_name %>"
7
+
8
+ field :<%= file_name %>, Types::<%= options[:namespaced_types] ? 'Objects::' : '' %><%= class_name %>Type, null: false
9
+
10
+ argument :<%= file_name %>_input, Types::<%= options[:namespaced_types] ? 'Inputs::' : '' %><%= class_name %>InputType, required: true
11
+
12
+ def resolve(<%= file_name %>_input:)
13
+ <%= singular_table_name %> = ::<%= orm_class.build(class_name, "**#{file_name}_input") %>
14
+ raise GraphQL::ExecutionError.new "Error creating <%= file_name %>", extensions: <%= singular_table_name %>.errors.to_hash unless <%= orm_instance.save %>
15
+
16
+ { <%= file_name %>: <%= singular_table_name %> }
17
+ end
18
+ end
19
+ end
20
+ <% end -%>
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ <% module_namespacing_when_supported do -%>
4
+ module Mutations
5
+ class <%= class_name %>Delete < BaseMutation
6
+ description "Deletes a <%= file_name %> by ID"
7
+
8
+ field :<%= file_name %>, Types::<%= options[:namespaced_types] ? 'Objects::' : '' %><%= class_name %>Type, null: false
9
+
10
+ argument :id, ID, required: true
11
+
12
+ def resolve(id:)
13
+ <%= singular_table_name %> = ::<%= orm_class.find(class_name, "id") %>
14
+ raise GraphQL::ExecutionError.new "Error deleting <%= file_name %>", extensions: <%= singular_table_name %>.errors.to_hash unless <%= orm_instance.destroy %>
15
+
16
+ { <%= file_name %>: <%= singular_table_name %> }
17
+ end
18
+ end
19
+ end
20
+ <% end -%>
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ <% module_namespacing_when_supported do -%>
4
+ module Mutations
5
+ class <%= class_name %>Update < BaseMutation
6
+ description "Updates a <%= file_name %> by id"
7
+
8
+ field :<%= file_name %>, Types::<%= options[:namespaced_types] ? 'Objects::' : '' %><%= class_name %>Type, null: false
9
+
10
+ argument :id, ID, required: true
11
+ argument :<%= file_name %>_input, Types::<%= options[:namespaced_types] ? 'Inputs::' : '' %><%= class_name %>InputType, required: true
12
+
13
+ def resolve(id:, <%= file_name %>_input:)
14
+ <%= singular_table_name %> = ::<%= orm_class.find(class_name, "id") %>
15
+ raise GraphQL::ExecutionError.new "Error updating <%= file_name %>", extensions: <%= singular_table_name %>.errors.to_hash unless <%= orm_instance.update("**#{file_name}_input") %>
16
+
17
+ { <%= file_name %>: <%= singular_table_name %> }
18
+ end
19
+ end
20
+ end
21
+ <% end -%>
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  <% module_namespacing_when_supported do -%>
2
4
  module Types
3
- class <%= type_ruby_name.split('::')[-1] %> < Types::BaseObject
5
+ class <%= ruby_class_name %> < Types::BaseObject
4
6
  <% if options.node %> implements GraphQL::Types::Relay::Node
5
- <% end %><% normalized_fields.each do |f| %> <%= f.to_ruby %>
7
+ <% end %><% normalized_fields.each do |f| %> <%= f.to_object_field %>
6
8
  <% end %> end
7
9
  end
8
10
  <% end -%>
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  <% module_namespacing_when_supported do -%>
2
4
  module Types
3
- class <%= type_ruby_name.split('::')[-1] %> < Types::BaseScalar
5
+ class <%= ruby_class_name %> < Types::BaseScalar
4
6
  def self.coerce_input(input_value, context)
5
7
  # Override this to prepare a client-provided GraphQL value for your Ruby code
6
8
  input_value
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  <% module_namespacing_when_supported do -%>
2
4
  module Types
3
- class <%= type_ruby_name.split('::')[-1] %> < Types::BaseUnion
4
- <% if possible_types.any? %> possible_types <%= normalized_possible_types.join(", ") %>
5
+ class <%= ruby_class_name %> < Types::BaseUnion
6
+ <% if custom_fields.any? %> possible_types <%= normalized_possible_types.join(", ") %>
5
7
  <% end %> end
6
8
  end
7
9
  <% end -%>
@@ -8,13 +8,28 @@ require_relative 'core'
8
8
 
9
9
  module Graphql
10
10
  module Generators
11
- class TypeGeneratorBase < Rails::Generators::Base
11
+ class TypeGeneratorBase < Rails::Generators::NamedBase
12
12
  include Core
13
13
 
14
- argument :type_name,
15
- type: :string,
16
- banner: "TypeName",
17
- desc: "Name of this object type (expressed as Ruby or GraphQL)"
14
+ class_option 'namespaced_types',
15
+ type: :boolean,
16
+ required: false,
17
+ default: false,
18
+ banner: "Namespaced",
19
+ desc: "If the generated types will be namespaced"
20
+
21
+ argument :custom_fields,
22
+ type: :array,
23
+ default: [],
24
+ banner: "name:type name:type ...",
25
+ desc: "Fields for this object (type may be expressed as Ruby or GraphQL)"
26
+
27
+
28
+ attr_accessor :graphql_type
29
+
30
+ def create_type_file
31
+ template "#{graphql_type}.erb", "#{options[:directory]}/types#{subdirectory}/#{type_file_name}.rb"
32
+ end
18
33
 
19
34
  # Take a type expression in any combination of GraphQL or Ruby styles
20
35
  # and return it in a specified output style
@@ -60,12 +75,12 @@ module Graphql
60
75
 
61
76
  # @return [String] The user-provided type name, normalized to Ruby code
62
77
  def type_ruby_name
63
- @type_ruby_name ||= self.class.normalize_type_expression(type_name, mode: :ruby)[0]
78
+ @type_ruby_name ||= self.class.normalize_type_expression(name, mode: :ruby)[0]
64
79
  end
65
80
 
66
81
  # @return [String] The user-provided type name, as a GraphQL name
67
82
  def type_graphql_name
68
- @type_graphql_name ||= self.class.normalize_type_expression(type_name, mode: :graphql)[0]
83
+ @type_graphql_name ||= self.class.normalize_type_expression(name, mode: :graphql)[0]
69
84
  end
70
85
 
71
86
  # @return [String] The user-provided type name, as a file name (without extension)
@@ -82,6 +97,24 @@ module Graphql
82
97
  }
83
98
  end
84
99
 
100
+ def ruby_class_name
101
+ class_prefix =
102
+ if options[:namespaced_types]
103
+ "#{graphql_type.pluralize.camelize}::"
104
+ else
105
+ ""
106
+ end
107
+ @ruby_class_name || class_prefix + type_ruby_name.sub(/^Types::/, "")
108
+ end
109
+
110
+ def subdirectory
111
+ if options[:namespaced_types]
112
+ "/#{graphql_type.pluralize}"
113
+ else
114
+ ""
115
+ end
116
+ end
117
+
85
118
  class NormalizedField
86
119
  def initialize(name, type_expr, null)
87
120
  @name = name
@@ -89,8 +122,12 @@ module Graphql
89
122
  @null = null
90
123
  end
91
124
 
92
- def to_ruby
93
- "field :#{@name}, #{@type_expr}, null: #{@null}"
125
+ def to_object_field
126
+ "field :#{@name}, #{@type_expr}#{@null ? '' : ', null: false'}"
127
+ end
128
+
129
+ def to_input_argument
130
+ "argument :#{@name}, #{@type_expr}, required: false"
94
131
  end
95
132
  end
96
133
  end
@@ -19,14 +19,14 @@ module Graphql
19
19
  banner: "type type ...",
20
20
  desc: "Possible types for this union (expressed as Ruby or GraphQL)"
21
21
 
22
- def create_type_file
23
- template "union.erb", "#{options[:directory]}/types/#{type_file_name}.rb"
24
- end
25
-
26
22
  private
27
23
 
24
+ def graphql_type
25
+ "union"
26
+ end
27
+
28
28
  def normalized_possible_types
29
- possible_types.map { |t| self.class.normalize_type_expression(t, mode: :ruby)[0] }
29
+ custom_fields.map { |t| self.class.normalize_type_expression(t, mode: :ruby)[0] }
30
30
  end
31
31
  end
32
32
  end
@@ -100,7 +100,8 @@ module GraphQL
100
100
  def on_field(node, parent)
101
101
  @response_path.push(node.alias || node.name)
102
102
  parent_type = @object_types.last
103
- field_definition = @schema.get_field(parent_type, node.name, @query.context)
103
+ # This could be nil if the previous field wasn't found:
104
+ field_definition = parent_type && @schema.get_field(parent_type, node.name, @query.context)
104
105
  @field_definitions.push(field_definition)
105
106
  if !field_definition.nil?
106
107
  next_object_type = field_definition.type.unwrap
@@ -138,8 +138,8 @@ module GraphQL
138
138
  # @api private
139
139
  def result_for(key)
140
140
  if !@results.key?(key)
141
- raise <<-ERR
142
- Invariant: fetching result for a key on #{self.class} that hasn't been loaded yet (#{key.inspect}, loaded: #{@results.keys})
141
+ raise GraphQL::InvariantError, <<-ERR
142
+ Fetching result for a key on #{self.class} that hasn't been loaded yet (#{key.inspect}, loaded: #{@results.keys})
143
143
 
144
144
  This key should have been loaded already. This is a bug in GraphQL::Dataloader, please report it on GitHub: https://github.com/rmosolgo/graphql-ruby/issues/new.
145
145
  ERR
@@ -25,16 +25,16 @@ module GraphQL
25
25
 
26
26
  def_node_matcher :argument_config_with_required_true?, <<-Pattern
27
27
  (
28
- send nil? :argument ... (hash <$(pair (sym :required) (true)) ...>)
28
+ send {nil? _} :argument ... (hash <$(pair (sym :required) (true)) ...>)
29
29
  )
30
30
  Pattern
31
31
 
32
32
  def on_send(node)
33
33
  argument_config_with_required_true?(node) do |required_config|
34
34
  add_offense(required_config) do |corrector|
35
- cleaned_node_source = source_without_keyword_argument(node, required_config)
36
- corrector.replace(node, cleaned_node_source)
37
- end
35
+ cleaned_node_source = source_without_keyword_argument(node, required_config)
36
+ corrector.replace(node, cleaned_node_source)
37
+ end
38
38
  end
39
39
  end
40
40
  end
@@ -48,13 +48,21 @@ module GraphQL
48
48
  # @param directives [Hash{Class => Hash}]
49
49
  # @param deprecation_reason [String]
50
50
  # @param validates [Hash, nil] Options for building validators, if any should be applied
51
- def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type: nil, name: nil, loads: nil, description: nil, ast_node: nil, default_value: NO_DEFAULT, as: nil, from_resolver: false, camelize: true, prepare: nil, method_access: true, owner:, validates: nil, directives: nil, deprecation_reason: nil, &definition_block)
51
+ # @param replace_null_with_default [Boolean] if `true`, incoming values of `null` will be replaced with the configured `default_value`
52
+ def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type: nil, name: nil, loads: nil, description: nil, ast_node: nil, default_value: NO_DEFAULT, as: nil, from_resolver: false, camelize: true, prepare: nil, method_access: true, owner:, validates: nil, directives: nil, deprecation_reason: nil, replace_null_with_default: false, &definition_block)
52
53
  arg_name ||= name
53
54
  @name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s)
54
55
  @type_expr = type_expr || type
55
56
  @description = desc || description
56
57
  @null = required != true
57
58
  @default_value = default_value
59
+ if replace_null_with_default
60
+ if !default_value?
61
+ raise ArgumentError, "`replace_null_with_default: true` requires a default value, please provide one with `default_value: ...`"
62
+ end
63
+ @replace_null_with_default = true
64
+ end
65
+
58
66
  @owner = owner
59
67
  @as = as
60
68
  @loads = loads
@@ -97,6 +105,10 @@ module GraphQL
97
105
  @default_value != NO_DEFAULT
98
106
  end
99
107
 
108
+ def replace_null_with_default?
109
+ @replace_null_with_default
110
+ end
111
+
100
112
  attr_writer :description
101
113
 
102
114
  # @return [String] Documentation for this argument
@@ -253,6 +265,11 @@ module GraphQL
253
265
  return
254
266
  end
255
267
 
268
+ if value.nil? && replace_null_with_default?
269
+ value = default_value
270
+ default_used = true
271
+ end
272
+
256
273
  loaded_value = nil
257
274
  coerced_value = context.schema.error_handler.with_error_handling(context) do
258
275
  type.coerce_input(value, context)
@@ -38,7 +38,9 @@ module GraphQL
38
38
 
39
39
  # @return [Class] The GraphQL type this field belongs to. (For fields defined on mutations, it's the payload type)
40
40
  def owner_type
41
- @owner_type ||= if owner < GraphQL::Schema::Mutation
41
+ @owner_type ||= if owner.nil?
42
+ raise GraphQL::InvariantError, "Field #{original_name.inspect} (graphql name: #{graphql_name.inspect}) has no owner, but all fields should have an owner. How did this happen?!"
43
+ elsif owner < GraphQL::Schema::Mutation
42
44
  owner.payload_type
43
45
  else
44
46
  owner
@@ -188,6 +190,7 @@ module GraphQL
188
190
  # @param deprecation_reason [String] If present, the field is marked "deprecated" with this message
189
191
  # @param method [Symbol] The method to call on the underlying object to resolve this field (defaults to `name`)
190
192
  # @param hash_key [String, Symbol] The hash key to lookup on the underlying object (if its a Hash) to resolve this field (defaults to `name` or `name.to_s`)
193
+ # @param dig [Array<String, Symbol>] The nested hash keys to lookup on the underlying hash to resolve this field using dig
191
194
  # @param resolver_method [Symbol] The method on the type to call to resolve this field (defaults to `name`)
192
195
  # @param connection [Boolean] `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name
193
196
  # @param connection_extension [Class] The extension to add, to implement connections. If `nil`, no extension is added.
@@ -210,7 +213,7 @@ module GraphQL
210
213
  # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
211
214
  # @param validates [Array<Hash>] Configurations for validating this field
212
215
  # @param legacy_edge_class [Class, nil] (DEPRECATED) If present, pass this along to the legacy field definition
213
- def initialize(type: nil, name: nil, owner: nil, null: true, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: nil, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, legacy_edge_class: nil, &definition_block)
216
+ def initialize(type: nil, name: nil, owner: nil, null: true, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: nil, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, legacy_edge_class: nil, &definition_block)
214
217
  if name.nil?
215
218
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
216
219
  end
@@ -236,8 +239,8 @@ module GraphQL
236
239
  @resolve = resolve
237
240
  self.deprecation_reason = deprecation_reason
238
241
 
239
- if method && hash_key
240
- raise ArgumentError, "Provide `method:` _or_ `hash_key:`, not both. (called with: `method: #{method.inspect}, hash_key: #{hash_key.inspect}`)"
242
+ if method && hash_key && dig
243
+ raise ArgumentError, "Provide `method:`, `hash_key:` _or_ `dig:`, not multiple. (called with: `method: #{method.inspect}, hash_key: #{hash_key.inspect}, dig: #{dig.inspect}`)"
241
244
  end
242
245
 
243
246
  if resolver_method
@@ -245,13 +248,14 @@ module GraphQL
245
248
  raise ArgumentError, "Provide `method:` _or_ `resolver_method:`, not both. (called with: `method: #{method.inspect}, resolver_method: #{resolver_method.inspect}`)"
246
249
  end
247
250
 
248
- if hash_key
249
- raise ArgumentError, "Provide `hash_key:` _or_ `resolver_method:`, not both. (called with: `hash_key: #{hash_key.inspect}, resolver_method: #{resolver_method.inspect}`)"
251
+ if hash_key || dig
252
+ raise ArgumentError, "Provide `hash_key:`, `dig:`, _or_ `resolver_method:`, not multiple. (called with: `hash_key: #{hash_key.inspect}, dig: #{dig.inspect}, resolver_method: #{resolver_method.inspect}`)"
250
253
  end
251
254
  end
252
255
 
253
256
  # TODO: I think non-string/symbol hash keys are wrongly normalized (eg `1` will not work)
254
257
  method_name = method || hash_key || name_s
258
+ @dig_keys = dig
255
259
  resolver_method ||= name_s.to_sym
256
260
 
257
261
  @method_str = -method_name.to_s
@@ -822,7 +826,9 @@ module GraphQL
822
826
  end
823
827
  elsif obj.object.is_a?(Hash)
824
828
  inner_object = obj.object
825
- if inner_object.key?(@method_sym)
829
+ if @dig_keys
830
+ inner_object.dig(*@dig_keys)
831
+ elsif inner_object.key?(@method_sym)
826
832
  inner_object[@method_sym]
827
833
  else
828
834
  inner_object[@method_str]
@@ -1258,11 +1258,7 @@ module GraphQL
1258
1258
  when Module
1259
1259
  type_or_name
1260
1260
  else
1261
- raise ArgumentError, <<-ERR
1262
- Invariant: unexpected field owner for #{field_name.inspect}: #{type_or_name.inspect} (#{type_or_name.class})
1263
-
1264
- This is probably a bug in GraphQL-Ruby, please report this error on GitHub: https://github.com/rmosolgo/graphql-ruby/issues/new?template=bug_report.md
1265
- ERR
1261
+ raise GraphQL::InvariantError, "Unexpected field owner for #{field_name.inspect}: #{type_or_name.inspect} (#{type_or_name.class})"
1266
1262
  end
1267
1263
 
1268
1264
  if parent_type.kind.fields? && (field = parent_type.get_field(field_name, context))
@@ -54,7 +54,14 @@ module GraphQL
54
54
  Time.iso8601(str_value)
55
55
  rescue ArgumentError, TypeError
56
56
  begin
57
- Date.iso8601(str_value).to_time
57
+ dt = Date.iso8601(str_value).to_time
58
+ # For compatibility, continue accepting dates given without times
59
+ # But without this, it would zero out given any time part of `str_value` (hours and/or minutes)
60
+ if dt.iso8601.start_with?(str_value)
61
+ dt
62
+ else
63
+ nil
64
+ end
58
65
  rescue ArgumentError, TypeError
59
66
  # Invalid input
60
67
  nil
@@ -83,7 +83,8 @@ module GraphQL
83
83
  end
84
84
 
85
85
  def visible?(ctx)
86
- node_type.visible?(ctx)
86
+ # if this is an abstract base class, there may be no `node_type`
87
+ node_type ? node_type.visible?(ctx) : super
87
88
  end
88
89
 
89
90
  # Set the default `node_nullable` for this class and its child classes. (Defaults to `true`.)
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.13.6"
3
+ VERSION = "1.13.7"
4
4
  end
data/lib/graphql.rb CHANGED
@@ -17,6 +17,17 @@ module GraphQL
17
17
  class Error < StandardError
18
18
  end
19
19
 
20
+ # This error is raised when GraphQL-Ruby encounters a situation
21
+ # that it *thought* would never happen. Please report this bug!
22
+ class InvariantError < Error
23
+ def initialize(message)
24
+ message += "
25
+
26
+ This is probably a bug in GraphQL-Ruby, please report this error on GitHub: https://github.com/rmosolgo/graphql-ruby/issues/new?template=bug_report.md"
27
+ super(message)
28
+ end
29
+ end
30
+
20
31
  class RequiredImplementationMissingError < Error
21
32
  end
22
33
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.13.6
4
+ version: 1.13.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-20 00:00:00.000000000 Z
11
+ date: 2022-01-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark-ips
@@ -245,11 +245,20 @@ files:
245
245
  - MIT-LICENSE
246
246
  - lib/generators/graphql/core.rb
247
247
  - lib/generators/graphql/enum_generator.rb
248
+ - lib/generators/graphql/field_extractor.rb
249
+ - lib/generators/graphql/input_generator.rb
250
+ - lib/generators/graphql/install/mutation_root_generator.rb
251
+ - lib/generators/graphql/install/templates/base_mutation.erb
252
+ - lib/generators/graphql/install/templates/mutation_type.erb
248
253
  - lib/generators/graphql/install_generator.rb
249
254
  - lib/generators/graphql/interface_generator.rb
250
255
  - lib/generators/graphql/loader_generator.rb
256
+ - lib/generators/graphql/mutation_create_generator.rb
257
+ - lib/generators/graphql/mutation_delete_generator.rb
251
258
  - lib/generators/graphql/mutation_generator.rb
259
+ - lib/generators/graphql/mutation_update_generator.rb
252
260
  - lib/generators/graphql/object_generator.rb
261
+ - lib/generators/graphql/orm_mutations_base.rb
253
262
  - lib/generators/graphql/relay.rb
254
263
  - lib/generators/graphql/relay_generator.rb
255
264
  - lib/generators/graphql/scalar_generator.rb
@@ -260,16 +269,18 @@ files:
260
269
  - lib/generators/graphql/templates/base_field.erb
261
270
  - lib/generators/graphql/templates/base_input_object.erb
262
271
  - lib/generators/graphql/templates/base_interface.erb
263
- - lib/generators/graphql/templates/base_mutation.erb
264
272
  - lib/generators/graphql/templates/base_object.erb
265
273
  - lib/generators/graphql/templates/base_scalar.erb
266
274
  - lib/generators/graphql/templates/base_union.erb
267
275
  - lib/generators/graphql/templates/enum.erb
268
276
  - lib/generators/graphql/templates/graphql_controller.erb
277
+ - lib/generators/graphql/templates/input.erb
269
278
  - lib/generators/graphql/templates/interface.erb
270
279
  - lib/generators/graphql/templates/loader.erb
271
280
  - lib/generators/graphql/templates/mutation.erb
272
- - lib/generators/graphql/templates/mutation_type.erb
281
+ - lib/generators/graphql/templates/mutation_create.erb
282
+ - lib/generators/graphql/templates/mutation_delete.erb
283
+ - lib/generators/graphql/templates/mutation_update.erb
273
284
  - lib/generators/graphql/templates/node_type.erb
274
285
  - lib/generators/graphql/templates/object.erb
275
286
  - lib/generators/graphql/templates/query_type.erb