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