graphql 0.0.1 → 0.0.2

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +78 -9
  3. data/lib/graphql/call.rb +7 -0
  4. data/lib/graphql/connection.rb +44 -0
  5. data/lib/graphql/field.rb +117 -0
  6. data/lib/graphql/field_definer.rb +29 -0
  7. data/lib/graphql/introspection/call_node.rb +13 -0
  8. data/lib/graphql/introspection/connection.rb +9 -0
  9. data/lib/graphql/introspection/field_node.rb +19 -0
  10. data/lib/graphql/introspection/root_call_argument_node.rb +5 -0
  11. data/lib/graphql/introspection/root_call_node.rb +16 -0
  12. data/lib/graphql/introspection/schema_call.rb +8 -0
  13. data/lib/graphql/introspection/schema_node.rb +17 -0
  14. data/lib/graphql/introspection/type_call.rb +8 -0
  15. data/lib/graphql/introspection/type_node.rb +16 -0
  16. data/lib/graphql/node.rb +141 -34
  17. data/lib/graphql/parser.rb +19 -8
  18. data/lib/graphql/query.rb +64 -21
  19. data/lib/graphql/root_call.rb +176 -0
  20. data/lib/graphql/root_call_argument.rb +8 -0
  21. data/lib/graphql/root_call_argument_definer.rb +20 -0
  22. data/lib/graphql/schema.rb +99 -0
  23. data/lib/graphql/syntax/call.rb +3 -12
  24. data/lib/graphql/syntax/field.rb +4 -2
  25. data/lib/graphql/syntax/node.rb +3 -10
  26. data/lib/graphql/syntax/query.rb +7 -0
  27. data/lib/graphql/syntax/variable.rb +7 -0
  28. data/lib/graphql/transform.rb +14 -5
  29. data/lib/graphql/types/boolean_field.rb +3 -0
  30. data/lib/graphql/types/connection_field.rb +30 -0
  31. data/lib/graphql/types/cursor_field.rb +9 -0
  32. data/lib/graphql/types/number_field.rb +3 -0
  33. data/lib/graphql/types/object_field.rb +8 -0
  34. data/lib/graphql/types/string_field.rb +3 -0
  35. data/lib/graphql/types/type_field.rb +6 -0
  36. data/lib/graphql/version.rb +3 -0
  37. data/readme.md +142 -10
  38. data/spec/graphql/field_spec.rb +66 -0
  39. data/spec/graphql/node_spec.rb +68 -0
  40. data/spec/graphql/parser_spec.rb +75 -25
  41. data/spec/graphql/query_spec.rb +185 -83
  42. data/spec/graphql/root_call_spec.rb +55 -0
  43. data/spec/graphql/schema_spec.rb +128 -0
  44. data/spec/graphql/transform_spec.rb +124 -39
  45. data/spec/spec_helper.rb +2 -1
  46. data/spec/support/dummy_app.rb +43 -23
  47. data/spec/support/nodes.rb +145 -32
  48. metadata +78 -16
  49. data/lib/graphql/collection_edge.rb +0 -62
  50. data/lib/graphql/syntax/edge.rb +0 -12
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe GraphQL::RootCall do
4
+ let(:query_string) { %{
5
+ upvote_post(<post_data>, <person_id>) {
6
+ post {
7
+ likes { count, any }
8
+ }
9
+ upvote {
10
+ post_id
11
+ }
12
+ }
13
+ <post_data>: { "id" : #{@post_id} }
14
+ <person_id>: 888
15
+ }}
16
+ let(:result) { GraphQL::Query.new(query_string).as_result }
17
+
18
+ before do
19
+ # make sure tests don't conflict :(
20
+ @post_id = "#{Time.now.to_i}#{[1,2,3].sample}".to_i
21
+ @post = Post.create(id: @post_id, content: "My great post")
22
+ @like = Like.create(post_id: @post_id)
23
+ end
24
+
25
+ after do
26
+ @post.likes.map(&:destroy)
27
+ @post.destroy
28
+ end
29
+
30
+ describe '#as_result' do
31
+ it 'operates on the application' do
32
+ assert_equal 1, @post.likes.count
33
+ result
34
+ assert_equal 2, @post.likes.count
35
+ end
36
+
37
+ it 'returns fields for the node' do
38
+ assert_equal @post_id, result["upvote"]["post_id"]
39
+ assert_equal 2, result["post"]["likes"]["count"]
40
+ assert_equal true, result["post"]["likes"]["any"]
41
+ end
42
+
43
+ describe 'when the input is the wrong type' do
44
+ let(:query_string) { %{upvote_post(bogus_arg, 888) { post { id } } } }
45
+ it 'validates the input' do
46
+ assert_raises(GraphQL::RootCallArgumentError) { result }
47
+ end
48
+ end
49
+ end
50
+
51
+ describe '#__type__' do
52
+ it 'describes the input'
53
+ it 'describes the response'
54
+ end
55
+ end
@@ -0,0 +1,128 @@
1
+ require 'spec_helper'
2
+
3
+ describe GraphQL::Schema do
4
+ let(:schema) { GraphQL::SCHEMA }
5
+ describe 'global instance' do
6
+ it 'exists as GraphQL::SCHEMA' do
7
+ assert GraphQL::SCHEMA.is_a?(GraphQL::Schema)
8
+ end
9
+ end
10
+
11
+ describe '#get_call' do
12
+ it 'finds calls from their class name' do
13
+ assert_equal Nodes::ContextCall, schema.get_call("context")
14
+ end
15
+
16
+ it 'finds calls from declared names' do
17
+ assert_equal Nodes::LikePostCall, schema.get_call("upvote_post")
18
+ end
19
+ end
20
+
21
+ describe '#get_type' do
22
+ it 'finds nodes from their class name' do
23
+ assert_equal Nodes::PostNode, schema.get_type("post")
24
+ end
25
+
26
+ it 'finds nodes from declared names' do
27
+ assert_equal Nodes::ThumbUpNode, schema.get_type("upvote")
28
+ end
29
+ end
30
+
31
+ describe 'querying schema' do
32
+ let(:query_string) { "schema() {
33
+ calls {
34
+ count,
35
+ edges {
36
+ node {
37
+ name,
38
+ returns,
39
+ arguments {
40
+ edges {
41
+ node {
42
+ name, type
43
+ }
44
+ }
45
+ }
46
+ }
47
+ }
48
+ },
49
+ types {
50
+ count,
51
+ edges {
52
+ node {
53
+ name,
54
+ fields {
55
+ count,
56
+ edges {
57
+ node {
58
+ name,
59
+ type,
60
+ calls {
61
+ count,
62
+ edges {
63
+ node {
64
+ name,
65
+ arguments
66
+ }
67
+ }
68
+ }
69
+ }
70
+ }
71
+ }
72
+ }
73
+ }
74
+ }
75
+ }"}
76
+ let(:query) { GraphQL::Query.new(query_string) }
77
+ let(:result) { query.as_result }
78
+
79
+ describe 'querying calls' do
80
+ let(:upvote_post_call) { result["schema"]["calls"]["edges"].find {|e| e["node"]["name"] == "upvote_post"} }
81
+
82
+ it 'returns all calls' do
83
+ assert_equal 7, result["schema"]["calls"]["count"]
84
+ end
85
+
86
+ it 'doesnt show abstract call classes' do
87
+ call_names = result["schema"]["calls"]["edges"].map {|e| e["node"]["name"] }
88
+ assert(!call_names.include?("find"))
89
+ end
90
+
91
+ it 'shows return types' do
92
+ assert_equal ["post", "upvote"], upvote_post_call["node"]["returns"]
93
+ end
94
+
95
+ it 'shows argument types' do
96
+ expected_arguments = [{"node"=>{"name"=>"post_data", "type"=>"object"}}, {"node"=>{"name"=>"person_id", "type"=>"number"}}]
97
+ assert_equal expected_arguments, upvote_post_call["node"]["arguments"]["edges"]
98
+ end
99
+ end
100
+
101
+ describe 'querying types' do
102
+ let(:post_type) { result["schema"]["types"]["edges"].find { |e| e["node"]["name"] == "post" }["node"]}
103
+ let(:content_field) { post_type["fields"]["edges"].find { |e| e["node"]["name"] == "content" }["node"]}
104
+ let(:select_call) { content_field["calls"]["edges"].find { |e| e["node"]["name"] == "select"}["node"]}
105
+
106
+ it 'returns all types' do
107
+ assert_equal 13, result["schema"]["types"]["count"]
108
+ end
109
+
110
+ it 'doesnt return types that dont expose anything' do
111
+ type_names = result["schema"]["types"]["edges"].map {|e| e["node"]["name"] }
112
+ assert(!type_names.include?("application"))
113
+ end
114
+
115
+ it 'show type name & fields' do
116
+ assert_equal "post", post_type["name"]
117
+ assert_equal 8, post_type["fields"]["count"]
118
+ end
119
+
120
+ it 'shows field type & calls' do
121
+ assert_equal "string", content_field["type"]
122
+ assert_equal 3, content_field["calls"]["count"]
123
+ assert_equal "select", select_call["name"]
124
+ assert_equal "from_chars (req), for_chars (req)", select_call["arguments"]
125
+ end
126
+ end
127
+ end
128
+ end
@@ -5,54 +5,139 @@ describe GraphQL::Transform do
5
5
  let(:parser) { GraphQL::PARSER }
6
6
 
7
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
8
+ describe 'query' do
9
+ it 'parses node and variables' do
10
+ tree = parser.query.parse(%{
11
+ like_page(<page_info>) { page { id, likes } }
13
12
 
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')
13
+ <page_info>: {
14
+ "page" : { "id": 4},
15
+ "person" : {"id": 4}
16
+ }
17
+ <other>: {
18
+ "page" : { "id": 4},
19
+ "person" : {"id": 4}
20
+ }
21
+ })
22
+ res = transform.apply(tree)
23
+ assert_equal 1, res.nodes.length
24
+ assert_equal "like_page", res.nodes[0].identifier
25
+ assert_equal ["<page_info>"], res.nodes[0].arguments
26
+ assert_equal ["<page_info>", "<other>"], res.variables.map(&:identifier)
27
+ end
22
28
  end
23
29
 
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")
30
+ describe 'nodes' do
31
+ it 'turns a simple node into a Node' do
32
+ tree = parser.node.parse("post(123) { name }")
33
+ res = transform.apply(tree)
34
+ assert(res.is_a?(GraphQL::Syntax::Node), 'it gets a node')
35
+ end
36
+
37
+ it 'turns a node into a Node' do
38
+ tree = parser.node.parse("person(1) { name, check_ins.last(4) { count, edges { node { id } } } }")
39
+ res = transform.apply(tree)
40
+ assert(res.is_a?(GraphQL::Syntax::Node), 'it gets a node')
41
+ assert(res.identifier == "person")
42
+ assert(res.fields.length == 2)
43
+ assert(res.fields[0].is_a?(GraphQL::Syntax::Field), 'it gets a field')
44
+ assert(res.fields[1].is_a?(GraphQL::Syntax::Field), 'it gets an field with fields')
45
+ assert(res.fields[1].calls.first.is_a?(GraphQL::Syntax::Call), 'it gets a call')
46
+ end
29
47
  end
30
48
 
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"})
49
+ describe 'fields' do
50
+ it 'turns a field into a Field' do
51
+ tree = parser.field.parse("friends")
52
+ res = transform.apply(tree)
53
+ assert(res.is_a?(GraphQL::Syntax::Field))
54
+ assert(res.identifier == "friends")
55
+ end
56
+
57
+ it 'gets aliases' do
58
+ tree = parser.field.parse("friends as pals")
59
+ res = transform.apply(tree)
60
+ assert(res.is_a?(GraphQL::Syntax::Field))
61
+ assert(res.identifier == "friends")
62
+ assert(res.alias_name == "pals")
63
+ end
64
+
65
+ it 'gets calls' do
66
+ tree = parser.field.parse("friends.orderby(name, birthdate).first(3)")
67
+ res = transform.apply(tree)
68
+ assert_equal "orderby", res.calls[0].identifier
69
+ assert_equal ["name", "birthdate"], res.calls[0].arguments
70
+ assert_equal "first", res.calls[1].identifier
71
+ assert_equal ["3"], res.calls[1].arguments
72
+ end
73
+
74
+ describe 'fields that return objects' do
75
+ it 'gets them' do
76
+ tree = parser.field.parse("friends { count }")
77
+ res = transform.apply(tree)
78
+ assert_equal "friends", res.identifier
79
+ assert_equal 1, res.fields.length
80
+ end
81
+ it 'gets them with aliases' do
82
+ tree = parser.field.parse("friends as pals { count }")
83
+ res = transform.apply(tree)
84
+ assert_equal "friends", res.identifier
85
+ assert_equal "pals", res.alias_name
86
+ assert_equal 1, res.fields.length
87
+ end
88
+ it 'gets them with calls' do
89
+ tree = parser.field.parse("friends.orderby(name, birthdate).last(1) { count }")
90
+ res = transform.apply(tree)
91
+ assert_equal "friends", res.identifier
92
+ assert_equal 1, res.fields.length
93
+ assert_equal 2, res.calls.length
94
+ end
95
+ it 'gets them with calls and aliases' do
96
+ tree = parser.field.parse("friends.orderby(name, birthdate).last(1) as pals { count }")
97
+ res = transform.apply(tree)
98
+ assert_equal "friends", res.identifier
99
+ assert_equal "pals", res.alias_name
100
+ assert_equal 1, res.fields.length
101
+ assert_equal 2, res.calls.length
102
+ end
103
+ end
40
104
  end
41
105
 
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")
106
+ describe 'calls' do
107
+ it 'turns call into a Call' do
108
+ tree = parser.call.parse("node(4, 6, tree)")
109
+ res = transform.apply(tree)
110
+ assert(res.is_a?(GraphQL::Syntax::Call))
111
+ assert(res.identifier == "node")
112
+ assert(res.arguments == ["4", "6", "tree"])
113
+ end
114
+
115
+ it 'turns a call without an argument into a Call' do
116
+ tree = parser.call.parse("viewer()")
117
+ res = transform.apply(tree)
118
+ assert(res.is_a?(GraphQL::Syntax::Call))
119
+ assert(res.identifier == "viewer")
120
+ assert(res.arguments.length == 0)
121
+ end
122
+
123
+ it 'gets calls with variable identifiers' do
124
+ tree = parser.call.parse("like_page(<page_info>)")
125
+ res = transform.apply(tree)
126
+ assert_equal "<page_info>", res.arguments[0]
127
+ end
48
128
  end
49
129
 
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)
130
+ describe 'variables' do
131
+ it 'gets variables' do
132
+ tree = parser.variable.parse(%{
133
+ <page_info>: {
134
+ "page" : { "id": 4},
135
+ "person" : {"id": 4}
136
+ }
137
+ })
138
+ res = transform.apply(tree)
139
+ assert_equal "<page_info>", res.identifier
140
+ end
56
141
  end
57
142
  end
58
143
  end
data/spec/spec_helper.rb CHANGED
@@ -1,7 +1,8 @@
1
+ require 'date'
1
2
  require "minitest/autorun"
2
3
  require "minitest/focus"
3
4
  require "minitest/reporters"
4
-
5
+ require 'pry'
5
6
  Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
6
7
 
7
8
  # Filter out Minitest backtrace while allowing backtrace from other libraries
@@ -6,56 +6,76 @@ class InadequateRecordBase
6
6
  end
7
7
 
8
8
  def destroy
9
- self.class.objects.delete(self)
9
+ self.class.all.delete(self)
10
10
  end
11
11
 
12
12
  class << self
13
13
  attr_accessor :_objects
14
- def objects
14
+ def all
15
15
  @_objects ||= []
16
16
  end
17
- end
18
17
 
19
- def self.find(id)
20
- objects.find { |object| object.id.to_s == id.to_s}
21
- end
18
+ def find(id)
19
+ all.find { |object| object.id.to_s == id.to_s }
20
+ end
22
21
 
23
- def self.where(query={})
24
- result = []
25
- objects.each do |object|
26
- match = true
22
+ def where(query={})
23
+ result = []
24
+ all.each do |object|
25
+ match = true
27
26
 
28
- query.each do |key, value|
29
- if object.send(key) != value
30
- match = false
27
+ query.each do |key, value|
28
+ if object.send(key) != value
29
+ match = false
30
+ end
31
31
  end
32
- end
33
32
 
34
- result << object if match
33
+ result << object if match
34
+ end
35
+ result
35
36
  end
36
- result
37
- end
38
37
 
39
- def self.create(attributes)
40
- instance = self.new(attributes)
41
- objects << instance
42
- instance
38
+ def create(attributes)
39
+ @next_id ||= 0
40
+ attributes[:id] ||= @next_id += 1
41
+ instance = self.new(attributes)
42
+ all << instance
43
+ instance
44
+ end
43
45
  end
44
46
  end
45
47
 
46
48
  class Post < InadequateRecordBase
47
- attr_accessor :id, :title, :content
49
+ attr_accessor :id, :title, :content, :published_at
48
50
 
49
51
  def comments
50
52
  Comment.where(post_id: id)
51
53
  end
54
+
55
+ def likes
56
+ Like.where(post_id: id)
57
+ end
52
58
  end
53
59
 
54
60
  class Comment < InadequateRecordBase
55
- attr_accessor :id, :post_id, :content
61
+ attr_accessor :id, :post_id, :content, :rating
62
+
56
63
  def post
57
64
  Post.find(post_id)
58
65
  end
59
66
  end
60
67
 
68
+ class Like < InadequateRecordBase
69
+ attr_accessor :id, :post_id
70
+
71
+ def post
72
+ Post.find(post_id)
73
+ end
74
+ end
61
75
 
76
+ class Context
77
+ attr_reader :person_name
78
+ def initialize(person_name:)
79
+ @person_name = person_name
80
+ end
81
+ end