graphql-activerecord 0.12.6 → 0.13.0
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.
- 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
|
|