graphql 0.0.1

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: c86e3ad590a80d6430b2fd781fb097e433d5e5c4
4
+ data.tar.gz: 60d41b9666ffaa6d63a73238fd572fdbbba38218
5
+ SHA512:
6
+ metadata.gz: 27b1a4571a57b15e35dff3dcab058d8f8b9926c374361a6df52930296a1be705a49a5b345e23ad128d73eb81675a0b798e0b225c0c5fc92904cb76ebf3aaccfe
7
+ data.tar.gz: 5594ef107005a1d7abca4e9f6099a6c4f515176679d4421126e7a332a433271f367d447a981263a89accd573b8060ec22b1ee7f71809cb5a8c23e19d534eccee
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2015 Robert Mosolgo
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/lib/graphql.rb ADDED
@@ -0,0 +1,26 @@
1
+ require "parslet"
2
+ require "active_support/core_ext/string/inflections"
3
+ require "active_support/core_ext/object/blank"
4
+
5
+ module GraphQL
6
+ VERSION = "0.0.1"
7
+
8
+ autoload(:CollectionEdge, "graphql/collection_edge")
9
+ autoload(:Parser, "graphql/parser")
10
+ autoload(:Query, "graphql/query")
11
+ autoload(:Node, "graphql/node")
12
+ autoload(:Transform, "graphql/transform")
13
+
14
+ module Syntax
15
+ autoload(:Call, "graphql/syntax/call")
16
+ autoload(:Edge, "graphql/syntax/edge")
17
+ autoload(:Field, "graphql/syntax/field")
18
+ autoload(:Node, "graphql/syntax/node")
19
+ end
20
+
21
+ PARSER = Parser.new
22
+ TRANSFORM = Transform.new
23
+
24
+ class FieldNotDefinedError < RuntimeError
25
+ end
26
+ end
@@ -0,0 +1,62 @@
1
+ class GraphQL::CollectionEdge
2
+ attr_accessor :fields, :edge_class, :calls, :fields
3
+
4
+ def initialize(items:, edge_class:)
5
+ @items = items
6
+ @edge_class = edge_class
7
+ end
8
+
9
+ def to_json
10
+ json = {}
11
+ fields.each do |field|
12
+ name = field.identifier
13
+ if name == "edges"
14
+ json["edges"] = edges(fields: field.fields)
15
+ else
16
+ json[name] = safe_send(name)
17
+ end
18
+ end
19
+ json
20
+ end
21
+
22
+ def count
23
+ @items.count
24
+ end
25
+
26
+ def apply_calls(unfiltered_items, call_hash)
27
+ # override this to apply calls to your items
28
+ unfiltered_items
29
+ end
30
+
31
+ def edges(fields:)
32
+ filtered_items = apply_calls(items, calls)
33
+ filtered_items.map do |item|
34
+ node = edge_class.new(item)
35
+ json = {}
36
+ fields.each do |field|
37
+ name = field.identifier
38
+ if name == "node" # it's magic
39
+ node.fields = field.fields
40
+ json[name] = node.to_json
41
+ else
42
+ json[name] = node.safe_send(name)
43
+ end
44
+ end
45
+ json
46
+ end
47
+ end
48
+
49
+ def safe_send(identifier)
50
+ if respond_to?(identifier)
51
+ public_send(identifier)
52
+ else
53
+ raise GraphQL::FieldNotDefinedError, "#{self.class.name}##{identifier} was requested, but it isn't defined."
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def items
60
+ @items
61
+ end
62
+ end
@@ -0,0 +1,61 @@
1
+ class GraphQL::Node
2
+ attr_accessor :fields
3
+
4
+ def initialize(target=nil)
5
+ # DONT EXPOSE Node#target! otherwise you might be able to access it
6
+ @target = target
7
+ end
8
+
9
+ def safe_send(identifier)
10
+ if respond_to?(identifier)
11
+ public_send(identifier)
12
+ else
13
+ raise GraphQL::FieldNotDefinedError, "#{self.class.name}##{identifier} was requested, but it isn't defined."
14
+ end
15
+ end
16
+
17
+ def to_json
18
+ json = {}
19
+ fields.each do |field|
20
+ name = field.identifier
21
+ if field.is_a?(GraphQL::Syntax::Field)
22
+ json[name] = safe_send(name)
23
+ elsif field.is_a?(GraphQL::Syntax::Edge)
24
+ edge = safe_send(field.identifier)
25
+ edge.calls = field.call_hash
26
+ edge.fields = field.fields
27
+ json[name] = edge.to_json
28
+ end
29
+ end
30
+ json
31
+ end
32
+
33
+
34
+ def self.call(argument)
35
+ raise NotImplementedError, "Implement #{name}#call(argument) to use this node as a call"
36
+ end
37
+
38
+ def self.field_reader(*field_names)
39
+ field_names.each do |field_name|
40
+ define_method(field_name) do
41
+ @target.public_send(field_name)
42
+ end
43
+ end
44
+ end
45
+
46
+ def self.edges(field_name, collection_class_name:, edge_class_name:)
47
+ define_method(field_name) do
48
+ collection_items = @target.send(field_name)
49
+ collection_class = Object.const_get(collection_class_name)
50
+ edge_class = Object.const_get(edge_class_name)
51
+ collection = collection_class.new(items: collection_items, edge_class: edge_class)
52
+ end
53
+ end
54
+
55
+
56
+ def self.cursor(field_name)
57
+ define_method "cursor" do
58
+ safe_send(field_name).to_s
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,20 @@
1
+ class GraphQL::Parser < Parslet::Parser
2
+ root(:node)
3
+
4
+ rule(:node) { space? >> call >> space? >> fields.as(:fields) }
5
+
6
+ rule(:fields) { str("{") >> space? >> ((edge | field) >> str(",").maybe >> space?).repeat(1) >> space? >> str("}") >> space?}
7
+
8
+ rule(:edge) { call_chain >> space? >> fields.as(:fields) }
9
+ rule(:call_chain) { identifier >> (dot >> call).repeat(0).as(:calls) }
10
+
11
+ rule(:call) { identifier >> str("(") >> name.maybe.as(:argument) >> str(")") }
12
+ rule(:dot) { str(".") }
13
+
14
+ rule(:field) { identifier }
15
+
16
+ rule(:identifier) { name.as(:identifier) }
17
+ rule(:name) { match('\w').repeat(1) }
18
+ rule(:space) { match('[\s\n]+').repeat(1) }
19
+ rule(:space?) { space.maybe }
20
+ end
@@ -0,0 +1,41 @@
1
+ class GraphQL::Query
2
+ attr_reader :query_string, :root, :namespace
3
+ def initialize(query_string, namespace: Object)
4
+ if !query_string.is_a?(String) || query_string.length == 0
5
+ raise "You must send a query string, not a #{query_string.class.name}"
6
+ end
7
+ @query_string = query_string
8
+ @root = parse(query_string)
9
+ @namespace = namespace
10
+ end
11
+
12
+ def to_json
13
+ root_node = make_call(nil, root.identifier, root.argument)
14
+ raise "Couldn't find root for #{root.identifier}(#{root.argument})" if root.nil?
15
+
16
+ root_node.fields = root.fields
17
+ {
18
+ root_node.cursor => root_node.to_json
19
+ }
20
+ end
21
+
22
+ def get_node(identifier)
23
+ name = "#{identifier}_node"
24
+ namespace.const_get(name.camelize)
25
+ end
26
+
27
+ def make_call(context, name, *arguments)
28
+ if context.nil?
29
+ context = get_node(name)
30
+ name = "call"
31
+ end
32
+ context.send(name, *arguments)
33
+ end
34
+
35
+ private
36
+
37
+ def parse(query_string)
38
+ parsed_hash = GraphQL::PARSER.parse(query_string)
39
+ root_node = GraphQL::TRANSFORM.apply(parsed_hash)
40
+ end
41
+ end
@@ -0,0 +1,17 @@
1
+ class GraphQL::Syntax::Call
2
+ attr_reader :identifier, :argument, :calls
3
+ def initialize(identifier:, argument: nil, calls: [])
4
+ @identifier = identifier
5
+ @argument = argument
6
+ @calls = calls
7
+ end
8
+
9
+ def execute!(query)
10
+ node_class = query.get_node(identifier)
11
+ node_class.call(argument)
12
+ end
13
+
14
+ def to_query
15
+ (["#{identifier}(#{argument})"] + calls.map(&:to_query)).join(".")
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ class GraphQL::Syntax::Edge
2
+ attr_reader :identifier, :fields, :calls
3
+ def initialize(identifier:, fields:, calls:)
4
+ @identifier = identifier
5
+ @calls = calls
6
+ @fields = fields
7
+ end
8
+
9
+ def call_hash
10
+ calls.inject({}) { |memo, call| memo[call.identifier] = call.argument; memo }
11
+ end
12
+ end
@@ -0,0 +1,7 @@
1
+ class GraphQL::Syntax::Field
2
+ attr_reader :identifier, :calls
3
+ def initialize(identifier:, calls: [])
4
+ @identifier = identifier
5
+ @calls = calls
6
+ end
7
+ end
@@ -0,0 +1,15 @@
1
+ class GraphQL::Syntax::Node
2
+ attr_reader :identifier, :argument, :fields
3
+ def initialize(identifier:, argument:, fields: [])
4
+ @identifier = identifier
5
+ @argument = argument
6
+ @fields = fields
7
+ end
8
+
9
+ def execute!(query)
10
+ obj = identifier.execute!(query)
11
+ fields.each do |field|
12
+ obj.apply_field(field)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ class GraphQL::Transform < Parslet::Transform
2
+ # node
3
+ rule(identifier: simple(:i), argument: simple(:a), fields: sequence(:f)) {GraphQL::Syntax::Node.new(identifier: i.to_s, argument: a, fields: f)}
4
+ # edge
5
+ rule(identifier: simple(:i), calls: sequence(:c), fields: sequence(:f)) { GraphQL::Syntax::Edge.new(identifier: i.to_s, fields: f, calls: c)}
6
+ # field
7
+ rule(identifier: simple(:i)) { GraphQL::Syntax::Field.new(identifier: i.to_s)}
8
+ # call
9
+ rule(identifier: simple(:i), argument: simple(:a)) { GraphQL::Syntax::Call.new(identifier: i.to_s, argument: a) }
10
+ end
data/readme.md ADDED
@@ -0,0 +1,18 @@
1
+ # graphql
2
+
3
+ - Parser & tranform from [parslet](http://kschiess.github.io/parslet/)
4
+ - Your app can implement nodes
5
+ - You can pass strings to `GraphQL::Query` and execute them with your nodes
6
+
7
+ See `/spec/support/dummy_app/nodes.rb` for node examples
8
+
9
+ __Nodes__ provide information to queries by mapping to application objects (via `.call` and `field_reader`) or implementing fields themselves (eg `Nodes::PostNode#teaser`).
10
+
11
+ __Edges__ handle node-to-node relationships.
12
+
13
+
14
+ ## To do:
15
+
16
+ - Better class inference. Declaring edge classes is stupid.
17
+ - How to authenticate?
18
+ - What do graphql mutation queries even look like?
@@ -0,0 +1,89 @@
1
+ require 'spec_helper'
2
+
3
+ describe GraphQL::Parser do
4
+ let(:node_name) { "" }
5
+ let(:fields) { "id, name"}
6
+ let(:query) { "#{node_name} { #{fields} }"}
7
+ let(:parser) { GraphQL::PARSER }
8
+
9
+ describe 'field' do
10
+ let(:field) { parser.field }
11
+ it 'finds words' do
12
+ assert field.parse_with_debug("name")
13
+ assert field.parse_with_debug("date_of_birth")
14
+ end
15
+ end
16
+
17
+ describe 'edge' do
18
+ let(:edge) { parser.edge }
19
+
20
+ it 'finds calls on fields' do
21
+ assert edge.parse_with_debug("friends.first(1) {
22
+ count,
23
+ edges {
24
+ cursor,
25
+ node {
26
+ name
27
+ }
28
+ }
29
+ }
30
+ ")
31
+ end
32
+ end
33
+
34
+ describe 'call' do
35
+ let(:call) { parser.call }
36
+ it 'finds bare calls' do
37
+ assert call.parse_with_debug("node(123)")
38
+ assert call.parse_with_debug("viewer()")
39
+ end
40
+ end
41
+
42
+ describe 'call_chain' do
43
+ let(:call_chain) { parser.call_chain }
44
+ it 'finds deep calls' do
45
+ assert call_chain.parse_with_debug("friends.after(123).first(2)")
46
+ end
47
+ it 'finds chain with no calls' do
48
+ assert call_chain.parse_with_debug("friends")
49
+ end
50
+ end
51
+
52
+ describe 'fields' do
53
+ let(:fields) { parser.fields }
54
+
55
+ it 'finds fields' do
56
+ assert fields.parse_with_debug("{id,name}")
57
+ assert fields.parse_with_debug("{ id, name, favorite_food }")
58
+ assert fields.parse_with_debug("{\n id,\n name,\n favorite_food\n}")
59
+ end
60
+
61
+ it 'finds nested field list' do
62
+ assert fields.parse_with_debug("{id,date_of_birth{month, year}}")
63
+ end
64
+ end
65
+
66
+ describe 'node' do
67
+ let(:node) { parser.node }
68
+
69
+ it 'parses root calls' do
70
+ assert node.parse_with_debug("viewer() {id}")
71
+ end
72
+
73
+ it 'parses nested nodes' do
74
+ assert node.parse_with_debug("node(someone)
75
+ {
76
+ id,
77
+ name,
78
+ friends.after(12345).first(3) {
79
+ cursor,
80
+ node {
81
+ id,
82
+ name
83
+ }
84
+ }
85
+ }
86
+ ")
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,154 @@
1
+ require 'spec_helper'
2
+
3
+ describe GraphQL::Query do
4
+ let(:query_string) { "post(123) { title, content } "}
5
+ let(:namespace) { Nodes }
6
+ let(:query) { GraphQL::Query.new(query_string, namespace: namespace) }
7
+
8
+ describe '#root' do
9
+ it 'contains the first node of the graph' do
10
+ assert query.root.is_a?(GraphQL::Syntax::Node)
11
+ end
12
+ end
13
+
14
+ describe '#to_json' do
15
+ before do
16
+ @post = Post.create(id: 123, content: "So many great things", title: "My great post")
17
+ @comment1 = Comment.create(id: 444, post_id: 123, content: "I agree")
18
+ @comment2 = Comment.create(id: 445, post_id: 123, content: "I disagree")
19
+ end
20
+
21
+ after do
22
+ @post.destroy
23
+ @comment1.destroy
24
+ @comment2.destroy
25
+ end
26
+
27
+ it 'performs the root node call' do
28
+ assert_send([Nodes::PostNode, :call, "123"])
29
+ query.to_json
30
+ end
31
+
32
+ it 'finds fields that delegate to a target' do
33
+ assert_equal query.to_json, {
34
+ "123" => {
35
+ "title" => "My great post",
36
+ "content" => "So many great things"
37
+ }
38
+ }
39
+ end
40
+
41
+ describe 'when requesting fields defined on the node' do
42
+ let(:query_string) { "post(123) { teaser } "}
43
+ it 'finds fields defined on the node' do
44
+ assert_equal query.to_json, { "123" => { "teaser" => @post.content[0,10] + "..."}}
45
+ end
46
+ end
47
+
48
+
49
+ describe 'when requesting an undefined field' do
50
+ let(:query_string) { "post(123) { destroy } "}
51
+ it 'raises a FieldNotDefined error' do
52
+ assert_raises(GraphQL::FieldNotDefinedError) { query.to_json }
53
+ assert(Post.find(123).present?)
54
+ end
55
+ end
56
+
57
+ describe 'when the root call doesnt have an argument' do
58
+ let(:query_string) { "viewer() { name }"}
59
+ it 'calls the node with nil' do
60
+ assert_send([Nodes::ViewerNode, :call, nil])
61
+ query.to_json
62
+ end
63
+ end
64
+
65
+ describe 'when requesting a collection' do
66
+ let(:query_string) { "post(123) {
67
+ title,
68
+ comments {
69
+ count,
70
+ edges {
71
+ cursor,
72
+ node {
73
+ content
74
+ }
75
+ }
76
+ }
77
+ }"}
78
+ it 'returns collection data' do
79
+ assert_equal query.to_json, {
80
+ "123" => {
81
+ "title" => "My great post",
82
+ "comments" => {
83
+ "count" => 2,
84
+ "edges" => [
85
+ {
86
+ "cursor" => "444",
87
+ "node" => {
88
+ "content" => "I agree"
89
+ }
90
+ },
91
+ {
92
+ "cursor" => "445",
93
+ "node" => {
94
+ "content" => "I disagree"
95
+ }
96
+ }
97
+ ]
98
+ }
99
+ }
100
+ }
101
+ end
102
+ end
103
+
104
+ describe 'when making calls on a collection' do
105
+ let(:query_string) { "post(123) {
106
+ comments.first(1) {
107
+ edges { cursor, node { content } }
108
+ }
109
+ }"}
110
+
111
+ it 'executes those calls' do
112
+ assert_equal query.to_json, {
113
+ "123" => {
114
+ "comments" => {
115
+ "edges" => [
116
+ {
117
+ "cursor" => "444",
118
+ "node" => {
119
+ "content" => "I agree"
120
+ }
121
+ }
122
+ ]
123
+ }
124
+ }
125
+ }
126
+ end
127
+ end
128
+
129
+ describe 'when making DEEP calls on a collection' do
130
+ let(:query_string) { "post(123) {
131
+ comments.after(444).first(1) {
132
+ edges { cursor, node { content } }
133
+ }
134
+ }"}
135
+
136
+ it 'executes those calls' do
137
+ assert_equal query.to_json, {
138
+ "123" => {
139
+ "comments" => {
140
+ "edges" => [
141
+ {
142
+ "cursor" => "445",
143
+ "node" => {
144
+ "content" => "I disagree"
145
+ }
146
+ }
147
+ ]
148
+ }
149
+ }
150
+ }
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ describe GraphQL::Transform do
4
+ let(:transform) { GraphQL::TRANSFORM }
5
+ let(:parser) { GraphQL::PARSER }
6
+
7
+ describe '#apply' do
8
+ it 'turns a simple node into a Node' do
9
+ tree = parser.parse("post(123) { name }")
10
+ res = transform.apply(tree)
11
+ assert(res.is_a?(GraphQL::Syntax::Node), 'it gets a node')
12
+ end
13
+
14
+ it 'turns a node into a Node' do
15
+ tree = parser.parse("viewer() { name, friends.first(10) { birthdate } }")
16
+ res = transform.apply(tree)
17
+ assert(res.is_a?(GraphQL::Syntax::Node), 'it gets a node')
18
+ assert(res.identifier == "viewer")
19
+ assert(res.fields.length == 2)
20
+ assert(res.fields[0].is_a?(GraphQL::Syntax::Field), 'it gets a field')
21
+ assert(res.fields[1].is_a?(GraphQL::Syntax::Edge), 'it gets an edge')
22
+ end
23
+
24
+ it 'turns a field into a Field' do
25
+ tree = parser.field.parse("friends")
26
+ res = transform.apply(tree)
27
+ assert(res.is_a?(GraphQL::Syntax::Field))
28
+ assert(res.identifier == "friends")
29
+ end
30
+
31
+ it 'turns edge into an Edge' do
32
+ tree = parser.edge.parse("friends.after(123).first(2) { count, edges { node { name } } }")
33
+ res = transform.apply(tree)
34
+ assert(res.is_a?(GraphQL::Syntax::Edge), 'it gets the Edge')
35
+ assert(res.identifier == "friends")
36
+ assert(res.calls.length == 2, 'it tracks calls')
37
+ assert(res.calls[0].identifier == "after")
38
+ assert(res.calls[1].identifier == "first")
39
+ assert_equal(res.call_hash, {"after" => "123", "first" => "2"})
40
+ end
41
+
42
+ it 'turns call into a Call' do
43
+ tree = parser.call.parse("node(123)")
44
+ res = transform.apply(tree)
45
+ assert(res.is_a?(GraphQL::Syntax::Call))
46
+ assert(res.identifier == "node")
47
+ assert(res.argument == "123")
48
+ end
49
+
50
+ it 'turns a call without an argument into a Call' do
51
+ tree = parser.call.parse("viewer()")
52
+ res = transform.apply(tree)
53
+ assert(res.is_a?(GraphQL::Syntax::Call))
54
+ assert(res.identifier == "viewer")
55
+ assert(res.argument == nil)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,15 @@
1
+ require "minitest/autorun"
2
+ require "minitest/focus"
3
+ require "minitest/reporters"
4
+
5
+ Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
6
+
7
+ # Filter out Minitest backtrace while allowing backtrace from other libraries
8
+ # to be shown.
9
+ Minitest.backtrace_filter = Minitest::BacktraceFilter.new
10
+
11
+ # # Load support files
12
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
13
+
14
+ require 'parslet'
15
+ require 'parslet/convenience'
@@ -0,0 +1,61 @@
1
+ class InadequateRecordBase
2
+ def initialize(attributes={})
3
+ attributes.each do |key, value|
4
+ self.send("#{key}=", value)
5
+ end
6
+ end
7
+
8
+ def destroy
9
+ self.class.objects.delete(self)
10
+ end
11
+
12
+ class << self
13
+ attr_accessor :_objects
14
+ def objects
15
+ @_objects ||= []
16
+ end
17
+ end
18
+
19
+ def self.find(id)
20
+ objects.find { |object| object.id.to_s == id.to_s}
21
+ end
22
+
23
+ def self.where(query={})
24
+ result = []
25
+ objects.each do |object|
26
+ match = true
27
+
28
+ query.each do |key, value|
29
+ if object.send(key) != value
30
+ match = false
31
+ end
32
+ end
33
+
34
+ result << object if match
35
+ end
36
+ result
37
+ end
38
+
39
+ def self.create(attributes)
40
+ instance = self.new(attributes)
41
+ objects << instance
42
+ instance
43
+ end
44
+ end
45
+
46
+ class Post < InadequateRecordBase
47
+ attr_accessor :id, :title, :content
48
+
49
+ def comments
50
+ Comment.where(post_id: id)
51
+ end
52
+ end
53
+
54
+ class Comment < InadequateRecordBase
55
+ attr_accessor :id, :post_id, :content
56
+ def post
57
+ Post.find(post_id)
58
+ end
59
+ end
60
+
61
+
@@ -0,0 +1,59 @@
1
+ module Nodes
2
+ class PostNode < GraphQL::Node
3
+ field_reader :id, :title, :content
4
+ cursor :id
5
+
6
+ edges :comments,
7
+ collection_class_name: "Nodes::ApplicationCollectionEdge",
8
+ edge_class_name: "Nodes::CommentNode"
9
+
10
+ def teaser
11
+ content.length > 10 ? "#{content[0..9]}..." : content
12
+ end
13
+
14
+ def self.call(argument)
15
+ post = Post.find(argument.to_i)
16
+ self.new(post)
17
+ end
18
+ end
19
+
20
+ class CommentNode < GraphQL::Node
21
+ field_reader :id, :post, :content
22
+ cursor :id
23
+
24
+ def self.call(argument)
25
+ obj = Comment.find(argument)
26
+ self.new(obj)
27
+ end
28
+ end
29
+
30
+ class ViewerNode < GraphQL::Node
31
+ def name
32
+ "It's you again"
33
+ end
34
+
35
+ def cursor
36
+ "viewer"
37
+ end
38
+
39
+ def self.call(argument)
40
+ self.new
41
+ end
42
+ end
43
+
44
+ class ApplicationCollectionEdge < GraphQL::CollectionEdge
45
+ def apply_calls(items, calls)
46
+ filtered_items = items
47
+
48
+ if calls["after"].present?
49
+ filtered_items = filtered_items.select {|i| i.id > calls["after"].to_i }
50
+ end
51
+
52
+ if calls["first"].present?
53
+ filtered_items = filtered_items.first(calls["first"].to_i)
54
+ end
55
+
56
+ filtered_items
57
+ end
58
+ end
59
+ end
metadata ADDED
@@ -0,0 +1,180 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: graphql
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Robert Mosolgo
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-01-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: parslet
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.6.2
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 1.6.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: guard
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: guard-bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard-minitest
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: minitest
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: minitest-focus
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: minitest-reporters
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: A GraphQL adapter for Ruby
126
+ email:
127
+ - rdmosolgo@gmail.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - MIT-LICENSE
133
+ - lib/graphql.rb
134
+ - lib/graphql/collection_edge.rb
135
+ - lib/graphql/node.rb
136
+ - lib/graphql/parser.rb
137
+ - lib/graphql/query.rb
138
+ - lib/graphql/syntax/call.rb
139
+ - lib/graphql/syntax/edge.rb
140
+ - lib/graphql/syntax/field.rb
141
+ - lib/graphql/syntax/node.rb
142
+ - lib/graphql/transform.rb
143
+ - readme.md
144
+ - spec/graphql/parser_spec.rb
145
+ - spec/graphql/query_spec.rb
146
+ - spec/graphql/transform_spec.rb
147
+ - spec/spec_helper.rb
148
+ - spec/support/dummy_app.rb
149
+ - spec/support/nodes.rb
150
+ homepage: http://github.com/rmosolgo/graphql
151
+ licenses:
152
+ - MIT
153
+ metadata: {}
154
+ post_install_message:
155
+ rdoc_options: []
156
+ require_paths:
157
+ - lib
158
+ required_ruby_version: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - ">="
161
+ - !ruby/object:Gem::Version
162
+ version: '0'
163
+ required_rubygems_version: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - ">="
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ requirements: []
169
+ rubyforge_project:
170
+ rubygems_version: 2.2.2
171
+ signing_key:
172
+ specification_version: 4
173
+ summary: GraphQL
174
+ test_files:
175
+ - spec/graphql/parser_spec.rb
176
+ - spec/graphql/query_spec.rb
177
+ - spec/graphql/transform_spec.rb
178
+ - spec/spec_helper.rb
179
+ - spec/support/dummy_app.rb
180
+ - spec/support/nodes.rb