gql 0.0.6 → 0.0.7
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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/Rakefile +4 -4
- data/gql.gemspec +1 -1
- data/lib/gql.rb +2 -2
- data/lib/gql/array.rb +3 -11
- data/lib/gql/call.rb +16 -49
- data/lib/gql/config.rb +1 -1
- data/lib/gql/connection.rb +5 -20
- data/lib/gql/executor.rb +5 -5
- data/lib/gql/field.rb +3 -15
- data/lib/gql/node.rb +91 -32
- data/lib/gql/number.rb +1 -1
- data/lib/gql/object.rb +3 -9
- data/lib/gql/parser.rb +206 -193
- data/lib/gql/parser.y +31 -11
- data/lib/gql/schema/call.rb +4 -12
- data/lib/gql/schema/field.rb +4 -12
- data/lib/gql/schema/node.rb +3 -11
- data/lib/gql/schema/parameter.rb +4 -6
- data/lib/gql/string.rb +3 -3
- data/lib/gql/version.rb +1 -1
- metadata +3 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c91705621b9dd8b0515c2d72859f30af612d5f24
|
4
|
+
data.tar.gz: d5f410d2acdc7c18f9832058db5c70fd720f7b08
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 73cb1ed1e7bb3cdbdb4cb6cbe9eea50c8eeec8aac68d708f9bac42e9feb887bc44a03ed477b2f51da659b370c1bea5a2192490730885eaa33d2d9cee10225e73
|
7
|
+
data.tar.gz: 2b41f407270151041915c441b9ff6ad790298d43ea925af074b4a7405d3b5475c27db40865c62e37d50d84e4d0fd373d6cd5cd5d26c82c86a9e126f32cb6adc6
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# gql
|
2
2
|
|
3
|
-
|
3
|
+
A Ruby implementation of Facebook's yet-to-be-released GraphQL specification, inspired by [graphql-ruby](https://github.com/rmosolgo/graphql-ruby), but with other/more/less features/bugs.
|
4
4
|
|
5
5
|
Caution! This is pre-alpha software. Use at your own risk.
|
6
6
|
|
data/Rakefile
CHANGED
@@ -1,10 +1,6 @@
|
|
1
1
|
require 'bundler/gem_tasks'
|
2
2
|
require 'rake/testtask'
|
3
3
|
|
4
|
-
Rake::TestTask.new :test => :compile do |t|
|
5
|
-
t.libs << 'test'
|
6
|
-
end
|
7
|
-
|
8
4
|
file 'lib/gql/tokenizer.rb' => 'lib/gql/tokenizer.rex' do |t|
|
9
5
|
sh "bundle exec rex #{t.prerequisites.first} --output-file #{t.name}"
|
10
6
|
end
|
@@ -19,4 +15,8 @@ end
|
|
19
15
|
|
20
16
|
task :compile => ['lib/gql/tokenizer.rb', 'lib/gql/parser.rb']
|
21
17
|
|
18
|
+
Rake::TestTask.new :test => :compile do |t|
|
19
|
+
t.libs << 'test'
|
20
|
+
end
|
21
|
+
|
22
22
|
task :default => :test
|
data/gql.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.authors = ['Martin Andert']
|
11
11
|
spec.email = ['mandert@gmail.com']
|
12
12
|
|
13
|
-
spec.summary = '
|
13
|
+
spec.summary = 'A Ruby implementation of Facebook\'s yet-to-be-released GraphQL specification.'
|
14
14
|
spec.homepage = 'https://github.com/martinandert/gql'
|
15
15
|
spec.license = 'MIT'
|
16
16
|
|
data/lib/gql.rb
CHANGED
data/lib/gql/array.rb
CHANGED
@@ -5,27 +5,19 @@ module GQL
|
|
5
5
|
class_attribute :item_class, instance_accessor: false, instance_predicate: false
|
6
6
|
|
7
7
|
class << self
|
8
|
-
def build_class(id,
|
8
|
+
def build_class(id, proc, options = {})
|
9
9
|
item_class = options[:item_class] || self.item_class
|
10
10
|
|
11
|
-
|
12
|
-
raise Errors::UndefinedNodeClass.new(self, 'item')
|
13
|
-
end
|
14
|
-
|
15
|
-
unless item_class <= Node
|
16
|
-
raise Errors::InvalidNodeClass.new(item_class, Node)
|
17
|
-
end
|
11
|
+
Node.validate_is_subclass! item_class, 'item'
|
18
12
|
|
19
13
|
Class.new(self).tap do |field_class|
|
20
14
|
field_class.id = id.to_s
|
21
|
-
field_class.
|
15
|
+
field_class.proc = proc
|
22
16
|
field_class.item_class = item_class
|
23
17
|
end
|
24
18
|
end
|
25
19
|
end
|
26
20
|
|
27
|
-
call :size, Number, -> { target.size }
|
28
|
-
|
29
21
|
def value
|
30
22
|
target.map do |item|
|
31
23
|
node = self.class.item_class.new(ast_node, item, variables, context)
|
data/lib/gql/call.rb
CHANGED
@@ -1,70 +1,37 @@
|
|
1
|
+
require 'active_support/core_ext/class/attribute'
|
2
|
+
|
1
3
|
module GQL
|
2
4
|
class Call
|
3
|
-
|
4
|
-
attr_reader :target, :context
|
5
|
-
|
6
|
-
def initialize(target, context)
|
7
|
-
@target, @context = target, context
|
8
|
-
end
|
5
|
+
class_attribute :result_class, :proc, instance_accessor: false, instance_predicate: false
|
9
6
|
|
10
|
-
|
11
|
-
|
7
|
+
class << self
|
8
|
+
def returns(result_class)
|
9
|
+
self.result_class = result_class
|
12
10
|
end
|
13
11
|
end
|
14
12
|
|
15
|
-
|
16
|
-
|
17
|
-
class << self
|
18
|
-
def build_class(id, result_class, method)
|
19
|
-
if result_class.is_a? ::Array
|
20
|
-
if result_class.size == 1
|
21
|
-
result_class.unshift GQL.default_list_class || Connection
|
22
|
-
end
|
23
|
-
|
24
|
-
list_class, item_class = result_class
|
25
|
-
|
26
|
-
unless list_class <= Connection
|
27
|
-
raise Errors::InvalidNodeClass.new(list_class, Connection)
|
28
|
-
end
|
29
|
-
|
30
|
-
unless item_class <= Node
|
31
|
-
raise Errors::InvalidNodeClass.new(list_class, Node)
|
32
|
-
end
|
13
|
+
attr_reader :target, :context
|
33
14
|
|
34
|
-
|
35
|
-
|
36
|
-
elsif result_class && !(result_class <= Node)
|
37
|
-
raise Errors::InvalidNodeClass.new(result_class, Node)
|
38
|
-
end
|
39
|
-
|
40
|
-
Class.new(self).tap do |call_class|
|
41
|
-
call_class.id = id.to_s
|
42
|
-
call_class.method = method
|
43
|
-
call_class.result_class = result_class
|
44
|
-
end
|
45
|
-
end
|
15
|
+
def initialize(target, context)
|
16
|
+
@target, @context = target, context
|
46
17
|
end
|
47
18
|
|
48
|
-
|
49
|
-
|
50
|
-
def initialize(caller, ast_node, target, variables, context)
|
51
|
-
@caller, @ast_node, @target = caller, ast_node, target
|
52
|
-
@variables, @context = variables, context
|
19
|
+
def execute(*)
|
20
|
+
raise NotImplementedError, 'override in subclass'
|
53
21
|
end
|
54
22
|
|
55
|
-
def
|
56
|
-
args = substitute_variables(ast_node.arguments)
|
23
|
+
def result_for(caller_class, ast_node, variables)
|
24
|
+
args = substitute_variables(ast_node.arguments, variables)
|
25
|
+
target = execute(*args)
|
57
26
|
|
58
|
-
|
59
|
-
target = method.execute(self.class.method, args)
|
60
|
-
result_class = self.class.result_class || caller.class
|
27
|
+
result_class = self.class.result_class || caller_class
|
61
28
|
|
62
29
|
result = result_class.new(ast_node, target, variables, context)
|
63
30
|
result.value
|
64
31
|
end
|
65
32
|
|
66
33
|
private
|
67
|
-
def substitute_variables(args)
|
34
|
+
def substitute_variables(args, variables)
|
68
35
|
args.map { |arg| arg.is_a?(::Symbol) ? variables[arg] : arg }
|
69
36
|
end
|
70
37
|
end
|
data/lib/gql/config.rb
CHANGED
data/lib/gql/connection.rb
CHANGED
@@ -6,33 +6,18 @@ module GQL
|
|
6
6
|
class_attribute :item_class, instance_accessor: false, instance_predicate: false
|
7
7
|
|
8
8
|
class << self
|
9
|
-
def build_class(id,
|
9
|
+
def build_class(id, proc, options = {})
|
10
10
|
list_class = options[:list_class] || self.list_class || GQL.default_list_class
|
11
11
|
item_class = options[:item_class] || self.item_class
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
end
|
16
|
-
|
17
|
-
unless list_class <= Connection
|
18
|
-
raise Errors::InvalidNodeClass.new(list_class, Connection)
|
19
|
-
end
|
20
|
-
|
21
|
-
if item_class.nil?
|
22
|
-
raise Errors::UndefinedNodeClass.new(self, 'item')
|
23
|
-
end
|
24
|
-
|
25
|
-
unless item_class <= Node
|
26
|
-
raise Errors::InvalidNodeClass.new(item_class, Node)
|
27
|
-
end
|
13
|
+
Connection.validate_is_subclass! list_class, 'list'
|
14
|
+
Node.validate_is_subclass! item_class, 'item'
|
28
15
|
|
29
16
|
Class.new(list_class).tap do |field_class|
|
30
17
|
field_class.id = id.to_s
|
31
|
-
field_class.
|
18
|
+
field_class.proc = proc
|
32
19
|
|
33
|
-
field_class.array :edges, item_class: item_class
|
34
|
-
target
|
35
|
-
end
|
20
|
+
field_class.array :edges, -> { target }, item_class: item_class
|
36
21
|
end
|
37
22
|
end
|
38
23
|
end
|
data/lib/gql/executor.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
module GQL
|
2
2
|
class Executor
|
3
|
-
attr_reader :
|
3
|
+
attr_reader :ast_root, :variables
|
4
4
|
|
5
|
-
def initialize(
|
6
|
-
@
|
7
|
-
@variables =
|
5
|
+
def initialize(ast_query)
|
6
|
+
@ast_root = ast_query.root
|
7
|
+
@variables = ast_query.variables
|
8
8
|
end
|
9
9
|
|
10
10
|
def execute(context = {})
|
@@ -14,7 +14,7 @@ module GQL
|
|
14
14
|
|
15
15
|
context[:_schema_root] = node_class if ENV['DEBUG']
|
16
16
|
|
17
|
-
node = node_class.new(
|
17
|
+
node = node_class.new(ast_root, nil, variables, context)
|
18
18
|
node.value
|
19
19
|
end
|
20
20
|
end
|
data/lib/gql/field.rb
CHANGED
@@ -2,25 +2,13 @@ require 'active_support/core_ext/class/attribute'
|
|
2
2
|
|
3
3
|
module GQL
|
4
4
|
class Field < Node
|
5
|
-
|
6
|
-
attr_reader :target, :context
|
7
|
-
|
8
|
-
def initialize(target, context)
|
9
|
-
@target, @context = target, context
|
10
|
-
end
|
11
|
-
|
12
|
-
def execute(method)
|
13
|
-
instance_exec(&method)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
class_attribute :id, :method, instance_accessor: false, instance_predicate: false
|
5
|
+
class_attribute :id, :proc, instance_accessor: false, instance_predicate: false
|
18
6
|
|
19
7
|
class << self
|
20
|
-
def build_class(id,
|
8
|
+
def build_class(id, proc, options = {})
|
21
9
|
Class.new(self).tap do |field_class|
|
22
10
|
field_class.id = id.to_s
|
23
|
-
field_class.
|
11
|
+
field_class.proc = proc
|
24
12
|
end
|
25
13
|
end
|
26
14
|
end
|
data/lib/gql/node.rb
CHANGED
@@ -1,57 +1,116 @@
|
|
1
1
|
require 'active_support/core_ext/class/attribute'
|
2
2
|
require 'active_support/core_ext/string/inflections'
|
3
3
|
require 'active_support/core_ext/array/extract_options'
|
4
|
+
require 'active_support/core_ext/object/try'
|
4
5
|
|
5
6
|
module GQL
|
6
7
|
class Node
|
8
|
+
class ExecutionContext
|
9
|
+
attr_reader :target, :context
|
10
|
+
|
11
|
+
def initialize(target, context)
|
12
|
+
@target, @context = target, context
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute(method, args = [])
|
16
|
+
instance_exec(*args, &method)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
7
20
|
class_attribute :calls, :fields, instance_accessor: false, instance_predicate: false
|
8
21
|
|
9
22
|
self.calls = {}
|
10
23
|
self.fields = {}
|
11
24
|
|
12
25
|
class << self
|
13
|
-
def call(id,
|
14
|
-
if
|
15
|
-
|
16
|
-
|
17
|
-
|
26
|
+
def call(id, *args)
|
27
|
+
if id.is_a? Hash
|
28
|
+
id.each do |id, call_class|
|
29
|
+
call id, call_class
|
30
|
+
end
|
31
|
+
else
|
32
|
+
options = args.extract_options!
|
18
33
|
|
19
|
-
|
34
|
+
proc_or_class = args.shift || -> (*args) { target.public_send(id, *args) }
|
35
|
+
result_class = options[:returns] || proc_or_class.try(:result_class)
|
20
36
|
|
21
|
-
|
37
|
+
if result_class.is_a? ::Array
|
38
|
+
if result_class.size == 1
|
39
|
+
result_class.unshift GQL.default_list_class || Connection
|
40
|
+
end
|
22
41
|
|
23
|
-
|
24
|
-
|
25
|
-
|
42
|
+
options = {
|
43
|
+
list_class: result_class.first,
|
44
|
+
item_class: result_class.last
|
45
|
+
}
|
26
46
|
|
27
|
-
|
28
|
-
|
47
|
+
result_class = Connection.build_class(:result, nil, options)
|
48
|
+
elsif result_class
|
49
|
+
Node.validate_is_subclass! result_class, 'result'
|
50
|
+
end
|
29
51
|
|
30
|
-
|
31
|
-
|
32
|
-
|
52
|
+
call_class =
|
53
|
+
if proc_or_class.is_a? Proc
|
54
|
+
Class.new(Call).tap do |call_class|
|
55
|
+
call_class.class_eval do
|
56
|
+
self.proc = proc_or_class
|
33
57
|
|
34
|
-
|
35
|
-
|
36
|
-
|
58
|
+
def execute(*args)
|
59
|
+
instance_exec(*args, &self.class.proc)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
self.const_set "#{id.to_s.camelize}Call", call_class
|
64
|
+
end
|
65
|
+
else
|
66
|
+
proc_or_class
|
67
|
+
end
|
37
68
|
|
38
|
-
|
69
|
+
call_class.result_class = result_class
|
39
70
|
|
71
|
+
self.calls = calls.merge(id.to_sym => call_class)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def field(id, *args)
|
76
|
+
options = args.extract_options!
|
77
|
+
proc = args.shift || -> { target.public_send(id) }
|
78
|
+
type = options.delete(:type) || Field
|
79
|
+
|
80
|
+
Field.validate_is_subclass! type, 'type'
|
81
|
+
|
82
|
+
type.build_class(id, proc, options).tap do |field_class|
|
40
83
|
self.const_set "#{id.to_s.camelize}Field", field_class
|
41
84
|
self.fields = fields.merge(id.to_sym => field_class)
|
42
85
|
end
|
43
86
|
end
|
44
87
|
|
45
|
-
def cursor(
|
46
|
-
|
47
|
-
|
88
|
+
def cursor(id_or_proc)
|
89
|
+
id = id_or_proc.is_a?(Proc) ? nil : id_or_proc
|
90
|
+
proc = id ? -> { target.public_send(id) } : id_or_proc
|
91
|
+
|
92
|
+
field :cursor, proc, type: Simple
|
93
|
+
end
|
94
|
+
|
95
|
+
def validate_is_subclass!(subclass, name)
|
96
|
+
if subclass.nil?
|
97
|
+
raise Errors::UndefinedNodeClass.new(self, name)
|
98
|
+
end
|
99
|
+
|
100
|
+
unless subclass <= self
|
101
|
+
raise Errors::InvalidNodeClass.new(subclass, self)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def respond_to?(method, *args)
|
106
|
+
GQL.field_types.has_key?(method) || super
|
48
107
|
end
|
49
108
|
|
50
|
-
def method_missing(method, *
|
51
|
-
if
|
52
|
-
options =
|
109
|
+
def method_missing(method, *args, &block)
|
110
|
+
if type = GQL.field_types[method]
|
111
|
+
options = args.extract_options!.merge(type: type)
|
53
112
|
|
54
|
-
field(*
|
113
|
+
field(*args.push(options), &block)
|
55
114
|
else
|
56
115
|
super
|
57
116
|
end
|
@@ -84,15 +143,15 @@ module GQL
|
|
84
143
|
raise Errors::UndefinedCall.new(ast_call.id, self.class)
|
85
144
|
end
|
86
145
|
|
87
|
-
call = call_class.new(
|
88
|
-
call.
|
146
|
+
call = call_class.new(target, context)
|
147
|
+
call.result_for self.class, ast_call, variables
|
89
148
|
end
|
90
149
|
|
91
150
|
def value_of_fields(ast_fields)
|
92
|
-
ast_fields.reduce({}) do |
|
151
|
+
ast_fields.reduce({}) do |result, ast_field|
|
93
152
|
key = ast_field.alias_id || ast_field.id
|
94
153
|
|
95
|
-
|
154
|
+
result.merge key => value_of_field(ast_field)
|
96
155
|
end
|
97
156
|
end
|
98
157
|
|
@@ -108,8 +167,8 @@ module GQL
|
|
108
167
|
raise Errors::UndefinedField.new(ast_field.id, self.class)
|
109
168
|
end
|
110
169
|
|
111
|
-
method =
|
112
|
-
target = method.execute(field_class.
|
170
|
+
method = ExecutionContext.new(target, context)
|
171
|
+
target = method.execute(field_class.proc)
|
113
172
|
|
114
173
|
field = field_class.new(ast_field, target, variables, context)
|
115
174
|
field.value
|