graphql-api 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 95f514ccff92debe339818cb597bb3be03a46537
4
- data.tar.gz: 7032b93c8e4c5abefdf09e434ad01ef9f6c2fb01
3
+ metadata.gz: d066b645b42c6471423c84254b296abe806d8bda
4
+ data.tar.gz: dcbc217929b63f258c12c63f392b373acbe6d9dd
5
5
  SHA512:
6
- metadata.gz: 207a89f484b7dab769130910326067f0fa08bc0b83b843c28cb577ae952c631b3ce024c5c2b5e160b43107bc82870b2c53567432fa6b4897add62a77e6addb93
7
- data.tar.gz: bf7290f3e1397193d4d14edc4d456508f4270dc8195bb74701428475a9b27f6f576490947de824470d786f437a95720de66d607ca0e178ae357d0c496b3596ad
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 instantiated for accessing models. These objects
155
- should meet the following interface methods.
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
- ##### CRUD Policies
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
- The policy object should return true or false methods for all of the crud
160
- options allowing the mutation to take place, ie
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
@@ -29,8 +29,12 @@ module GraphQL::Api
29
29
  true
30
30
  end
31
31
 
32
- def unauthorized!(msg=nil)
33
- raise UnauthorizedException.new(msg)
32
+ def unauthorized!
33
+ raise UnauthorizedException.new
34
+ end
35
+
36
+ def unauthorized_field_access(name)
37
+ nil
34
38
  end
35
39
 
36
40
  end
@@ -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[:current_user], obj, ctx)
14
- return nil unless policy.read?
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
- obj.send(@name) if policy.send("access_#{@name}?")
18
- else
19
- obj.send(@name)
17
+ return policy.unauthorized_field_access(@name) unless policy.send("access_#{@name}?")
20
18
  end
21
- obj.send(@name) if policy.respond_to?("access_#{@name}?")
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[:current_user], nil, 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[:current_user], item, 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[:current_user], item, 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[:current_user], item, 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)
@@ -1,5 +1,5 @@
1
1
  module GraphQL
2
2
  module Api
3
- VERSION = '0.1.5'
3
+ VERSION = '0.1.6'
4
4
  end
5
5
  end
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.5
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: 2016-11-17 00:00:00.000000000 Z
11
+ date: 2017-01-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails