graphql 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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