graphql-api 0.1.0
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +256 -0
- data/Rakefile +34 -0
- data/lib/graphql/api.rb +16 -0
- data/lib/graphql/api/command_type.rb +24 -0
- data/lib/graphql/api/helpers.rb +88 -0
- data/lib/graphql/api/query_type.rb +24 -0
- data/lib/graphql/api/schema.rb +284 -0
- data/lib/graphql/api/schema_error.rb +4 -0
- data/lib/graphql/api/version.rb +5 -0
- metadata +102 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 213d171fcd46bcfca65679b3af693a3c77e9e3eb
|
4
|
+
data.tar.gz: ebfd301985d54acc70f1150b85a86c2f2f3be9f8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5aa16322431206472f7d22de81f61d516d44b93357aa3b6d45e3c6776dd2983e27b7440015c853466188c1ce01ab04c050124b6a0034903fa27a60d5d07cfb1a
|
7
|
+
data.tar.gz: d9cfc59ecdd63c43d2f9c4a8f74912d340d1decd7fa339f3cfbf2e81ad206890a110bba893cc0a520b7e6908e42bce08f23e90228b84618cddb72b4e0e30a0d1
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2016 Colin Walker
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,256 @@
|
|
1
|
+
# GraphQL-Api
|
2
|
+
GraphQL-Api is an opinionated Graphql framework for Rails that supports
|
3
|
+
auto generating queries based on Active Record models and plain Ruby
|
4
|
+
objects.
|
5
|
+
|
6
|
+
## Example
|
7
|
+
|
8
|
+
Given the following model structure:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
class Author < ActiveRecord::Base
|
12
|
+
has_many :blogs
|
13
|
+
# columns: name
|
14
|
+
end
|
15
|
+
|
16
|
+
class Blog < ActiveRecord::Base
|
17
|
+
belongs_to :author
|
18
|
+
# columns: title, content
|
19
|
+
end
|
20
|
+
```
|
21
|
+
|
22
|
+
GraphQL-Api will respond to the following queries for the blog resource:
|
23
|
+
|
24
|
+
```graphql
|
25
|
+
query { blog(id: 1) { id, title, author { name } } }
|
26
|
+
|
27
|
+
query { blogs(limit: 5) { id, title, author { name } } }
|
28
|
+
|
29
|
+
mutation { createBlog(input: {name: "test", author_id: 2}) { blog { id } } }
|
30
|
+
|
31
|
+
mutation { updateBlog(input: {id: 1, title: "test"}) { blog { id } } }
|
32
|
+
|
33
|
+
mutation { deleteBlog(input: {id: 1}) { blog_id } }
|
34
|
+
```
|
35
|
+
|
36
|
+
GraphQL-Api also has support for command objects:
|
37
|
+
```ruby
|
38
|
+
# Graphql mutation derived from the below command object:
|
39
|
+
# mutation { blogCreateCommand(input: {tags: ["test", "testing"], name: "hello"}) { blog { id, tags { name } } } }
|
40
|
+
|
41
|
+
class BlogCreateCommand < GraphQL::Api::CommandType
|
42
|
+
inputs name: :string, tags: [:string]
|
43
|
+
returns blog: Blog
|
44
|
+
|
45
|
+
def perform
|
46
|
+
# do something here to add some tags to a blog, you could also use ctx[:current_user] to access the user
|
47
|
+
{blog: blog}
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
... and query objects:
|
54
|
+
```ruby
|
55
|
+
# Graphql query derived from the below query object:
|
56
|
+
# query { blogQuery(content_matches: ["name"]) { id, name } }
|
57
|
+
|
58
|
+
class BlogQuery < GraphQL::Api::QueryType
|
59
|
+
arguments name: :string, content_matches: [:string]
|
60
|
+
return_type [Blog]
|
61
|
+
|
62
|
+
def execute
|
63
|
+
Blog.all
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
## Contents
|
70
|
+
|
71
|
+
1. [Guides](#guides)
|
72
|
+
2. [Documentation](#documentation)
|
73
|
+
3. [Roadmap](#roadmap)
|
74
|
+
|
75
|
+
## Guides
|
76
|
+
|
77
|
+
### Endpoint
|
78
|
+
|
79
|
+
Creating an endpoint for GraphQL-Api.
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
# inside an initializer or other file inside the load path
|
83
|
+
GraphSchema = GraphQL::Api::Schema.new.schema
|
84
|
+
|
85
|
+
# controllers/graphql_controller.rb
|
86
|
+
class GraphqlController < ApplicationController
|
87
|
+
|
88
|
+
# needed by the relay framework, defines the graphql schema
|
89
|
+
def index
|
90
|
+
render json: GraphSchema.execute(GraphQL::Introspection::INTROSPECTION_QUERY)
|
91
|
+
end
|
92
|
+
|
93
|
+
# will respond to graphql requests and pass through the current user
|
94
|
+
def create
|
95
|
+
render json: GraphSchema.execute(
|
96
|
+
params[:query],
|
97
|
+
variables: params[:variables] || {},
|
98
|
+
context: {current_user: current_user}
|
99
|
+
)
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
```
|
104
|
+
|
105
|
+
### Authorization
|
106
|
+
|
107
|
+
GraphQL-Api will check for an `access_<field>?(ctx)` method on all model
|
108
|
+
objects before returning the value. If this method returns false, the
|
109
|
+
value will be `nil`.
|
110
|
+
|
111
|
+
To scope queries for the model, define the `graph_find(args, ctx)` and
|
112
|
+
`graph_where(args, ctx)` methods using the `ctx` parameter to get the
|
113
|
+
current user and apply a scoped query. For example:
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
class Blog < ActiveRecord::Base
|
117
|
+
belongs_to :author
|
118
|
+
|
119
|
+
def self.graph_find(args, ctx)
|
120
|
+
ctx[:current_user].blogs.find(args[:id])
|
121
|
+
end
|
122
|
+
|
123
|
+
def access_content?(ctx)
|
124
|
+
ctx[:current_user].is_admin?
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
```
|
129
|
+
|
130
|
+
For more complicated access management, define query objects and Poro's
|
131
|
+
with only a subset of fields that can be accessed.
|
132
|
+
|
133
|
+
Future work is this area is ongoing. For example, a CanCan integration
|
134
|
+
could be a much simpler way of separating out this logic.
|
135
|
+
|
136
|
+
## Documentation
|
137
|
+
|
138
|
+
### Querying
|
139
|
+
|
140
|
+
Instantiate an instance of GraphQL-Api and get the `schema` object which is
|
141
|
+
a `GraphQL::Schema` instance from [graphql-ruby](https://rmosolgo.github.io/graphql-ruby).
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
graph = GraphQL::Api::Schema.new(commands: [], models: [], queries: [])
|
145
|
+
graph.schema.execute('query { ... }')
|
146
|
+
```
|
147
|
+
|
148
|
+
GraphQL-Api will load in all models, query objects and commands from the rails
|
149
|
+
app directory automatically. If you store these in a different location
|
150
|
+
you can pass them in directly to the new command.
|
151
|
+
|
152
|
+
### Model Objects
|
153
|
+
|
154
|
+
Model objects are the core return value from GraphQL-Api. They can be a plain
|
155
|
+
old ruby object or they can be an active record model. Active record models
|
156
|
+
have more automatic inference, whereas poro objects are more flexible.
|
157
|
+
|
158
|
+
#### Active Record
|
159
|
+
|
160
|
+
GraphQL-Api reads your associations and columns from your models and creates
|
161
|
+
a graphql schema from them. In the examples above you can see that 'Author'
|
162
|
+
is automatically accessible from the 'Blog' object because the belongs to
|
163
|
+
relationship is set up. Column types are also inferred.
|
164
|
+
|
165
|
+
GraphQL-Api will set up two queries on the main Graphql query object. One for
|
166
|
+
a single record and another for a collection. You can override these queries
|
167
|
+
by setting a `graph_find(args, ctx)` and `graph_where(args, ctx)` class
|
168
|
+
methods on your model. The `ctx` parameter will contain the context passed
|
169
|
+
in from the controller while the `args` parameter will contain the arguments
|
170
|
+
passed into the graphql query.
|
171
|
+
|
172
|
+
#### Poro
|
173
|
+
|
174
|
+
Plain old ruby objects are supported by implementing a class method called
|
175
|
+
`fields` on the object that returns the expected [types](#types) hash.
|
176
|
+
Methods on the Poro should be defined with the same name as the provided
|
177
|
+
fields.
|
178
|
+
|
179
|
+
### Command Objects
|
180
|
+
|
181
|
+
Command objects are an object oriented approach to defining mutations.
|
182
|
+
They take a set of inputs as well as a graphql context and provide a
|
183
|
+
`perform` method that returns a Graphql understandable type. These objects
|
184
|
+
give you an object oriented abstraction for handling mutations.
|
185
|
+
|
186
|
+
Command objects must implement the interface defined in `GraphQL::Api::CommandType`
|
187
|
+
|
188
|
+
### Query Objects
|
189
|
+
|
190
|
+
Query objects are designed to provide a wrapper around complex queries
|
191
|
+
with potentially a lot of inputs. They return a single type or array of
|
192
|
+
types.
|
193
|
+
|
194
|
+
Query objects must implement the interface defined in `GraphQL::Api::QueryType`
|
195
|
+
|
196
|
+
### Customization
|
197
|
+
|
198
|
+
Sometimes you cannot fit every possible use case into a library like GraphQL-Api
|
199
|
+
as a result, you can always drop down to the excellent Graphql library for
|
200
|
+
ruby to combine both hand rolled and GraphQL-Api graphql schemas. Here is an
|
201
|
+
example creating a custom mutation.
|
202
|
+
|
203
|
+
```ruby
|
204
|
+
simple_mutation = GraphQL::Relay::Mutation.define do
|
205
|
+
input_field :name, !types.String
|
206
|
+
return_field :item, types.String
|
207
|
+
resolve -> (inputs, ctx) { {item: 'hello'} }
|
208
|
+
end
|
209
|
+
|
210
|
+
graph = GraphQL::Api::Schema.new
|
211
|
+
mutation = graph.mutation do
|
212
|
+
field 'simpleMutation', simple_mutation.field
|
213
|
+
end
|
214
|
+
|
215
|
+
schema = GraphQL::Schema.define(query: graph.query, mutation: mutation)
|
216
|
+
puts schema.execute('mutation { simpleMutation(input: {name: "hello"}) { item } }')
|
217
|
+
```
|
218
|
+
|
219
|
+
The `GraphQL::Api::Schema#mutation` and `GraphQL::Api::Schema#query` methods accept
|
220
|
+
a block that allows you to add custom fields or methods to the mutation or
|
221
|
+
query definitions. You can refer to the [graphql-ruby](https://rmosolgo.github.io/graphql-ruby)
|
222
|
+
docs for how to do this.
|
223
|
+
|
224
|
+
### Types
|
225
|
+
|
226
|
+
Field types and argument types are all supplied as a hash of key value
|
227
|
+
pairs. An exclamation mark at the end of the type marks it as required,
|
228
|
+
and wrapping the type in an array marks it as a list of that type.
|
229
|
+
|
230
|
+
```ruby
|
231
|
+
{
|
232
|
+
name: :string,
|
233
|
+
more_names: [:string],
|
234
|
+
required: :integer!,
|
235
|
+
}
|
236
|
+
```
|
237
|
+
|
238
|
+
The supported types are:
|
239
|
+
|
240
|
+
- integer
|
241
|
+
- text
|
242
|
+
- string
|
243
|
+
- decimal
|
244
|
+
- float
|
245
|
+
- boolean
|
246
|
+
|
247
|
+
Note, these are the same as active record's column types for consistency.
|
248
|
+
|
249
|
+
|
250
|
+
## Roadmap
|
251
|
+
|
252
|
+
- [ ] Customizing resolvers
|
253
|
+
- [ ] CanCan support
|
254
|
+
- [ ] Relay support
|
255
|
+
- [ ] Additional object support (enums, interfaces ...)
|
256
|
+
- [ ] Support non rails frameworks
|
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'Graphite'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
require 'bundler/gem_tasks'
|
23
|
+
|
24
|
+
require 'rake/testtask'
|
25
|
+
|
26
|
+
Rake::TestTask.new(:test) do |t|
|
27
|
+
t.libs << 'lib'
|
28
|
+
t.libs << 'test'
|
29
|
+
t.pattern = 'test/**/*_test.rb'
|
30
|
+
t.verbose = false
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
task default: :test
|
data/lib/graphql/api.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "graphql/api/version"
|
2
|
+
require "graphql/api/schema"
|
3
|
+
|
4
|
+
module GraphQL
|
5
|
+
module Api
|
6
|
+
|
7
|
+
def self.schema(opts={})
|
8
|
+
GraphQL::Api::Schema.new(opts).schema
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.graph(opts={})
|
12
|
+
GraphQL::Api::Schema.new(opts)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module GraphQL::Api
|
2
|
+
class CommandType
|
3
|
+
attr_accessor :ctx, :inputs
|
4
|
+
|
5
|
+
def initialize(inputs, ctx)
|
6
|
+
@inputs = inputs
|
7
|
+
@ctx = ctx
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.inputs(inputs=nil)
|
11
|
+
@inputs = inputs if inputs
|
12
|
+
@inputs || {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.returns(fields=nil)
|
16
|
+
@returns = fields if fields
|
17
|
+
@returns || {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def perform
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'graphql/api/schema_error'
|
2
|
+
|
3
|
+
module GraphQL::Api
|
4
|
+
module Helpers
|
5
|
+
def all_constants(root)
|
6
|
+
begin
|
7
|
+
Dir["#{Rails.root}/app/#{root}/*"].map do |f|
|
8
|
+
file = f.split('/')[-1]
|
9
|
+
if file.end_with?('.rb')
|
10
|
+
const = file.split('.')[0].camelize.constantize
|
11
|
+
const unless const.try(:abstract_class)
|
12
|
+
end
|
13
|
+
end.compact
|
14
|
+
rescue
|
15
|
+
[]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def graphql_type_for_object(return_type, object_types)
|
20
|
+
if return_type.nil?
|
21
|
+
raise SchemaError.new("return type is nil for object")
|
22
|
+
end
|
23
|
+
|
24
|
+
if return_type.respond_to?(:to_sym) || (return_type.is_a?(Array) && return_type[0].respond_to?(:to_sym))
|
25
|
+
type = graphql_type_of(return_type.to_sym)
|
26
|
+
elsif return_type.is_a?(Array)
|
27
|
+
type = object_types[return_type[0]].to_list_type
|
28
|
+
else
|
29
|
+
type = object_types[return_type]
|
30
|
+
end
|
31
|
+
|
32
|
+
if type.nil?
|
33
|
+
raise SchemaError.new("could not parse return type for: #{return_type}")
|
34
|
+
end
|
35
|
+
|
36
|
+
type
|
37
|
+
end
|
38
|
+
|
39
|
+
def graphql_type_of(type)
|
40
|
+
|
41
|
+
is_required = false
|
42
|
+
if type.to_s.end_with?('!')
|
43
|
+
is_required = true
|
44
|
+
type = type.to_s.chomp('!').to_sym
|
45
|
+
end
|
46
|
+
|
47
|
+
is_list = false
|
48
|
+
if type.is_a?(Array)
|
49
|
+
is_list = true
|
50
|
+
type = type[0]
|
51
|
+
end
|
52
|
+
|
53
|
+
case type
|
54
|
+
when :integer
|
55
|
+
res = GraphQL::INT_TYPE
|
56
|
+
when :text
|
57
|
+
res = GraphQL::STRING_TYPE
|
58
|
+
when :string
|
59
|
+
res = GraphQL::STRING_TYPE
|
60
|
+
when :decimal
|
61
|
+
res = GraphQL::FLOAT_TYPE
|
62
|
+
when :float
|
63
|
+
res = GraphQL::FLOAT_TYPE
|
64
|
+
when :boolean
|
65
|
+
res = GraphQL::BOOLEAN_TYPE
|
66
|
+
else
|
67
|
+
res = GraphQL::STRING_TYPE
|
68
|
+
end
|
69
|
+
|
70
|
+
res = res.to_list_type if is_list
|
71
|
+
res = !res if is_required
|
72
|
+
|
73
|
+
res
|
74
|
+
end
|
75
|
+
|
76
|
+
def graphql_type(column)
|
77
|
+
graphql_type_of(column.type)
|
78
|
+
end
|
79
|
+
|
80
|
+
def graphql_fetch(obj, ctx, name)
|
81
|
+
if obj.respond_to?("access_#{name}?")
|
82
|
+
obj.send(name) if obj.send("access_#{name}?", ctx)
|
83
|
+
else
|
84
|
+
obj.send(name)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module GraphQL::Api
|
2
|
+
class QueryType
|
3
|
+
attr_accessor :ctx, :inputs
|
4
|
+
|
5
|
+
def initialize(inputs, ctx)
|
6
|
+
@inputs = inputs
|
7
|
+
@ctx = ctx
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.arguments(arguments=nil)
|
11
|
+
@arguments = arguments if arguments
|
12
|
+
@arguments || []
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.return_type(type=nil)
|
16
|
+
@return_type = type if type
|
17
|
+
@return_type
|
18
|
+
end
|
19
|
+
|
20
|
+
def execute
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,284 @@
|
|
1
|
+
require 'graphql/api/command_type'
|
2
|
+
require 'graphql/api/query_type'
|
3
|
+
require 'graphql/api/helpers'
|
4
|
+
require 'graphql/api/schema_error'
|
5
|
+
require 'graphql'
|
6
|
+
|
7
|
+
include GraphQL::Api::Helpers
|
8
|
+
|
9
|
+
module GraphQL::Api
|
10
|
+
class Schema
|
11
|
+
|
12
|
+
def initialize(commands: [], queries: [], models: [])
|
13
|
+
@types = {}
|
14
|
+
@mutations = {}
|
15
|
+
|
16
|
+
@load_commands = commands
|
17
|
+
@load_queries = queries
|
18
|
+
@load_models = models
|
19
|
+
|
20
|
+
build_model_types
|
21
|
+
build_mutations
|
22
|
+
build_object_types
|
23
|
+
end
|
24
|
+
|
25
|
+
def all_models
|
26
|
+
@all_models ||= all_constants('models') + @load_models
|
27
|
+
end
|
28
|
+
|
29
|
+
def all_queries
|
30
|
+
@all_queries ||= all_constants('queries') + @load_queries
|
31
|
+
end
|
32
|
+
|
33
|
+
def all_commands
|
34
|
+
@all_commands ||= all_constants('commands') + @load_commands
|
35
|
+
end
|
36
|
+
|
37
|
+
def create_type(model_class)
|
38
|
+
object_types = @types
|
39
|
+
|
40
|
+
GraphQL::ObjectType.define do
|
41
|
+
name model_class.name
|
42
|
+
description "Get #{model_class.name}"
|
43
|
+
|
44
|
+
if model_class.respond_to?(:columns)
|
45
|
+
model_class.columns.each do |column|
|
46
|
+
field column.name do
|
47
|
+
type graphql_type(column)
|
48
|
+
resolve -> (obj, args, ctx) { graphql_fetch(obj, ctx, column.name) }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
if model_class.respond_to?(:fields)
|
54
|
+
model_class.fields.each do |field_name, field_type|
|
55
|
+
field field_name, graphql_type_of(field_type)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
if model_class.respond_to?(:reflections)
|
60
|
+
model_class.reflections.each do |name, association|
|
61
|
+
field name do
|
62
|
+
if association.collection?
|
63
|
+
type types[object_types[association.class_name.constantize]]
|
64
|
+
else
|
65
|
+
type object_types[association.class_name.constantize]
|
66
|
+
end
|
67
|
+
resolve -> (obj, args, ctx) { graphql_fetch(obj, ctx, name) }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def create_command_type(object_type)
|
76
|
+
object_types = @types
|
77
|
+
|
78
|
+
GraphQL::Relay::Mutation.define do
|
79
|
+
name object_type.name
|
80
|
+
description "Command #{object_type.name}"
|
81
|
+
|
82
|
+
object_type.inputs.each do |input, type|
|
83
|
+
input_field input, graphql_type_of(type)
|
84
|
+
end
|
85
|
+
|
86
|
+
object_type.returns.each do |return_name, return_type|
|
87
|
+
return_field return_name, graphql_type_for_object(return_type, object_types)
|
88
|
+
end
|
89
|
+
|
90
|
+
resolve -> (inputs, ctx) {
|
91
|
+
object_type.new(inputs, ctx).perform
|
92
|
+
}
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def create_mutation(model_class)
|
97
|
+
return nil unless model_class < ActiveRecord::Base
|
98
|
+
|
99
|
+
object_types = @types
|
100
|
+
|
101
|
+
GraphQL::Relay::Mutation.define do
|
102
|
+
name "Create#{model_class.name}"
|
103
|
+
description "Create #{model_class.name}"
|
104
|
+
|
105
|
+
model_class.columns.each do |column|
|
106
|
+
input_field column.name, graphql_type(column)
|
107
|
+
end
|
108
|
+
|
109
|
+
return_field model_class.name.underscore.to_sym, object_types[model_class]
|
110
|
+
|
111
|
+
resolve -> (inputs, ctx) {
|
112
|
+
item = model_class.create!(inputs.to_h)
|
113
|
+
{model_class.name.underscore.to_sym => item}
|
114
|
+
}
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def update_mutation(model_class)
|
119
|
+
return nil unless model_class < ActiveRecord::Base
|
120
|
+
|
121
|
+
object_types = @types
|
122
|
+
|
123
|
+
GraphQL::Relay::Mutation.define do
|
124
|
+
name "Update#{model_class.name}"
|
125
|
+
description "Update #{model_class.name}"
|
126
|
+
|
127
|
+
input_field :id, !types.ID
|
128
|
+
model_class.columns.each do |column|
|
129
|
+
input_field column.name, graphql_type(column)
|
130
|
+
end
|
131
|
+
|
132
|
+
return_field model_class.name.underscore.to_sym, object_types[model_class]
|
133
|
+
|
134
|
+
resolve -> (inputs, ctx) {
|
135
|
+
item = model_class.find(inputs[:id])
|
136
|
+
item.update!(inputs.to_h)
|
137
|
+
{model_class.name.underscore.to_sym => item}
|
138
|
+
}
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def delete_mutation(model_class)
|
143
|
+
return nil unless model_class < ActiveRecord::Base
|
144
|
+
|
145
|
+
GraphQL::Relay::Mutation.define do
|
146
|
+
name "Delete#{model_class.name}"
|
147
|
+
description "Delete #{model_class.name}"
|
148
|
+
|
149
|
+
input_field :id, !types.ID
|
150
|
+
|
151
|
+
return_field "#{model_class.name.underscore}_id".to_sym, types.ID
|
152
|
+
|
153
|
+
resolve -> (inputs, ctx) {
|
154
|
+
item = model_class.find(inputs[:id]).destroy!
|
155
|
+
{"#{model_class.name.underscore}_id".to_sym => item.id}
|
156
|
+
}
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def query(&block)
|
161
|
+
object_types = @types
|
162
|
+
|
163
|
+
@query ||= GraphQL::ObjectType.define do
|
164
|
+
name 'Query'
|
165
|
+
description 'The query root for this schema'
|
166
|
+
|
167
|
+
instance_eval(&block) if block
|
168
|
+
|
169
|
+
object_types.each do |object_class, graph_type|
|
170
|
+
if object_class < ActiveRecord::Base
|
171
|
+
|
172
|
+
field(object_class.name.camelize(:lower)) do
|
173
|
+
type graph_type
|
174
|
+
argument :id, types.ID
|
175
|
+
|
176
|
+
if object_class.respond_to?(:arguments)
|
177
|
+
object_class.arguments.each do |arg|
|
178
|
+
argument arg, graphql_type(object_class.columns.find { |c| c.name.to_sym == arg.to_sym })
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
resolve -> (obj, args, ctx) {
|
183
|
+
if object_class.respond_to?(:graph_find)
|
184
|
+
object_class.graph_find(args, ctx)
|
185
|
+
else
|
186
|
+
object_class.find_by!(args.to_h)
|
187
|
+
end
|
188
|
+
}
|
189
|
+
end
|
190
|
+
|
191
|
+
field(object_class.name.camelize(:lower).pluralize) do
|
192
|
+
type types[graph_type]
|
193
|
+
argument :limit, types.Int
|
194
|
+
|
195
|
+
if object_class.respond_to?(:arguments)
|
196
|
+
object_class.arguments.each do |arg|
|
197
|
+
argument arg, graphql_type(object_class.columns.find { |c| c.name.to_sym == arg.to_sym })
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
resolve -> (obj, args, ctx) {
|
202
|
+
if object_class.respond_to?(:graph_where)
|
203
|
+
object_class.graph_where(args, ctx)
|
204
|
+
else
|
205
|
+
eager_load = []
|
206
|
+
ctx.irep_node.children.each do |child|
|
207
|
+
eager_load << child[0] if object_class.reflections.find { |name, _| name == child[0] }
|
208
|
+
end
|
209
|
+
|
210
|
+
query_args = args.to_h
|
211
|
+
query_args.delete('limit')
|
212
|
+
|
213
|
+
q = object_class.where(query_args)
|
214
|
+
q.eager_load(*eager_load) if eager_load.any?
|
215
|
+
q.limit(args[:limit] || 30)
|
216
|
+
end
|
217
|
+
}
|
218
|
+
end
|
219
|
+
|
220
|
+
elsif object_class.respond_to?(:arguments) && object_class.respond_to?(:return_type)
|
221
|
+
|
222
|
+
field(object_class.name.camelize(:lower)) do
|
223
|
+
type(graphql_type_for_object(object_class.return_type, object_types))
|
224
|
+
|
225
|
+
object_class.arguments.each do |argument_name, argument_type|
|
226
|
+
argument argument_name, graphql_type_of(argument_type)
|
227
|
+
end
|
228
|
+
|
229
|
+
resolve -> (obj, args, ctx) {
|
230
|
+
object_class.new(args, ctx).execute
|
231
|
+
}
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def mutation(&block)
|
241
|
+
mutations = @mutations
|
242
|
+
|
243
|
+
@mutation ||= GraphQL::ObjectType.define do
|
244
|
+
name 'Mutation'
|
245
|
+
instance_eval(&block) if block
|
246
|
+
|
247
|
+
mutations.each do |model_class, muts|
|
248
|
+
muts.each do |mutation|
|
249
|
+
field mutation[0], field: mutation[1].field
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def schema
|
256
|
+
@schema ||= GraphQL::Schema.define(query: query, mutation: mutation)
|
257
|
+
end
|
258
|
+
|
259
|
+
def build_model_types
|
260
|
+
all_models.each { |model_class| @types[model_class] = create_type(model_class) }
|
261
|
+
end
|
262
|
+
|
263
|
+
def build_object_types
|
264
|
+
all_queries.each { |query| @types[query] = nil }
|
265
|
+
end
|
266
|
+
|
267
|
+
def build_mutations
|
268
|
+
all_models.each do |model_class|
|
269
|
+
@mutations[model_class] = [
|
270
|
+
["create#{model_class.name}", create_mutation(model_class)],
|
271
|
+
["update#{model_class.name}", update_mutation(model_class)],
|
272
|
+
["delete#{model_class.name}", delete_mutation(model_class)],
|
273
|
+
].map { |x| x if x[1] }.compact
|
274
|
+
end
|
275
|
+
|
276
|
+
all_commands.each do |command|
|
277
|
+
@mutations[command] = [
|
278
|
+
[command.name.camelize(:lower), create_command_type(command)]
|
279
|
+
]
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
end
|
284
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: graphql-api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Colin Walker
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-10-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 5.0.0
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 5.0.0.1
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 5.0.0
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 5.0.0.1
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: graphql
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: sqlite3
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
description:
|
62
|
+
email:
|
63
|
+
- colinwalker270@gmail.com
|
64
|
+
executables: []
|
65
|
+
extensions: []
|
66
|
+
extra_rdoc_files: []
|
67
|
+
files:
|
68
|
+
- MIT-LICENSE
|
69
|
+
- README.md
|
70
|
+
- Rakefile
|
71
|
+
- lib/graphql/api.rb
|
72
|
+
- lib/graphql/api/command_type.rb
|
73
|
+
- lib/graphql/api/helpers.rb
|
74
|
+
- lib/graphql/api/query_type.rb
|
75
|
+
- lib/graphql/api/schema.rb
|
76
|
+
- lib/graphql/api/schema_error.rb
|
77
|
+
- lib/graphql/api/version.rb
|
78
|
+
homepage: https://github.com/coldog/graphql-api
|
79
|
+
licenses:
|
80
|
+
- MIT
|
81
|
+
metadata: {}
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options: []
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
requirements: []
|
97
|
+
rubyforge_project:
|
98
|
+
rubygems_version: 2.5.1
|
99
|
+
signing_key:
|
100
|
+
specification_version: 4
|
101
|
+
summary: Rails graphql framework.
|
102
|
+
test_files: []
|