graphql-activerecord 0.12.6 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.rubocop.yml +581 -11
- data/CHANGELOG.md +6 -0
- data/Gemfile +1 -0
- data/README.md +69 -37
- data/Rakefile +1 -0
- data/bin/bundle +105 -0
- data/bin/coderay +12 -0
- data/bin/htmldiff +12 -0
- data/bin/ldiff +12 -0
- data/bin/pry +12 -0
- data/bin/rake +12 -0
- data/bin/rspec +12 -0
- data/bin/rubocop +12 -0
- data/bin/ruby-parse +12 -0
- data/bin/ruby-rewrite +12 -0
- data/graphql-activerecord.gemspec +6 -6
- data/lib/graphql/activerecord.rb +6 -2
- data/lib/graphql/models/active_record_extension.rb +2 -1
- data/lib/graphql/models/association_load_request.rb +1 -0
- data/lib/graphql/models/attribute_loader.rb +1 -0
- data/lib/graphql/models/backed_by_model.rb +1 -0
- data/lib/graphql/models/database_types.rb +1 -0
- data/lib/graphql/models/definer.rb +1 -0
- data/lib/graphql/models/definition_helpers.rb +3 -2
- data/lib/graphql/models/definition_helpers/associations.rb +3 -2
- data/lib/graphql/models/definition_helpers/attributes.rb +1 -0
- data/lib/graphql/models/hash_combiner.rb +1 -0
- data/lib/graphql/models/helpers.rb +1 -0
- data/lib/graphql/models/instrumentation.rb +6 -4
- data/lib/graphql/models/monkey_patches/graphql_query_context.rb +1 -0
- data/lib/graphql/models/mutation_field_map.rb +9 -7
- data/lib/graphql/models/mutation_helpers/apply_changes.rb +16 -5
- data/lib/graphql/models/mutation_helpers/authorization.rb +1 -0
- data/lib/graphql/models/mutation_helpers/print_input_fields.rb +4 -3
- data/lib/graphql/models/mutation_helpers/validation.rb +1 -0
- data/lib/graphql/models/mutation_helpers/validation_error.rb +1 -0
- data/lib/graphql/models/mutator.rb +3 -2
- data/lib/graphql/models/promise_relation_connection.rb +1 -0
- data/lib/graphql/models/reflection.rb +2 -0
- data/lib/graphql/models/relation_load_request.rb +1 -0
- data/lib/graphql/models/relation_loader.rb +1 -0
- data/lib/graphql/models/version.rb +2 -1
- metadata +23 -22
data/bin/rspec
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
|
+
|
3
4
|
#
|
4
5
|
# This file was generated by Bundler.
|
5
6
|
#
|
@@ -11,6 +12,17 @@ require "pathname"
|
|
11
12
|
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
12
13
|
Pathname.new(__FILE__).realpath)
|
13
14
|
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
14
26
|
require "rubygems"
|
15
27
|
require "bundler/setup"
|
16
28
|
|
data/bin/rubocop
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
|
+
|
3
4
|
#
|
4
5
|
# This file was generated by Bundler.
|
5
6
|
#
|
@@ -11,6 +12,17 @@ require "pathname"
|
|
11
12
|
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
12
13
|
Pathname.new(__FILE__).realpath)
|
13
14
|
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
14
26
|
require "rubygems"
|
15
27
|
require "bundler/setup"
|
16
28
|
|
data/bin/ruby-parse
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
|
+
|
3
4
|
#
|
4
5
|
# This file was generated by Bundler.
|
5
6
|
#
|
@@ -11,6 +12,17 @@ require "pathname"
|
|
11
12
|
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
12
13
|
Pathname.new(__FILE__).realpath)
|
13
14
|
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
14
26
|
require "rubygems"
|
15
27
|
require "bundler/setup"
|
16
28
|
|
data/bin/ruby-rewrite
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
|
+
|
3
4
|
#
|
4
5
|
# This file was generated by Bundler.
|
5
6
|
#
|
@@ -11,6 +12,17 @@ require "pathname"
|
|
11
12
|
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
12
13
|
Pathname.new(__FILE__).realpath)
|
13
14
|
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
14
26
|
require "rubygems"
|
15
27
|
require "bundler/setup"
|
16
28
|
|
@@ -1,6 +1,6 @@
|
|
1
|
-
# coding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
|
-
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
5
|
require 'graphql/models/version'
|
6
6
|
|
@@ -20,14 +20,14 @@ 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_runtime_dependency "activesupport", ">= 4.2", '< 6'
|
24
23
|
spec.add_runtime_dependency "activerecord", ">= 4.2", '< 6'
|
25
|
-
spec.add_runtime_dependency "
|
24
|
+
spec.add_runtime_dependency "activesupport", ">= 4.2", '< 6'
|
25
|
+
spec.add_runtime_dependency "graphql", ">= 1.7.5", '< 2'
|
26
26
|
spec.add_runtime_dependency "graphql-batch", ">= 0.2.4"
|
27
27
|
|
28
28
|
spec.add_development_dependency "bundler", "~> 1.11"
|
29
|
+
spec.add_development_dependency "pry"
|
29
30
|
spec.add_development_dependency "rake", "~> 10.0"
|
30
31
|
spec.add_development_dependency "rspec", "~> 3.0"
|
31
|
-
spec.add_development_dependency "
|
32
|
-
spec.add_development_dependency "rubocop", '~> 0.47.1'
|
32
|
+
spec.add_development_dependency "rubocop"
|
33
33
|
end
|
data/lib/graphql/activerecord.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'active_support'
|
3
4
|
require 'active_record'
|
4
5
|
require 'graphql'
|
@@ -39,6 +40,7 @@ module GraphQL
|
|
39
40
|
module Models
|
40
41
|
class << self
|
41
42
|
attr_accessor :model_from_id, :authorize, :id_for_model, :model_to_graphql_type, :unknown_scalar
|
43
|
+
attr_accessor :legacy_nulls
|
42
44
|
end
|
43
45
|
|
44
46
|
# Returns a promise that will traverse the associations and resolve to the model at the end of the path.
|
@@ -72,11 +74,13 @@ module GraphQL
|
|
72
74
|
authorize.call(context, model, action)
|
73
75
|
end
|
74
76
|
|
75
|
-
def self.define_mutator(definer, model_type, null_behavior
|
77
|
+
def self.define_mutator(definer, model_type, null_behavior: :leave_unchanged, legacy_nulls: GraphQL::Models.legacy_nulls, &block)
|
78
|
+
legacy_nulls ||= false
|
79
|
+
|
76
80
|
# HACK: To get the name of the mutation, to avoid possible collisions with other type names
|
77
81
|
prefix = definer.instance_variable_get(:@target).name
|
78
82
|
|
79
|
-
mutator_definition = MutatorDefinition.new(model_type, null_behavior: null_behavior)
|
83
|
+
mutator_definition = MutatorDefinition.new(model_type, null_behavior: null_behavior, legacy_nulls: legacy_nulls)
|
80
84
|
mutator_definition.field_map.instance_exec(&block)
|
81
85
|
MutationHelpers.print_input_fields(mutator_definition.field_map, definer, "#{prefix}Input")
|
82
86
|
mutator_definition
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module GraphQL
|
3
4
|
module Models
|
4
5
|
module ActiveRecordExtension
|
@@ -24,7 +25,7 @@ module GraphQL
|
|
24
25
|
extend ::ActiveSupport::Concern
|
25
26
|
class_methods do
|
26
27
|
def graphql_enum_types
|
27
|
-
@
|
28
|
+
@graphql_enum_types ||= EnumTypeHash.new
|
28
29
|
end
|
29
30
|
|
30
31
|
# Defines a GraphQL enum type on the model
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'ostruct'
|
3
4
|
|
4
5
|
module GraphQL
|
@@ -66,7 +67,7 @@ module GraphQL
|
|
66
67
|
return false unless context
|
67
68
|
|
68
69
|
reflection = association.reflection
|
69
|
-
return false unless [
|
70
|
+
return false unless %i[has_one belongs_to].include?(reflection.macro)
|
70
71
|
|
71
72
|
if reflection.macro == :belongs_to
|
72
73
|
target_id = model.send(reflection.foreign_key)
|
@@ -123,7 +124,7 @@ module GraphQL
|
|
123
124
|
field_name = field_name.to_s
|
124
125
|
|
125
126
|
field_meta = graph_type.instance_variable_get(:@field_metadata)
|
126
|
-
field_meta
|
127
|
+
field_meta ||= graph_type.instance_variable_set(:@field_metadata, {})
|
127
128
|
field_meta[field_name] = OpenStruct.new(metadata).freeze
|
128
129
|
end
|
129
130
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module GraphQL
|
3
4
|
module Models
|
4
5
|
module DefinitionHelpers
|
@@ -6,7 +7,7 @@ module GraphQL
|
|
6
7
|
reflection = model_type.reflect_on_association(association)
|
7
8
|
raise ArgumentError, "Association #{association} wasn't found on model #{model_type.name}" unless reflection
|
8
9
|
raise ArgumentError, "Cannot proxy to polymorphic association #{association} on model #{model_type.name}" if reflection.polymorphic?
|
9
|
-
raise ArgumentError, "Cannot proxy to #{reflection.macro} association #{association} on model #{model_type.name}" unless [
|
10
|
+
raise ArgumentError, "Cannot proxy to #{reflection.macro} association #{association} on model #{model_type.name}" unless %i[has_one belongs_to].include?(reflection.macro)
|
10
11
|
|
11
12
|
return unless block_given?
|
12
13
|
|
@@ -56,7 +57,7 @@ module GraphQL
|
|
56
57
|
reflection = model_type.reflect_on_association(association)
|
57
58
|
|
58
59
|
raise ArgumentError, "Association #{association} wasn't found on model #{model_type.name}" unless reflection
|
59
|
-
raise ArgumentError, "Cannot include #{reflection.macro} association #{association} on model #{model_type.name} with has_one" unless [
|
60
|
+
raise ArgumentError, "Cannot include #{reflection.macro} association #{association} on model #{model_type.name} with has_one" unless %i[has_one belongs_to].include?(reflection.macro)
|
60
61
|
|
61
62
|
# Define the field for the association itself
|
62
63
|
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
class GraphQL::Models::Instrumentation
|
3
4
|
# @param skip_nil_models If true, field resolvers (in proxy_to or backed_by_model blocks) will not be invoked if the model is nil.
|
4
5
|
def initialize(skip_nil_models = true)
|
@@ -12,10 +13,11 @@ class GraphQL::Models::Instrumentation
|
|
12
13
|
old_resolver = field.resolve_proc
|
13
14
|
|
14
15
|
new_resolver = -> (object, args, ctx) {
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
Promise.resolve(field_info.object_to_base_model.call(object)).then do |base_model|
|
17
|
+
GraphQL::Models.load_association(base_model, field_info.path, ctx).then do |model|
|
18
|
+
next nil if model.nil? && @skip_nil_models
|
19
|
+
old_resolver.call(model, args, ctx)
|
20
|
+
end
|
19
21
|
end
|
20
22
|
}
|
21
23
|
|
@@ -1,14 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module GraphQL::Models
|
3
4
|
class MutationFieldMap
|
4
|
-
attr_accessor :model_type, :find_by, :null_behavior, :fields, :nested_maps
|
5
|
+
attr_accessor :model_type, :find_by, :null_behavior, :fields, :nested_maps, :legacy_nulls
|
5
6
|
|
6
7
|
# These are used when this is a proxy_to or a nested field map
|
7
8
|
attr_accessor :name, :association, :has_many, :required, :path
|
8
9
|
|
9
|
-
def initialize(model_type, find_by:, null_behavior:)
|
10
|
+
def initialize(model_type, find_by:, null_behavior:, legacy_nulls:)
|
10
11
|
raise ArgumentError, "model_type must be a model" if model_type && !(model_type <= ActiveRecord::Base)
|
11
|
-
raise ArgumentError, "null_behavior must be :set_null or :leave_unchanged" unless [
|
12
|
+
raise ArgumentError, "null_behavior must be :set_null or :leave_unchanged" unless %i[set_null leave_unchanged].include?(null_behavior)
|
12
13
|
|
13
14
|
@fields = []
|
14
15
|
@nested_maps = []
|
@@ -16,6 +17,7 @@ module GraphQL::Models
|
|
16
17
|
@model_type = model_type
|
17
18
|
@find_by = Array.wrap(find_by)
|
18
19
|
@null_behavior = null_behavior
|
20
|
+
@legacy_nulls = legacy_nulls
|
19
21
|
|
20
22
|
@find_by.each { |f| attr(f) }
|
21
23
|
end
|
@@ -65,7 +67,7 @@ module GraphQL::Models
|
|
65
67
|
reflection = model_type&.reflect_on_association(association)
|
66
68
|
|
67
69
|
if reflection
|
68
|
-
unless [
|
70
|
+
unless %i[belongs_to has_one].include?(reflection.macro)
|
69
71
|
raise ArgumentError, "Cannot proxy to #{reflection.macro} association #{association} from #{model_type.name}"
|
70
72
|
end
|
71
73
|
|
@@ -74,7 +76,7 @@ module GraphQL::Models
|
|
74
76
|
klass = nil
|
75
77
|
end
|
76
78
|
|
77
|
-
proxy = MutationFieldMap.new(klass, find_by: nil, null_behavior: null_behavior)
|
79
|
+
proxy = MutationFieldMap.new(klass, find_by: nil, null_behavior: null_behavior, legacy_nulls: legacy_nulls)
|
78
80
|
proxy.association = association
|
79
81
|
proxy.instance_exec(&block)
|
80
82
|
|
@@ -97,7 +99,7 @@ module GraphQL::Models
|
|
97
99
|
end
|
98
100
|
end
|
99
101
|
|
100
|
-
def nested(association, find_by: nil, null_behavior:, name: nil,
|
102
|
+
def nested(association, find_by: nil, null_behavior:, name: nil, &block)
|
101
103
|
unless model_type
|
102
104
|
raise ArgumentError, "Cannot use `nested` unless the model type is known at build time."
|
103
105
|
end
|
@@ -116,7 +118,7 @@ module GraphQL::Models
|
|
116
118
|
has_many = reflection.macro == :has_many
|
117
119
|
required = Reflection.is_required(model_type, association)
|
118
120
|
|
119
|
-
map = MutationFieldMap.new(reflection.klass, find_by: find_by, null_behavior: null_behavior)
|
121
|
+
map = MutationFieldMap.new(reflection.klass, find_by: find_by, null_behavior: null_behavior, legacy_nulls: legacy_nulls)
|
120
122
|
map.name = name || association.to_s.camelize(:lower)
|
121
123
|
map.association = association.to_s
|
122
124
|
map.has_many = has_many
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module GraphQL::Models
|
3
4
|
module MutationHelpers
|
4
5
|
def self.apply_changes(field_map, model, inputs, context)
|
@@ -7,7 +8,13 @@ module GraphQL::Models
|
|
7
8
|
|
8
9
|
# Values will now contain the list of inputs that we should actually act on. Any null values should actually
|
9
10
|
# be set to null, and missing fields should be skipped.
|
10
|
-
values = field_map.leave_null_unchanged?
|
11
|
+
values = if field_map.leave_null_unchanged? && field_map.legacy_nulls
|
12
|
+
prep_legacy_leave_unchanged(inputs)
|
13
|
+
elsif field_map.leave_null_unchanged?
|
14
|
+
prep_leave_unchanged(inputs)
|
15
|
+
else
|
16
|
+
prep_set_null(field_map, inputs)
|
17
|
+
end
|
11
18
|
|
12
19
|
values.each do |name, value|
|
13
20
|
field_def = field_map.fields.detect { |f| f[:name] == name }
|
@@ -232,10 +239,10 @@ module GraphQL::Models
|
|
232
239
|
end
|
233
240
|
end
|
234
241
|
|
235
|
-
# If the field map has the option leave_null_unchanged, there's an `unsetFields` string
|
236
|
-
# name of inputs that should be treated as if they are null. We handle that by removing null
|
237
|
-
# adding back any unsetFields with null values.
|
238
|
-
def self.
|
242
|
+
# If the field map has the option leave_null_unchanged, and legacy_nulls is true, then there's an `unsetFields` string
|
243
|
+
# array that contains the name of inputs that should be treated as if they are null. We handle that by removing null
|
244
|
+
# inputs, and then adding back any unsetFields with null values.
|
245
|
+
def self.prep_legacy_leave_unchanged(inputs)
|
239
246
|
# String key hash
|
240
247
|
values = inputs.to_h.compact
|
241
248
|
|
@@ -249,6 +256,10 @@ module GraphQL::Models
|
|
249
256
|
values
|
250
257
|
end
|
251
258
|
|
259
|
+
def self.prep_leave_unchanged(inputs)
|
260
|
+
inputs.to_h
|
261
|
+
end
|
262
|
+
|
252
263
|
# Field map has the option to set_null. Any field that has the value null, or is missing, will be set to null.
|
253
264
|
def self.prep_set_null(field_map, inputs)
|
254
265
|
values = inputs.to_h.compact
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module GraphQL::Models
|
3
4
|
module MutationHelpers
|
4
5
|
def self.print_input_fields(field_map, definer, map_name_prefix)
|
@@ -7,14 +8,14 @@ module GraphQL::Models
|
|
7
8
|
field_type = f[:type]
|
8
9
|
|
9
10
|
if f[:required] && !field_map.leave_null_unchanged?
|
10
|
-
field_type = field_type.to_non_null_type
|
11
|
+
field_type = field_type.to_non_null_type unless field_type.non_null?
|
11
12
|
end
|
12
13
|
|
13
14
|
input_field(f[:name], field_type)
|
14
15
|
end
|
15
16
|
|
16
|
-
if field_map.leave_null_unchanged?
|
17
|
-
field_names = field_map.fields.
|
17
|
+
if field_map.leave_null_unchanged? && field_map.legacy_nulls
|
18
|
+
field_names = field_map.fields.reject { |f| f[:required] }.map { |f| f[:name].to_s }
|
18
19
|
field_names += field_map.nested_maps.reject(&:required).map { |fld| fld.name.to_s }
|
19
20
|
field_names = field_names.sort
|
20
21
|
|