graphql_includable 0.1.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.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/graphql_includable.rb +131 -0
  3. metadata +47 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 13f8e1de4b38eac1e571c18ecc7ca78dbb16327f
4
+ data.tar.gz: 8b8318def58be25d950473392715d16f73b0d829
5
+ SHA512:
6
+ metadata.gz: c105a43187fb8d9db518cce6ca824ef96f17f279303c9a5c459e8708370ded44ee22c593d6b5d183dfb108fbafc442ef0bedf0d343a2eb1eff5f5e67c1dcaf9a
7
+ data.tar.gz: 2745bfa726da7f5ebc806afcc6c438d532a176f19ca9631894e3a62e9132ec814f4447012532359d2675acc370deb9265903aa6a020f7a16bc191d1a6a2d9318
@@ -0,0 +1,131 @@
1
+ require "graphql"
2
+ require "active_support/concern"
3
+
4
+ GraphQL::Field.accepts_definitions includes: GraphQL::Define.assign_metadata_key(:includes)
5
+
6
+ module GraphQLIncludable
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+ def includes_from_graphql(query_context)
11
+ generated_includes = GraphQLIncludable.generate_includes_from_graphql(query_context, self.model_name.to_s)
12
+ includes(generated_includes)
13
+ end
14
+
15
+ def delegate_cache
16
+ @delegate_cache ||= {}
17
+ end
18
+
19
+ # hook ActiveSupport's delegate method to track models and where their delegated methods go
20
+ def delegate(*methods, args)
21
+ methods.each do |method|
22
+ delegate_cache[method] = args[:to]
23
+ end
24
+ super(*methods, args) if defined?(super)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def self.generate_includes_from_graphql(query_context, model_name)
31
+ matching_node = GraphQLIncludable.find_child_node_matching_model_name(query_context.irep_node, model_name)
32
+ GraphQLIncludable.includes_from_irep_node(matching_node)
33
+ end
34
+
35
+ def self.find_child_node_matching_model_name(node, model_name)
36
+ matching_node = nil
37
+ return_type = unwrapped_type(node)
38
+ if return_type.to_s == model_name
39
+ matching_node = node
40
+ elsif node.respond_to? :scoped_children
41
+ node.scoped_children[return_type].each do |child_name, child_node|
42
+ matching_node = find_child_node_matching_model_name(child_node, model_name)
43
+ break if matching_node
44
+ end
45
+ end
46
+ matching_node
47
+ end
48
+
49
+ def self.includes_from_irep_node(node)
50
+ includes = []
51
+ nested_includes = {}
52
+
53
+ return_type = unwrapped_type(node)
54
+ return_model = node_return_class(node)
55
+ return [] unless node && return_type && return_model
56
+
57
+ node.scoped_children[return_type].each do |child_name, child_node|
58
+ child_association_name, explicit_includes = suggested_association_name(child_name, child_node)
59
+ association, delegated_model_name = find_association(return_model, child_association_name)
60
+ if association
61
+ child_includes = includes_from_irep_node(child_node)
62
+
63
+ if node_has_active_record_children(child_node) && child_includes.size > 0
64
+ nested_includes[delegated_model_name || child_association_name] = wrap_delegate(child_includes, delegated_model_name, child_association_name)
65
+ else
66
+ includes << wrap_delegate(child_association_name, delegated_model_name)
67
+ end
68
+ elsif explicit_includes
69
+ includes << explicit_includes
70
+ end
71
+ end
72
+
73
+ includes << nested_includes if nested_includes.size > 0
74
+ includes
75
+ end
76
+
77
+ def self.node_has_active_record_children(node)
78
+ node.scoped_children[unwrapped_type(node)].each do |child_return_name, child_node|
79
+ node_returns_active_record?(child_node)
80
+ end
81
+ end
82
+
83
+ def self.node_return_class(node)
84
+ begin
85
+ Object.const_get(unwrapped_type(node).name)
86
+ rescue NameError
87
+ end
88
+ end
89
+
90
+ def self.node_returns_active_record?(node)
91
+ klass = node_return_class(node)
92
+ klass && klass < ActiveRecord::Base
93
+ end
94
+
95
+ # return raw contents, or contents wrapped in a hash (for delegated associations)
96
+ def self.wrap_delegate(contents, delegate, delegate_key = delegate)
97
+ return contents unless delegate
98
+
99
+ obj = {}
100
+ obj[delegate_key] = contents
101
+ obj
102
+ end
103
+
104
+ # unwrap GraphQL ListType and NonNullType wrappers
105
+ def self.unwrapped_type(node)
106
+ type = node.return_type
107
+ type = type.of_type while type.respond_to? :of_type
108
+ type
109
+ end
110
+
111
+ # find a valid association on return_model, following method delegation
112
+ def self.find_association(return_model, child_name)
113
+ delegated_model = return_model.instance_variable_get('@delegate_cache').try(:[], child_name.to_sym)
114
+ association_model = delegated_model ? return_model.reflect_on_association(delegated_model).klass : return_model
115
+ association = association_model.reflect_on_association(child_name)
116
+
117
+ [association, delegated_model]
118
+ end
119
+
120
+ # find the association to look for based on a field definition
121
+ # precedence is `includes` key, then `property` key, then the field name
122
+ def self.suggested_association_name(name, node)
123
+ includes_metadata = node.definitions[0].metadata[:includes]
124
+
125
+ assoc_name = node.definitions[0].property || name
126
+ assoc_name = includes_metadata if includes_metadata && includes_metadata.is_a?(Symbol)
127
+
128
+ [assoc_name.to_sym, includes_metadata]
129
+ end
130
+
131
+ end
metadata ADDED
@@ -0,0 +1,47 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: graphql_includable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Dan Rouse
8
+ - Josh Vickery
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2017-08-28 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description:
15
+ email:
16
+ - dan.rouse@squarefoot.com
17
+ - jvickery@squarefoot.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - lib/graphql_includable.rb
23
+ homepage: https://github.com/thesquarefoot/graphql_includable
24
+ licenses:
25
+ - MIT
26
+ metadata: {}
27
+ post_install_message:
28
+ rdoc_options: []
29
+ require_paths:
30
+ - lib
31
+ required_ruby_version: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ required_rubygems_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ requirements: []
42
+ rubyforge_project:
43
+ rubygems_version: 2.6.10
44
+ signing_key:
45
+ specification_version: 4
46
+ summary: An ActiveSupport::Concern for GraphQL Ruby to eager-load query data
47
+ test_files: []