graphql 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +46 -29
  3. data/lib/graphql/call.rb +1 -1
  4. data/lib/graphql/connection.rb +12 -35
  5. data/lib/graphql/field.rb +7 -177
  6. data/lib/graphql/field_definer.rb +5 -15
  7. data/lib/graphql/introspection/{call_node.rb → call_type.rb} +1 -1
  8. data/lib/graphql/introspection/field_type.rb +10 -0
  9. data/lib/graphql/introspection/{root_call_node.rb → root_call_type.rb} +6 -2
  10. data/lib/graphql/introspection/schema_type.rb +17 -0
  11. data/lib/graphql/introspection/{type_node.rb → type_type.rb} +4 -2
  12. data/lib/graphql/node.rb +118 -42
  13. data/lib/graphql/{parser.rb → parser/parser.rb} +9 -4
  14. data/lib/graphql/{transform.rb → parser/transform.rb} +4 -2
  15. data/lib/graphql/query.rb +33 -10
  16. data/lib/graphql/root_call.rb +26 -13
  17. data/lib/graphql/root_call_argument.rb +3 -1
  18. data/lib/graphql/root_call_argument_definer.rb +3 -7
  19. data/lib/graphql/schema/all.rb +46 -0
  20. data/lib/graphql/{schema.rb → schema/schema.rb} +27 -39
  21. data/lib/graphql/schema/schema_validation.rb +32 -0
  22. data/lib/graphql/syntax/fragment.rb +7 -0
  23. data/lib/graphql/syntax/query.rb +3 -2
  24. data/lib/graphql/types/boolean_type.rb +3 -0
  25. data/lib/graphql/types/number_type.rb +3 -0
  26. data/lib/graphql/types/object_type.rb +6 -0
  27. data/lib/graphql/types/string_type.rb +3 -0
  28. data/lib/graphql/version.rb +1 -1
  29. data/readme.md +46 -5
  30. data/spec/graphql/node_spec.rb +6 -5
  31. data/spec/graphql/{parser_spec.rb → parser/parser_spec.rb} +31 -2
  32. data/spec/graphql/{transform_spec.rb → parser/transform_spec.rb} +16 -2
  33. data/spec/graphql/query_spec.rb +27 -9
  34. data/spec/graphql/root_call_spec.rb +15 -1
  35. data/spec/graphql/{schema_spec.rb → schema/schema_spec.rb} +15 -50
  36. data/spec/graphql/schema/schema_validation_spec.rb +48 -0
  37. data/spec/support/nodes.rb +31 -28
  38. metadata +47 -47
  39. data/lib/graphql/introspection/field_node.rb +0 -19
  40. data/lib/graphql/introspection/schema_node.rb +0 -17
  41. data/lib/graphql/types/boolean_field.rb +0 -3
  42. data/lib/graphql/types/connection_field.rb +0 -30
  43. data/lib/graphql/types/cursor_field.rb +0 -9
  44. data/lib/graphql/types/number_field.rb +0 -3
  45. data/lib/graphql/types/object_field.rb +0 -8
  46. data/lib/graphql/types/string_field.rb +0 -3
  47. data/lib/graphql/types/type_field.rb +0 -6
  48. data/spec/graphql/field_spec.rb +0 -63
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6184108ff2ba0151d12bfaeb02feeacbdeba74cc
4
- data.tar.gz: 2a9cb034382d28f96a7e29024814848b63596a49
3
+ metadata.gz: ea9221c87a055fadbddc1b61de970e61b993e074
4
+ data.tar.gz: 289bff890b6c2fb4ff3487cd74f8333375dcdbe9
5
5
  SHA512:
6
- metadata.gz: 7bb555f0b19ce78ea3f79ba7388f31509c26e2518fc1c207a25e83bcdd4f38e3670194bcb26a1447dc134a5949e58d10e12da79846bb2b3fb4dab0d688561a8a
7
- data.tar.gz: 695837ba820b7709453584800eaa6d3f4082edc9d443055ab52d42e0db3b895189c2c10113a04d43e44c70726365621b99a878cc68a371acd85b46aeb05b925a
6
+ metadata.gz: 28d366cd1a906295fe885728ad08f1b13bcbd3b36ad35ac3aed6603d54a680a9e01ed048393f040f59d3b649c261e82958ef2432f652fa78d8a97d88ea69ca2b
7
+ data.tar.gz: eb17ca9aa091ccc42a85ecbf59b45bbbe61fa9dc154cb0794cbe59cae6bdc0a1e082fe35f95f8878c6b0dbe59432a420c58068f00f40eb594c00ef083d5c73a6
data/lib/graphql.rb CHANGED
@@ -2,62 +2,77 @@ require "active_support/core_ext/object/blank"
2
2
  require "active_support/core_ext/string/inflections"
3
3
  require "json"
4
4
  require "parslet"
5
- require "singleton"
6
5
 
7
6
  module GraphQL
8
7
  autoload(:Call, "graphql/call")
9
8
  autoload(:Connection, "graphql/connection")
10
- autoload(:Field, "graphql/field")
11
9
  autoload(:FieldDefiner, "graphql/field_definer")
10
+ autoload(:Field, "graphql/field")
12
11
  autoload(:Node, "graphql/node")
13
- autoload(:Parser, "graphql/parser")
14
12
  autoload(:Query, "graphql/query")
15
13
  autoload(:RootCall, "graphql/root_call")
16
14
  autoload(:RootCallArgument, "graphql/root_call_argument")
17
15
  autoload(:RootCallArgumentDefiner, "graphql/root_call_argument_definer")
18
- autoload(:Schema, "graphql/schema")
19
- autoload(:Transform, "graphql/transform")
20
16
  autoload(:VERSION, "graphql/version")
21
17
 
22
18
  # These objects are used for introspections (eg, responding to `schema()` calls).
23
19
  module Introspection
24
- autoload(:CallNode, "graphql/introspection/call_node")
20
+ autoload(:CallType, "graphql/introspection/call_type")
25
21
  autoload(:Connection, "graphql/introspection/connection")
26
- autoload(:FieldNode, "graphql/introspection/field_node")
22
+ autoload(:FieldType, "graphql/introspection/field_type")
27
23
  autoload(:RootCallArgumentNode, "graphql/introspection/root_call_argument_node")
28
- autoload(:RootCallNode, "graphql/introspection/root_call_node")
24
+ autoload(:RootCallType, "graphql/introspection/root_call_type")
29
25
  autoload(:SchemaCall, "graphql/introspection/schema_call")
30
- autoload(:SchemaNode, "graphql/introspection/schema_node")
26
+ autoload(:SchemaType, "graphql/introspection/schema_type")
31
27
  autoload(:TypeCall, "graphql/introspection/type_call")
32
- autoload(:TypeNode, "graphql/introspection/type_node")
28
+ autoload(:TypeType, "graphql/introspection/type_type")
29
+ end
30
+
31
+ # These objects are singletons used to parse queries
32
+ module Parser
33
+ autoload(:Parser, "graphql/parser/parser")
34
+ autoload(:Transform, "graphql/parser/transform")
35
+ end
36
+
37
+ # These objects are used to track the schema of the graph
38
+ module Schema
39
+ autoload(:ALL, "graphql/schema/all")
40
+ autoload(:Schema, "graphql/schema/schema")
41
+ autoload(:SchemaValidation, "graphql/schema/schema_validation")
33
42
  end
34
43
 
35
44
  # These objects are skinny wrappers for going from the AST to actual {Node} and {Field} instances.
36
45
  module Syntax
37
- autoload(:Call, "graphql/syntax/call")
38
- autoload(:Field, "graphql/syntax/field")
39
- autoload(:Query, "graphql/syntax/query")
40
- autoload(:Node, "graphql/syntax/node")
41
- autoload(:Variable, "graphql/syntax/variable")
46
+ autoload(:Call, "graphql/syntax/call")
47
+ autoload(:Field, "graphql/syntax/field")
48
+ autoload(:Query, "graphql/syntax/query")
49
+ autoload(:Fragment, "graphql/syntax/fragment")
50
+ autoload(:Node, "graphql/syntax/node")
51
+ autoload(:Variable, "graphql/syntax/variable")
42
52
  end
43
53
 
44
- # These fields wrap Ruby data types and some GraphQL internal values.
54
+ # These objects expose values
45
55
  module Types
46
- autoload(:BooleanField, "graphql/types/boolean_field")
47
- autoload(:ConnectionField, "graphql/types/connection_field")
48
- autoload(:CursorField, "graphql/types/cursor_field")
49
- autoload(:NumberField, "graphql/types/number_field")
50
- autoload(:ObjectField, "graphql/types/object_field")
51
- autoload(:StringField, "graphql/types/string_field")
52
- autoload(:TypeField, "graphql/types/type_field")
56
+ autoload(:BooleanType, "graphql/types/boolean_type")
57
+ autoload(:ObjectType, "graphql/types/object_type")
58
+ autoload(:StringType, "graphql/types/string_type")
59
+ autoload(:NumberType, "graphql/types/number_type")
53
60
  end
54
61
  # @abstract
55
62
  # Base class for all errors, so you can rescue from all graphql errors at once.
56
63
  class Error < RuntimeError; end
57
64
  # This node doesn't have a field with that name.
58
65
  class FieldNotDefinedError < Error
59
- def initialize(class_name, field_name)
60
- super("#{class_name}##{field_name} was requested, but it isn't defined. Defined fields are: #{SCHEMA.field_names}")
66
+ def initialize(node_class, field_name)
67
+ class_name = node_class.name
68
+ defined_field_names = node_class.all_fields.keys
69
+ super("#{class_name}##{field_name} was requested, but it isn't defined. Defined fields are: #{defined_field_names}")
70
+ end
71
+ end
72
+ # The class that this node is supposed to expose isn't defined
73
+ class ExposesClassMissingError < Error
74
+ def initialize(node_class)
75
+ super("#{node_class.name} exposes #{node_class.exposes_class_names.join(", ")}, but that class wasn't found.")
61
76
  end
62
77
  end
63
78
  # There's no Node defined for that kind of object.
@@ -92,12 +107,14 @@ module GraphQL
92
107
  end
93
108
  end
94
109
 
95
- PARSER = Parser.new
110
+ # Singleton {Parser::Parser} instance
111
+ PARSER = Parser::Parser.new
96
112
  # This singleton contains all defined nodes and fields.
97
- SCHEMA = Schema.instance
98
- TRANSFORM = Transform.new
113
+ SCHEMA = Schema::Schema.instance
114
+ # Singleton {Parser::Transform} instance
115
+ TRANSFORM = Parser::Transform.new
99
116
  # preload these so they're in SCHEMA
100
- ["types", "introspection"].each do |preload_dir|
117
+ ["introspection", "types"].each do |preload_dir|
101
118
  Dir["#{File.dirname(__FILE__)}/graphql/#{preload_dir}/*.rb"].each { |f| require f }
102
119
  end
103
120
  Node.field.__type__(:__type__)
data/lib/graphql/call.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Created by {Field.call}, used internally by GraphQL.
1
+ # Created by {Node.call}, used internally by GraphQL.
2
2
  class GraphQL::Call
3
3
  attr_reader :name, :lambda
4
4
  def initialize(name:, lambda:)
@@ -2,27 +2,27 @@
2
2
  #
3
3
  # Out of the box, the only field it has is `edges`, which provides access to the members of the collection.
4
4
  #
5
- # You can define a custom {Connection} to use. This allows you to define fields at the collection level (rather than the item level)
5
+ # You can define a custom {Connection} to use. This allows you to define fields and calls at the collection level (rather than the item level)
6
6
  #
7
- # Custom fields can access the collection as {Field#items}.
7
+ # You can access the collection as `target` ({Node#target}).
8
8
  #
9
9
  # @example
10
10
  # class UpvotesConnection < GraphQL::Collection
11
- # field.number(:count)
12
- # field.boolean(:any)
11
+ # type :upvotes # adds it to the schema
12
+ # call :first, -> (prev_items, first) { prev_items.first(first.to_i) }
13
+ # call :after, -> (prev_items, after) { prev_items.select {|i| i.id > after.to_i } }
13
14
  #
14
- # def count
15
- # items.count
16
- # end
15
+ # field.number(:count) # delegates to the underlying collection
16
+ # field.boolean(:any)
17
17
  #
18
18
  # def any
19
- # items.any?
19
+ # target.any?
20
20
  # end
21
21
  # end
22
22
  #
23
23
  # # Then, this connection will be used for connections whose names match:
24
24
  # class PostNode < GraphQL::Node
25
- # field.connection(:upvotes)
25
+ # field.upvotes(:upvotes)
26
26
  # # ^^ uses `UpvotesConnection` based on naming convention
27
27
  # end
28
28
  #
@@ -41,28 +41,15 @@
41
41
  # QUERY
42
42
  class GraphQL::Connection < GraphQL::Node
43
43
  exposes "Array"
44
- field.any(:edges)
45
-
46
- attr_reader :calls, :syntax_fields, :query
47
-
48
- def initialize(items, query:, fields: [])
49
- @target = items
50
- @syntax_fields = fields
51
- @query = query
52
- end
53
-
54
- # Returns the members of the collection, after any calls on the corresponding {Field} have been applied
55
- def items
56
- @target
57
- end
44
+ field.object(:edges)
58
45
 
59
46
  def edge_fields
60
47
  @edge_fields ||= syntax_fields.find { |f| f.identifier == "edges" }.fields
61
48
  end
62
49
 
63
50
  def edges
64
- raise "#{self.class} expected a connection, but got `nil`" if items.nil?
65
- items.map do |item|
51
+ raise "#{self.class} expected a connection, but got `nil`" if target.nil?
52
+ target.map do |item|
66
53
  node_class = GraphQL::SCHEMA.type_for_object(item)
67
54
  node = node_class.new(item, fields: edge_fields, query: query)
68
55
  res = node.as_result
@@ -74,15 +61,5 @@ class GraphQL::Connection < GraphQL::Node
74
61
  def default_schema_name
75
62
  name.split("::").last.sub(/Connection$/, '').underscore
76
63
  end
77
-
78
- attr_accessor :default_connection
79
- # Call this to make a the class the default connection
80
- # when one isn't found by name.
81
- def default_connection!
82
- GraphQL::Connection.default_connection = self
83
- end
84
-
85
64
  end
86
-
87
- self.default_connection!
88
65
  end
data/lib/graphql/field.rb CHANGED
@@ -1,182 +1,12 @@
1
- # {Field}s are used to safely lookup values on {Node}s. When you define a custom field, you can access it from {Node.field}
2
- #
3
- # `graphql` has built-in fields for some Ruby data types:
4
- # - `boolean`: {Types::BooleanField}
5
- # - `number`: {Types::NumberField}
6
- # - `string`: {Types::StringField}
7
- #
8
- # You can define custom fields that allow you to control how values are exposed.
9
- # - {Field.type} defines how it can be used inside {Node.field} calls.
10
- # - {Field.call} defines calls that can mutate the value before it is added to the response.
11
- #
12
- # @example
13
- # # For example, an `AddressField` which wraps a string but exposes address-specific information
14
- # class AddressField < GraphQL::Field
15
- # type :address
16
- # # ^^ now you can use it with `field.address` in node definitions
17
- #
18
- # # calls can modify the value:
19
- # # eg, get the numbers at the beginning:
20
- # call :house_number, -> (prev_value) { prev_value[/^\d*/]}
21
- # # get everything after a space:
22
- # call :street_name, -> (prev_value) { prev_value[/\s.*$/].strip }
23
- # end
24
- #
25
- # # Then, use it in a node definition:
26
- # class HouseNode < GraphQL::Node
27
- # exposes("House")
28
- # # create an `AddressField` for this node called `street_address`:
29
- # field.address(:street_address)
30
- # end
31
- #
32
- # # Then, use the field in queries:
33
- # <<QUERY
34
- # find_house(1) {
35
- # street_address,
36
- # street_address.house_number() as number,
37
- # street_address.street_name() as street,
38
- # }
39
- # QUERY
1
+ # Each {Node} has {Field}s that are used to look up connected nodes at query-time
40
2
  class GraphQL::Field
41
- attr_reader :query, :owner, :calls, :fields
42
- def initialize(query: nil, owner: nil, calls: [], fields: [])
43
- @query = query
44
- @owner = owner
45
- @calls = calls
46
- @fields = fields
3
+ attr_reader :type, :name
4
+ def initialize(name:, type:)
5
+ @name = name
6
+ @type = type.to_s
47
7
  end
48
8
 
49
- def raw_value
50
- owner.send(name)
9
+ def type_class
10
+ GraphQL::SCHEMA.get_type(type)
51
11
  end
52
-
53
- def as_result
54
- finished_value
55
- end
56
-
57
- def finished_value
58
- @finished_value ||= begin
59
- val = raw_value
60
- calls.each do |call|
61
- registered_call = self.class.calls[call.identifier]
62
- if registered_call.nil?
63
- raise "Call not found: #{self.class.name}##{call.identifier}"
64
- end
65
- val = registered_call.lambda.call(val, *call.arguments)
66
- end
67
- val
68
- end
69
- end
70
-
71
- # instance `const_get` reaches up to class namespace
72
- def const_get(const_name)
73
- self.class.const_get(const_name)
74
- rescue
75
- nil
76
- end
77
-
78
- # delegate to class constant
79
- ["name", "description"].each do |method_name|
80
- define_method(method_name) do
81
- const_get(method_name.upcase)
82
- end
83
- end
84
-
85
- class << self
86
- def inherited(child_class)
87
- GraphQL::SCHEMA.add_field(child_class)
88
- end
89
-
90
- def create_class(name:, owner_class:, type:, description: nil, connection_class_name: nil, node_class_name: nil)
91
- if type.is_a?(Symbol)
92
- type = GraphQL::SCHEMA.get_field(type)
93
- end
94
-
95
- field_superclass = type || self
96
- new_class = Class.new(field_superclass)
97
- new_class.const_set :NAME, name
98
- new_class.const_set :OWNER_CLASS, owner_class
99
- new_class.const_set :DESCRIPTION , description
100
- new_class.const_set :CONNECTION_CLASS_NAME, connection_class_name
101
- new_class.const_set :NODE_CLASS_NAME, node_class_name
102
- new_class
103
- end
104
-
105
- def to_s
106
- if const_defined?(:NAME)
107
- "<FieldClass: #{const_get(:OWNER_CLASS).name}::#{const_get(:NAME)}>"
108
- else
109
- super
110
- end
111
- end
112
- # @param [Symbol] type_name the name used for getting this field from the {GraphQL::SCHEMA}.
113
- # Defines the name used for getting fields of this type from the schema.
114
- # @example
115
- # # define the field with its type:
116
- # class IPAddressField < GraphQL::Field
117
- # type :ip_address
118
- # end
119
- #
120
- # # then, attach fields of this type to your nodes:
121
- # class ServerNode < GraphQL::Field
122
- # field.ip_address(:static_ip_address)
123
- # end
124
- def type(value_type_name)
125
- @value_type = value_type_name.to_s
126
- GraphQL::SCHEMA.add_field(self)
127
- end
128
-
129
- def value_type
130
- @value_type || superclass.value_type
131
- end
132
-
133
- def schema_name
134
- @value_type || (name.present? ? default_schema_name : nil)
135
- end
136
-
137
- def default_schema_name
138
- name.split("::").last.sub(/Field$/, '').underscore
139
- end
140
-
141
- def field_name
142
- const_get(:NAME)
143
- end
144
-
145
- def description
146
- const_get(:DESCRIPTION)
147
- end
148
-
149
- def calls
150
- superclass.calls.merge(_calls)
151
- rescue NoMethodError
152
- {}
153
- end
154
- # @param [String] name the identifier for this call
155
- # @param [lambda] operation the transformation this call makes
156
- #
157
- # Define a call that can be made on this field.
158
- # The `lambda` receives arguments:
159
- # - 1: `previous_value` -- the value of this field
160
- # - *: arguments passed in the query (as strings)
161
- #
162
- # @example
163
- # # upcase a string field:
164
- # call :upcase, -> (prev_value) { prev_value.upcase }
165
- # @example
166
- # # tests a number field:
167
- # call :greater_than, -> (prev_value, test_value) { prev_value > test_value.to_f }
168
- # # (`test_value` is passed in as a string)
169
- def call(name, lambda)
170
- _calls[name.to_s] = GraphQL::Call.new(name: name.to_s, lambda: lambda)
171
- end
172
-
173
- private
174
-
175
- def _calls
176
- @calls ||= {}
177
- end
178
-
179
- end
180
-
181
- type :any
182
12
  end
@@ -9,27 +9,17 @@ class GraphQL::FieldDefiner
9
9
  # `method_name` is used as a field type and looked up against {GraphQL::SCHEMA}.
10
10
  # `args[0]` is the name for the field of that type.
11
11
  def method_missing(method_name, *args, &block)
12
- type = GraphQL::SCHEMA.get_field(method_name)
13
- if type.present?
14
- create_field(args[0], type: type, description: args[1])
15
- else
16
- super
17
- end
12
+ map_field(args[0], type: method_name, description: args[1])
18
13
  end
19
14
 
20
15
  private
21
16
 
22
- def create_field(field_name, type: nil, description: nil)
17
+ def map_field(field_name, type: nil, description: nil)
23
18
  field_name = field_name.to_s
24
- field_class = GraphQL::Field.create_class({
19
+ mapping = GraphQL::Field.new(
25
20
  name: field_name,
26
21
  type: type,
27
- owner_class: owner_class,
28
- description: description,
29
- })
30
-
31
- field_class_name = field_name.camelize + "Field"
32
- owner_class.const_set(field_class_name, field_class)
33
- owner_class.own_fields[field_name] = field_class
22
+ )
23
+ owner_class.own_fields[field_name] = mapping
34
24
  end
35
25
  end