graphql-api 0.1.5 → 0.1.6
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 +46 -25
- data/lib/graphql/api/policy.rb +6 -2
- data/lib/graphql/api/resolvers/field.rb +5 -6
- data/lib/graphql/api/resolvers/model_create_mutation.rb +2 -4
- data/lib/graphql/api/resolvers/model_delete_mutation.rb +2 -4
- data/lib/graphql/api/resolvers/model_find_query.rb +2 -4
- data/lib/graphql/api/resolvers/model_list_query.rb +11 -1
- data/lib/graphql/api/resolvers/model_update_mutation.rb +2 -4
- data/lib/graphql/api/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d066b645b42c6471423c84254b296abe806d8bda
|
4
|
+
data.tar.gz: dcbc217929b63f258c12c63f392b373acbe6d9dd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e3b258f21b2d9d8d5de142659f3334c22d37ec2cf260c448dc6cb166b2461d22bb46ba033a65b2738e5258ee99b4f5e1a0fa4bd82fba6dc9a42d727e1c8718aa
|
7
|
+
data.tar.gz: eb187d750c57364262bccc79ac107fd331de3c34e51bdc625f1d5003b0a0491863376d57f50051d416c3a3d8ced201ed210f8f348497a87fcbc4507ad2d49908
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
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
|
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
4
|
objects.
|
5
5
|
|
6
6
|
## Example
|
@@ -93,46 +93,43 @@ class GraphqlController < ApplicationController
|
|
93
93
|
# will respond to graphql requests and pass through the current user
|
94
94
|
def create
|
95
95
|
render json: GraphSchema.execute(
|
96
|
-
params[:query],
|
97
|
-
variables: params[:variables] || {},
|
96
|
+
params[:query],
|
97
|
+
variables: params[:variables] || {},
|
98
98
|
context: {current_user: current_user}
|
99
99
|
)
|
100
100
|
end
|
101
|
-
|
101
|
+
|
102
102
|
end
|
103
103
|
```
|
104
104
|
|
105
105
|
### Authorization
|
106
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
|
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
109
|
value will be `nil`.
|
110
110
|
|
111
|
-
To scope queries for the model, define the `graph_find(args, ctx)` and
|
111
|
+
To scope queries for the model, define the `graph_find(args, ctx)` and
|
112
112
|
`graph_where(args, ctx)` methods using the `ctx` parameter to get the
|
113
113
|
current user and apply a scoped query. For example:
|
114
114
|
|
115
115
|
```ruby
|
116
116
|
class Blog < ActiveRecord::Base
|
117
117
|
belongs_to :author
|
118
|
-
|
118
|
+
|
119
119
|
def self.graph_find(args, ctx)
|
120
120
|
ctx[:current_user].blogs.find(args[:id])
|
121
121
|
end
|
122
|
-
|
122
|
+
|
123
123
|
def access_content?(ctx)
|
124
124
|
ctx[:current_user].is_admin?
|
125
125
|
end
|
126
|
-
|
126
|
+
|
127
127
|
end
|
128
128
|
```
|
129
129
|
|
130
130
|
For more complicated access management, define query objects and Poro's
|
131
131
|
with only a subset of fields that can be accessed.
|
132
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
133
|
## Documentation
|
137
134
|
|
138
135
|
### Querying
|
@@ -151,14 +148,39 @@ you can pass them in directly to the new command.
|
|
151
148
|
|
152
149
|
### Policy Objects
|
153
150
|
|
154
|
-
Policy objects can be
|
155
|
-
|
151
|
+
Policy objects can be created for controlling access to fields of a model.
|
152
|
+
This enables you to provide a DRY solution for controlling access to granular
|
153
|
+
attributes of models and PORO's.
|
156
154
|
|
157
|
-
|
155
|
+
The policy object is defined as follows, with the naming convention following
|
156
|
+
the pattern `{Model}Policy`.
|
157
|
+
```ruby
|
158
|
+
class UserPolicy < GraphQL::Api::Policy
|
158
159
|
|
159
|
-
|
160
|
-
|
160
|
+
def read?
|
161
|
+
# model is the instance, user is the current_user
|
162
|
+
user.role == 'admin' || user.id == model.id
|
163
|
+
end
|
161
164
|
|
165
|
+
end
|
166
|
+
```
|
167
|
+
There are four key methods that represent the CRUD operations:
|
168
|
+
- `read?`
|
169
|
+
- `create?`
|
170
|
+
- `update?`
|
171
|
+
- `destroy?`
|
172
|
+
|
173
|
+
These are checked by the default resolvers created by GraphQL-Api
|
174
|
+
before the mutations or queries take place. Additionally, for control
|
175
|
+
over access to fields, you may define a method on the policy with the
|
176
|
+
following form:
|
177
|
+
|
178
|
+
access_{field}?
|
179
|
+
|
180
|
+
If this method exists, the policy will be consulted before reading the
|
181
|
+
field. You can control the response behaviour by overriding the
|
182
|
+
`unauthorized_field_access(field_name)` method on the policy object.
|
183
|
+
By default it will return nil.
|
162
184
|
|
163
185
|
### Model Objects
|
164
186
|
|
@@ -175,7 +197,7 @@ relationship is set up. Column types are also inferred.
|
|
175
197
|
|
176
198
|
GraphQL-Api will set up two queries on the main Graphql query object. One for
|
177
199
|
a single record and another for a collection. You can override these queries
|
178
|
-
by setting a `graph_find(args, ctx)` and `graph_where(args, ctx)` class
|
200
|
+
by setting a `graph_find(args, ctx)` and `graph_where(args, ctx)` class
|
179
201
|
methods on your model. The `ctx` parameter will contain the context passed
|
180
202
|
in from the controller while the `args` parameter will contain the arguments
|
181
203
|
passed into the graphql query.
|
@@ -183,7 +205,7 @@ passed into the graphql query.
|
|
183
205
|
#### Poro
|
184
206
|
|
185
207
|
Plain old ruby objects are supported by implementing a class method called
|
186
|
-
`fields` on the object that returns the expected [types](#types) hash.
|
208
|
+
`fields` on the object that returns the expected [types](#types) hash.
|
187
209
|
Methods on the Poro should be defined with the same name as the provided
|
188
210
|
fields.
|
189
211
|
|
@@ -206,7 +228,7 @@ below will create a `updateBlogCommand` mutation as well as a `deleteBlogCommand
|
|
206
228
|
class BlogCommand < GraphQL::Api::CommandType
|
207
229
|
inputs name: :string, tags: [:string], id: :integer
|
208
230
|
returns blog: Blog
|
209
|
-
|
231
|
+
|
210
232
|
# this tells GraphQL-Api to make two mutations that call the below methods.
|
211
233
|
actions :update, :delete
|
212
234
|
|
@@ -262,8 +284,8 @@ docs for how to do this.
|
|
262
284
|
|
263
285
|
### Types
|
264
286
|
|
265
|
-
Field types and argument types are all supplied as a hash of key value
|
266
|
-
pairs. An exclamation mark at the end of the type marks it as required,
|
287
|
+
Field types and argument types are all supplied as a hash of key value
|
288
|
+
pairs. An exclamation mark at the end of the type marks it as required,
|
267
289
|
and wrapping the type in an array marks it as a list of that type.
|
268
290
|
|
269
291
|
```ruby
|
@@ -289,7 +311,6 @@ Note, these are the same as active record's column types for consistency.
|
|
289
311
|
## Roadmap
|
290
312
|
|
291
313
|
- [ ] Customizing resolvers
|
292
|
-
- [ ] CanCan support
|
293
314
|
- [ ] Relay support
|
294
315
|
- [ ] Additional object support (enums, interfaces ...)
|
295
316
|
- [ ] Support non rails frameworks
|
data/lib/graphql/api/policy.rb
CHANGED
@@ -10,15 +10,14 @@ module GraphQL::Api
|
|
10
10
|
|
11
11
|
def call(obj, args, ctx)
|
12
12
|
if @policy_class
|
13
|
-
policy = @policy_class.new(ctx
|
14
|
-
return
|
13
|
+
policy = @policy_class.new(ctx, obj)
|
14
|
+
return policy.unauthorized! unless policy.read?
|
15
15
|
|
16
16
|
if policy.respond_to?("access_#{@name}?")
|
17
|
-
|
18
|
-
else
|
19
|
-
obj.send(@name)
|
17
|
+
return policy.unauthorized_field_access(@name) unless policy.send("access_#{@name}?")
|
20
18
|
end
|
21
|
-
|
19
|
+
|
20
|
+
obj.send(@name)
|
22
21
|
elsif obj.respond_to?("access_#{@name}?")
|
23
22
|
obj.send(@name) if obj.send("access_#{@name}?", ctx)
|
24
23
|
else
|
@@ -9,10 +9,8 @@ module GraphQL::Api
|
|
9
9
|
|
10
10
|
def call(inputs, ctx)
|
11
11
|
if @policy_class
|
12
|
-
policy = @policy_class.new(ctx
|
13
|
-
unless policy.create?
|
14
|
-
return {key => nil}
|
15
|
-
end
|
12
|
+
policy = @policy_class.new(ctx, nil)
|
13
|
+
return policy.unauthorized! unless policy.create?
|
16
14
|
end
|
17
15
|
|
18
16
|
item = @model.create!(inputs.to_h)
|
@@ -11,10 +11,8 @@ module GraphQL::Api
|
|
11
11
|
item = @model.find(inputs[:id])
|
12
12
|
|
13
13
|
if @policy_class
|
14
|
-
policy = @policy_class.new(ctx
|
15
|
-
unless policy.destroy?
|
16
|
-
return {key => nil}
|
17
|
-
end
|
14
|
+
policy = @policy_class.new(ctx, item)
|
15
|
+
return policy.unauthorized! unless policy.destroy?
|
18
16
|
end
|
19
17
|
|
20
18
|
item.destroy!
|
@@ -15,10 +15,8 @@ module GraphQL::Api
|
|
15
15
|
end
|
16
16
|
|
17
17
|
if @policy_class
|
18
|
-
policy = @policy_class.new(ctx
|
19
|
-
unless policy.read?
|
20
|
-
return nil
|
21
|
-
end
|
18
|
+
policy = @policy_class.new(ctx, item)
|
19
|
+
return policy.unauthorized! unless policy.read?
|
22
20
|
end
|
23
21
|
|
24
22
|
item
|
@@ -4,6 +4,7 @@ module GraphQL::Api
|
|
4
4
|
|
5
5
|
def initialize(model)
|
6
6
|
@model = model
|
7
|
+
@policy_class = "#{model.name}Policy".safe_constantize
|
7
8
|
end
|
8
9
|
|
9
10
|
def call(obj, args, ctx)
|
@@ -20,7 +21,16 @@ module GraphQL::Api
|
|
20
21
|
|
21
22
|
q = @model.where(query_args)
|
22
23
|
q.eager_load(*eager_load) if eager_load.any?
|
23
|
-
q.limit(args[:limit] || 30)
|
24
|
+
results = q.limit(args[:limit] || 30)
|
25
|
+
|
26
|
+
if @policy_class
|
27
|
+
results.each do |res|
|
28
|
+
policy = @policy_class.new(ctx, res)
|
29
|
+
return policy.unauthorized! unless policy.read?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
results
|
24
34
|
end
|
25
35
|
end
|
26
36
|
|
@@ -11,10 +11,8 @@ module GraphQL::Api
|
|
11
11
|
item = @model.find(inputs[:id])
|
12
12
|
|
13
13
|
if @policy_class
|
14
|
-
policy = @policy_class.new(ctx
|
15
|
-
unless policy.destroy?
|
16
|
-
return {key => nil}
|
17
|
-
end
|
14
|
+
policy = @policy_class.new(ctx, item)
|
15
|
+
return policy.unauthorized! unless policy.destroy?
|
18
16
|
end
|
19
17
|
|
20
18
|
item.update!(inputs.to_h)
|
data/lib/graphql/api/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql-api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Colin Walker
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-01-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|