graphql-relay-walker 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: fea740ed6a86f1decc0f7ae7b45e47700d6a3044
4
+ data.tar.gz: 48376d689c94bc25f00c14364fc7eab80830187f
5
+ SHA512:
6
+ metadata.gz: 0469065f8d67ce7ce96517e8e63b20a582d5aadb49ac6ee9b752e4b576f4e8f7db97f8a32f114b2f2b66297f934bad70e762d972425ba05b8d22403a04058772
7
+ data.tar.gz: 03ac02451d15f05153203fd9b041bb5942ffd227b7c5dc242ebbb400f8cf25e006daa1084540eb5d754b5d637d38c44f40f97d8b8bfe41a421fa5646eadaec2c
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,33 @@
1
+ ## Contributing
2
+
3
+ [fork]: https://github.com/github/graphql-relay-walker/fork
4
+ [pr]: https://github.com/github/graphql-relay-walker/compare
5
+ [style]: https://github.com/styleguide/ruby
6
+ [code-of-conduct]: CODE_OF_CONDUCT.md
7
+
8
+ Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great.
9
+
10
+ Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms.
11
+
12
+ ## Submitting a pull request
13
+
14
+ 0. [Fork][fork] and clone the repository
15
+ 0. Configure and install the dependencies: `script/bootstrap`
16
+ 0. Make sure the tests pass on your machine: `rake`
17
+ 0. Create a new branch: `git checkout -b my-branch-name`
18
+ 0. Make your change, add tests, and make sure the tests still pass
19
+ 0. Push to your fork and [submit a pull request][pr]
20
+ 0. Pat your self on the back and wait for your pull request to be reviewed and merged.
21
+
22
+ Here are a few things you can do that will increase the likelihood of your pull request being accepted:
23
+
24
+ - Follow the [style guide][style].
25
+ - Write tests.
26
+ - Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests.
27
+ - Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
28
+
29
+ ## Resources
30
+
31
+ - [Contributing to Open Source on GitHub](https://guides.github.com/activities/contributing-to-open-source/)
32
+ - [Using Pull Requests](https://help.github.com/articles/using-pull-requests/)
33
+ - [GitHub Help](https://help.github.com)
data/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2016 GitHub, inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # GraphQL Relay Walker
2
+
3
+ ![](https://cloud.githubusercontent.com/assets/1144197/19287829/9ce479b8-8fc0-11e6-975c-8d686e3e0783.jpg)
4
+
5
+ `GraphQL::Relay::Walker` is a Ruby library that generates queries for walking a [Relay](https://facebook.github.io/relay/docs/graphql-relay-specification.html#content) [GraphQL](http://graphql.org/) schema. Given a `GraphQL::Schema`, you can walk from a given starting point, exercising any defined connections. This can be useful for various kinds of automated testing. Check out the [GitHub Walker](./examples/github_walker) example to see it in action.
6
+
7
+ ## Setup
8
+
9
+ You can install this library as a Ruby Gem:
10
+
11
+ ```bash
12
+ gem install graphql-relay-walker
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```ruby
18
+ require "graphql/relay/walker"
19
+
20
+ id = "<some starting node id>"
21
+ query = GraphQL::Relay::Walker.query_string(client.schema)
22
+
23
+
24
+ GraphQL::Relay::Walker.walk(from_id: id) do |frame|
25
+ # The global relay id of the object we're looking at.
26
+ frame.gid
27
+
28
+ # The frame where we discovered this object's GID.
29
+ frame.parent
30
+
31
+ # Execute the query and store the result in the frame.
32
+ # The implementation here is up to you, but you should set
33
+ # `frame.result` to the Hash result of executing the query.
34
+ frame.result = execute(query, variables: {"id" => frame.gid})
35
+
36
+ # Parse the results, adding any newly discovered IDs to our queue.
37
+ frame.enqueue_found_gids
38
+ end
39
+ ```
40
+
41
+ ## Usage with `GraphQL::Client`
42
+
43
+ Requiring `graphql/relay/walker/client_ext` will add a `GraphQL::Client#walk` method. This simplifies things by allowing the client to build and execute the query for you.
44
+
45
+ Here's how you would walk the [SWAPI GraphQL Wrapper](https://github.com/graphql/swapi-graphql), starting from Luke Skywalker, assuming a client configuration like [this](https://github.com/github/graphql-client/blob/2761908e735e6d34bf6056d26e97de54d384aa14/README.md#configuration).
46
+
47
+ ```ruby
48
+ require "graphql/relay/walker/client_ext"
49
+
50
+ skywalker_gid = "cGVvcGxlOjE="
51
+
52
+ SWAPI::Client.walk(from_id: skywalker_gid) do |frame|
53
+ # The global relay id of the object we're looking at.
54
+ frame.gid
55
+
56
+ # The frame where we discovered this object's GID.
57
+ frame.parent
58
+
59
+ # The result of executing the query for this frame's GID.
60
+ frame.result
61
+ end
62
+ ```
@@ -0,0 +1,13 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "graphql-relay-walker"
3
+ s.version = "0.0.2"
4
+ s.licenses = ["MIT"]
5
+ s.summary = "Traverse a Relay GraphQL graph"
6
+ s.authors = ["Ben Toews"]
7
+ s.email = "opensource+graphql-relay-walker@github.com"
8
+ s.files = %w(LICENSE.md README.md CONTRIBUTING.md CODE_OF_CONDUCT.md graphql-relay-walker.gemspec)
9
+ s.files += Dir.glob("lib/**/*.rb")
10
+ s.homepage = "https://github.com/github/graphql-relay-walker"
11
+
12
+ s.add_dependency "graphql", "~> 0.19"
13
+ end
@@ -0,0 +1,32 @@
1
+ module GraphQL::Relay
2
+ module Walker
3
+ # Build a query that starts with a relay node and grabs the IDs of all its
4
+ # connections and node fields.
5
+ #
6
+ # schema - The GraphQL::Schema to build a query for.
7
+ #
8
+ # Returns a String query.
9
+ def self.query_string(schema)
10
+ QueryBuilder.new(schema).query_string
11
+ end
12
+
13
+ # Start traversing a graph, starting from the given relay node ID.
14
+ #
15
+ # from_id: - The `ID!` id to start walking from.
16
+ # &blk - A block to call with each Walker::Queue::Frame that is visited.
17
+ # This block is responsible for executing a query for the frame's
18
+ # GID, storing the results in the frame, and enqueuing further
19
+ # node IDs to visit.
20
+ #
21
+ # Returns nothing.
22
+ def self.walk(from_id:, &blk)
23
+ queue = Queue.new
24
+ queue.add_gid(from_id)
25
+ queue.each_frame(&blk)
26
+ end
27
+
28
+ end
29
+ end
30
+
31
+ require "graphql/relay/walker/queue"
32
+ require "graphql/relay/walker/query_builder"
@@ -0,0 +1,29 @@
1
+ module GraphQL::Relay::Walker
2
+ module ClientExt
3
+ # Walk this client's graph from the given GID.
4
+ #
5
+ # from_id: - The String GID to start walking from.
6
+ # &blk - A block to call with each Walker::Queue::Frame that is visited.
7
+ #
8
+ # Returns nothing.
9
+ def walk(from_id:)
10
+ query_string = GraphQL::Relay::Walker.query_string(schema)
11
+ walker_query = parse(query_string)
12
+
13
+ GraphQL::Relay::Walker.walk(from_id: from_id) do |frame|
14
+ response = query(walker_query, variables: {"id" => frame.gid})
15
+ frame.context[:response] = response
16
+ frame.result = response.data.to_h
17
+ frame.enqueue_found_gids
18
+ yield(frame) if block_given?
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ begin
25
+ require "graphql/relay/walker"
26
+ require "graphql/client"
27
+ GraphQL::Client.send(:include, GraphQL::Relay::Walker::ClientExt)
28
+ rescue LoadError
29
+ end
@@ -0,0 +1,247 @@
1
+ module GraphQL::Relay::Walker
2
+ class QueryBuilder
3
+ DEFAULT_ARGUMENTS = {"first" => 5}
4
+ BASE_QUERY = "query($id: ID!) { node(id: $id) {} }"
5
+
6
+ attr_reader :schema, :connection_arguments, :ast
7
+
8
+ # Initialize a new QueryBuilder.
9
+ #
10
+ # schema - The GraphQL::Schema to build a query for.
11
+ # connection_arguments: - A Hash or arguments to use for connection fields
12
+ # (optional).
13
+ #
14
+ # Returns nothing.
15
+ def initialize(schema, connection_arguments: DEFAULT_ARGUMENTS)
16
+ @schema = schema
17
+ @connection_arguments = connection_arguments
18
+ @ast = build_query
19
+ end
20
+
21
+ # Get the built query.
22
+ #
23
+ # Returns a String.
24
+ def query_string
25
+ ast.to_query_string
26
+ end
27
+
28
+ private
29
+
30
+ # Build a query for our relay schema that selects an inline fragment for
31
+ # every node type. For every inline fragment, we select the ID of every node
32
+ # field and connection.
33
+ #
34
+ # Returns a GraphQL::Language::Nodes::Document instance.
35
+ def build_query
36
+ GraphQL.parse(BASE_QUERY).tap do |d_ast|
37
+ selections = d_ast.definitions.first.selections.first.selections
38
+
39
+ node_types.each do |type|
40
+ selections << inline_fragment_ast(type)
41
+ end
42
+
43
+ selections.compact!
44
+ end
45
+ end
46
+
47
+ # Private: Make a AST of the given type.
48
+ #
49
+ # klass - The GraphQL::Language::Nodes::AbstractNode subclass
50
+ # to create.
51
+ # needs_selections: - Boolean. Will this AST be invalid if it doesn't have
52
+ # any selections?
53
+ #
54
+ # Returns a GraphQL::Language::Nodes::AbstractNode subclass instance or nil
55
+ # if the created AST was invalid for having no selections.
56
+ def make(klass, needs_selections: true)
57
+ k_ast = klass.new
58
+ yield(k_ast) if block_given?
59
+ k_ast.selections.compact!
60
+
61
+ if k_ast.selections.empty? && needs_selections
62
+ nil
63
+ else
64
+ k_ast
65
+ end
66
+ end
67
+
68
+ # Make an inline fragment AST.
69
+ #
70
+ # type - The GraphQL::ObjectType instance to make the fragment
71
+ # for.
72
+ # with_children: - Boolean. Whether to select all children of this inline
73
+ # fragment, or just it's ID.
74
+ #
75
+ # Returns a GraphQL::Language::Nodes::InlineFragment instance or nil if the
76
+ # created AST was invalid for having no selections.
77
+ def inline_fragment_ast(type, with_children: true)
78
+ make(GraphQL::Language::Nodes::InlineFragment) do |if_ast|
79
+ if_ast.type = type.name
80
+
81
+ if with_children
82
+ type.all_fields.each do |field|
83
+ if node_field?(field)
84
+ if_ast.selections << node_field_ast(field)
85
+ elsif connection_field?(field)
86
+ if_ast.selections << connection_field_ast(field)
87
+ end
88
+ end
89
+ elsif id = type.get_field("id")
90
+ if_ast.selections << field_ast(id)
91
+ end
92
+ end
93
+ end
94
+
95
+ # Make a field AST.
96
+ #
97
+ # field - The GraphQL::Field instance to make the fragment for.
98
+ # arguments - A Hash of arguments to include in the field.
99
+ # &blk - A block to call with the AST and field type before returning
100
+ # the AST.
101
+ #
102
+ # Returns a GraphQL::Language::Nodes::Field instance or nil if the created
103
+ # AST was invalid for having no selections or missing required arguments.
104
+ def field_ast(field, arguments={}, &blk)
105
+ type = field.type.unwrap
106
+
107
+ # Bail unless we have the required arguments.
108
+ return unless field.arguments.reject do |_, arg|
109
+ arg.type.valid_input?(nil)
110
+ end.all? do |name, _|
111
+ arguments.key?(name)
112
+ end
113
+
114
+ make(GraphQL::Language::Nodes::Field, needs_selections: !type.kind.scalar?) do |f_ast|
115
+ f_ast.name = field.name
116
+ f_ast.alias = random_alias unless field.name == "id"
117
+ f_ast.arguments = arguments.map do |name, value|
118
+ GraphQL::Language::Nodes::Argument.new(name: name, value: value)
119
+ end
120
+
121
+ blk.call(f_ast, type) if blk
122
+ end
123
+ end
124
+
125
+ # Make a field AST for a node field.
126
+ #
127
+ # field - The GraphQL::Field instance to make the fragment for.
128
+ #
129
+ # Returns a GraphQL::Language::Nodes::Field instance.
130
+ def node_field_ast(field)
131
+ field_ast(field) do |f_ast, type|
132
+ selections = f_ast.selections
133
+
134
+ if type.kind.object?
135
+ selections << field_ast(type.get_field("id"))
136
+ else
137
+ possible_node_types(type).each do |if_type|
138
+ selections << inline_fragment_ast(if_type, with_children: false)
139
+ end
140
+ end
141
+ end
142
+ end
143
+
144
+ # Make a field AST for an edges field.
145
+ #
146
+ # field - The GraphQL::Field instance to make the fragment for.
147
+ #
148
+ # Returns a GraphQL::Language::Nodes::Field instance.
149
+ def edges_field_ast(field)
150
+ field_ast(field) do |f_ast, type|
151
+ f_ast.selections << node_field_ast(type.get_field("node"))
152
+ end
153
+ end
154
+
155
+ # Make a field AST for a connection field.
156
+ #
157
+ # field - The GraphQL::Field instance to make the fragment for.
158
+ #
159
+ # Returns a GraphQL::Language::Nodes::Field instance or nil if the created
160
+ # AST was invalid for missing required arguments.
161
+ def connection_field_ast(field)
162
+ field_ast(field, connection_arguments) do |f_ast, type|
163
+ f_ast.selections << edges_field_ast(type.get_field("edges"))
164
+ end
165
+ end
166
+
167
+ # Is this field for a relay node?
168
+ #
169
+ # field - A GraphQL::Field instance.
170
+ #
171
+ # Returns true if the field's type includes the `Node` interface or is a
172
+ # union or interface with a possible type that includes the `Node` interface
173
+ # Returns false otherwise.
174
+ def node_field?(field)
175
+ type = field.type.unwrap
176
+ kind = type.kind
177
+
178
+ if kind.object?
179
+ node_types.include?(type)
180
+ elsif kind.interface? || kind.union?
181
+ possible_node_types(type).any?
182
+ end
183
+ end
184
+
185
+ # Is this field for a relay connection?
186
+ #
187
+ # field - A GraphQL::Field instance.
188
+ #
189
+ # Returns true if this field's type has a `edges` field whose type has a
190
+ # `node` field that is a relay node. Returns false otherwise.
191
+ def connection_field?(field)
192
+ type = field.type.unwrap
193
+
194
+ if edges_field = type.get_field("edges")
195
+ edges = edges_field.type.unwrap
196
+ if node_field = edges.get_field("node")
197
+ return node_field?(node_field)
198
+ end
199
+ end
200
+
201
+ false
202
+ end
203
+
204
+ # Get the possible types of a union or interface.
205
+ #
206
+ # type - A GraphQL::UnionType or GraphQL::InterfaceType instance.
207
+ #
208
+ # Returns an Array of GraphQL::ObjectType instances.
209
+ def possible_types(type)
210
+ if type.kind.interface?
211
+ schema.possible_types(type)
212
+ elsif type.kind.union?
213
+ type.possible_types
214
+ end
215
+ end
216
+
217
+ # Get the possible types of a union or interface that are relay nodes.
218
+ #
219
+ # type - A GraphQL::UnionType or GraphQL::InterfaceType instance.
220
+ #
221
+ # Returns an Array of GraphQL::ObjectType instances.
222
+ def possible_node_types(type)
223
+ possible_types(type) & node_types
224
+ end
225
+
226
+ # Get the types that implement the `Node` interface.
227
+ #
228
+ # Returns an Array of GraphQL::ObjectType instances.
229
+ def node_types
230
+ schema.possible_types(node_interface)
231
+ end
232
+
233
+ # Get the `Node` interface.
234
+ #
235
+ # Returns a GraphQL::InterfaceType instance.
236
+ def node_interface
237
+ schema.types["Node"]
238
+ end
239
+
240
+ # Make a random alias for a field.
241
+ #
242
+ # Returns a six character random String.
243
+ def random_alias
244
+ 6.times.map { (SecureRandom.random_number(26) + 97).chr }.join
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,108 @@
1
+ module GraphQL::Relay::Walker
2
+ class Queue
3
+ attr_accessor :max_size, :random_idx
4
+
5
+ # Initialize a new Queue.
6
+ #
7
+ # max_size: - The maximum size the queue can grow to. This helps when
8
+ # walking a large graph by forcing us to walk deeper.
9
+ # random_idx: - Add frames to the queue at random indicies. This helps when
10
+ # walking a large graph by forcing us to walk deeper.
11
+ #
12
+ # Returns nothing.
13
+ def initialize(max_size: nil, random_idx: false)
14
+ @max_size = max_size
15
+ @random_idx = random_idx
16
+
17
+ @queue = []
18
+ @seen = Set.new
19
+ end
20
+
21
+ # Add a frame to the queue if its GID hasn't been seen already and the queue
22
+ # hasn't exceeded its max size.
23
+ #
24
+ # frame - The Queue::Frame to add to the queue.
25
+ #
26
+ # Returns true if the frame was added, false otherwise.
27
+ def add(frame)
28
+ return false if max_size && queue.length >= max_size
29
+ return false if @seen.include?(frame.gid)
30
+
31
+ @seen.add(frame.gid)
32
+ idx = random_idx ? rand(@queue.length + 1) : @queue.length
33
+ @queue.insert(idx, frame)
34
+
35
+ true
36
+ end
37
+
38
+ # Add a GID to the queue.
39
+ #
40
+ # gid - The String GID to add to the queue.
41
+ # parent - The frame where this GID was discovered (optional).
42
+ #
43
+ # Returns true if a frame was added, false otherwise.
44
+ def add_gid(gid, parent=nil)
45
+ frame = Frame.new(self, gid, parent)
46
+ add(frame)
47
+ end
48
+
49
+ # Iterate through the queue, yielding each frame.
50
+ #
51
+ # Returns nothing.
52
+ def each_frame
53
+ while frame = @queue.shift
54
+ yield(frame)
55
+ end
56
+ end
57
+ end
58
+
59
+ class Frame
60
+ attr_reader :queue, :gid, :parent, :context
61
+ attr_accessor :result
62
+
63
+ # Initialize a new Frame.
64
+ #
65
+ # queue - The Queue that this frame belongs to.
66
+ # gid - The String GID.
67
+ # parent - The Frame where this GID was discovered.
68
+ #
69
+ # Returns nothing.
70
+ def initialize(queue, gid, parent)
71
+ @queue = queue
72
+ @gid = gid
73
+ @parent = parent
74
+ @context = {}
75
+ end
76
+
77
+ # Add each found GID to the queue.
78
+ #
79
+ # Returns nothing.
80
+ def enqueue_found_gids
81
+ found_gids.each { |gid| queue.add(child(gid)) }
82
+ end
83
+
84
+ # Make a new frame with the given GID and this frame as its parent.
85
+ #
86
+ # gid - The String GID to create the frame with.
87
+ #
88
+ # Returns a Queue::Frame instance.
89
+ def child(gid)
90
+ Frame.new(queue, gid, self)
91
+ end
92
+
93
+ # The GIDs from this frame's results.
94
+ #
95
+ # Returns an Array of GID Strings.
96
+ def found_gids(data=result)
97
+ [].tap do |ids|
98
+ case data
99
+ when Hash
100
+ ids.concat(Array(data["id"]))
101
+ ids.concat(found_gids(data.values))
102
+ when Array
103
+ data.each { |datum| ids.concat(found_gids(datum)) }
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: graphql-relay-walker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Ben Toews
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-10-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: graphql
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.19'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.19'
27
+ description:
28
+ email: opensource+graphql-relay-walker@github.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - CODE_OF_CONDUCT.md
34
+ - CONTRIBUTING.md
35
+ - LICENSE.md
36
+ - README.md
37
+ - graphql-relay-walker.gemspec
38
+ - lib/graphql/relay/walker.rb
39
+ - lib/graphql/relay/walker/client_ext.rb
40
+ - lib/graphql/relay/walker/query_builder.rb
41
+ - lib/graphql/relay/walker/queue.rb
42
+ homepage: https://github.com/github/graphql-relay-walker
43
+ licenses:
44
+ - MIT
45
+ metadata: {}
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ requirements: []
61
+ rubyforge_project:
62
+ rubygems_version: 2.4.5.1
63
+ signing_key:
64
+ specification_version: 4
65
+ summary: Traverse a Relay GraphQL graph
66
+ test_files: []
67
+ has_rdoc: