graphql 0.0.3 → 0.0.4

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 (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