graphqr 0.0.4 → 0.0.5

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: f872050870c704c750620f560725fedbb386e1e2
4
- data.tar.gz: a951daec569df80f61bcc745c539d5a3ebcea16f
3
+ metadata.gz: 876e07cf3e0eb24c8d8c71f23b23e18ae3d014bc
4
+ data.tar.gz: bcd24faba39f7be1b3a07ca3629bf6f680f10c25
5
5
  SHA512:
6
- metadata.gz: f449a600a8c747a0ccbc0e54380862c0981f4be15b41298a0bb5b2a6b162b3ae74846e7a14f863ebc22e743ff61aadd26eb64d3f7bebc5084f65620c24ea4b70
7
- data.tar.gz: 28446614b1c6cde4eb44efca6656831dff436c4a4ffab9d29ae3031717499ec8a6ba357dfa4d86da22102a549d46e49a4df4207dd768a1839c0f1b228758b8e9
6
+ metadata.gz: 51f5c95b0ba53fe528e7e275bf6e00063c4d2cbd6add26bdc16cf41b0f2f2e6f4884e776108e8e4f3a522121ad0419863e50d111f0dadd0ce577d3611c524cf8
7
+ data.tar.gz: cf5ff9b77895930d9d7bc9d8aa6adb3bdd494c34c6310139055e4ebb57e3403903810a197cd6f0098855dcfad2b0c275ec715bf21ef212569c7b7beeb9290bfa
data/README.md CHANGED
@@ -1,11 +1,14 @@
1
+ [![Gem Version](https://badge.fury.io/rb/graphqr.svg)](https://rubygems.org/gems/graphqr)
1
2
  [![Build Status](https://travis-ci.com/QultureRocks/graphqr.svg?branch=master)](https://travis-ci.com/QultureRocks/graphqr)
2
3
  [![Maintainability](https://api.codeclimate.com/v1/badges/7f7b51e89e8fe4de1b23/maintainability)](https://codeclimate.com/github/QultureRocks/graphqr/maintainability)
3
4
 
4
5
  # GraphQR
5
6
 
6
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/graphqr`. To experiment with that code, run `bin/console` for an interactive prompt.
7
+ A compilation of useful extensions and helpers for [graphql-ruby](https://github.com/rmosolgo/graphql-ruby).
8
+
9
+ - [API Documentation](https://qulturerocks.github.io/graphqr/)
10
+ - [Qulture.Rocks](https://qulture.rocks)
7
11
 
8
- TODO: Delete this and the text above, and describe your gem
9
12
 
10
13
  ## Installation
11
14
 
@@ -23,12 +26,171 @@ Or install it yourself as:
23
26
 
24
27
  $ gem install graphqr
25
28
 
26
- If you'd like to use the pagination feature, you must have `pagy` installed.
29
+ ## Configuration (optional)
30
+
31
+ GraphQR uses `pagy` and `pundit` by default and activates both `Authorization` and `Pagination` modules.
32
+ If you'd like to create a specific configuration, create `config/initializers/graphql.rb` with
33
+
34
+ ```
35
+ GraphQR.configure do |config|
36
+ config.use_pagination = true # or false to disable
37
+ config.use_authorization = true # or false to disable
38
+
39
+ config.paginator = :pagy # only pagy is available for now
40
+ config.policy_provider = :pundit # only pundit is available for now
41
+ end
42
+ ```
43
+
44
+ ## Modules
45
+
46
+ To use the extensions correctly add
47
+ ```
48
+ field_class GraphQR::Fields::BaseField
49
+ ```
50
+
51
+ to your `BaseObject` class. This will add the `paginate` options to your fields and add the necessary extensions according to the modules you activated.
52
+
53
+ ### Pagination
54
+
55
+ The Pagination module consists in a easier way of dealing with pages. Instead of using `cursors` we implemented a more Rails way using `per` and `page`.
56
+ Our implementation is (for now) based on [Pagy](https://github.com/ddnexus/pagy) so you must have it installed.
57
+
58
+ To use the Pagination module add
59
+
60
+ ```
61
+ extend GraphQR::Pagination
62
+ ```
63
+
64
+ to any `GraphQL::Schema::Object` you'd like, but we recommend adding it to your `BaseObject` class.
65
+
66
+ #### Usage
67
+
68
+ ```
69
+ field :users, UserType.pagination_type, paginate: true
70
+ ```
71
+
72
+ A `pagination_type` adds the `per` and `page` arguments and adds a `page_info` field to the response.
73
+
74
+ Example gql:
75
+ ```
76
+ users(per: 10, page: 1) {
77
+ nodes {
78
+ id
79
+ name
80
+ }
81
+ }
82
+ ```
83
+
84
+ ### Authorization
85
+
86
+ The Authorization module in some wrappers around a `PolicyProvider` (only Pundit for now). And allows some basic behaviors.
87
+ Everything on this module depends on a `policy_provider` passed to the GraphQL context. You can add it like this:
88
+
89
+ ```
90
+ context = {
91
+ policy_provider: GraphQR::Policies::PunditProvider.new(policy_context: pundit_user)
92
+ ...
93
+ }
94
+
95
+ Schema.execute(query, variables: variables, context: context, operation_name: operation_name)
96
+ ```
97
+
98
+ #### Authorized
99
+
100
+ This module adds a check on the object policy before resolving it. It always searches for the `show?` policy of the record.
101
+ It works by extending the `authorized?` method.
102
+
103
+ To add this behavior, add
104
+ ```
105
+ extend GraphQR::Authorized
106
+ ```
107
+ to any `GraphQL::Schema::Object` you'd like, but we recommend adding it to your `BaseObject` class.
108
+
109
+ Example:
110
+
111
+ ```
112
+ users {
113
+ id
114
+ tags {
115
+ id
116
+ }
117
+ }
118
+ ```
119
+
120
+ In this case, the authorization check will run for each `user`, calling `UserPolicy.show?` and for each tag, calling `TagPolicy.show?`.
121
+ If any policy returns falsy, the object is returned as `null`.
122
+
123
+ #### ScopeItems
124
+
125
+ This module adds the PolicyProvider scope to the fields that represent an `ActiveRecord::Relation`. It works by implementing the `self.scope_items` method.
126
+
127
+ To add this behavior, add
128
+ ```
129
+ extend GraphQR::ScopeItems
130
+ ```
131
+ to any `GraphQL::Schema::Object` you'd like, but we recommend adding it to your `BaseObject` class.
132
+
133
+ Example:
134
+
135
+ ```
136
+ users {
137
+ id
138
+ }
139
+ ```
140
+
141
+ In this case, the `users` list will be scoped using `UserPolicy::Scope` provided by Pundit.
142
+
143
+ #### AuthorizeGraphQL
144
+
145
+ This module is a wrapper around the PolicyProvider authorization.
146
+ It adds the `authorize_graphql` method, similar to Pundit's `authorize`, but it returns an `GraphQL::ExecutionError` instead of a `Pundit::NotAuthorizedError`
147
+
148
+ To add this behavior, add
149
+ ```
150
+ include GraphQR::AuthorizeGraphQL
151
+ ```
152
+ where you want to use this methos, but we recommend adding it to your `Mutations` and `Resolvers` classes.
153
+
154
+ Example:
155
+
156
+ ```
157
+ authorize_graphql User, :index?
158
+ ```
159
+
160
+ ### Helpers
161
+
162
+ We also provide some helpers to make implementing GraphQL on ruby easier.
163
+
164
+ #### ApplyScopes
165
+
166
+ This modules is based on the [has_scope](https://github.com/plataformatec/has_scope/) gem.
167
+ It provides an `apply_scopes` method that can search for model scopes and use them on a collection
168
+
169
+ To add this method, add
170
+ ```
171
+ include GraphQR::ApplyScopes
172
+ ```
173
+ where you'd like to use it, but we recommend adding it to your `Resolvers`.
174
+
175
+ Example:
176
+
177
+ ```
178
+ apply_scopes(User, { order_by_name: true, with_id: [1,2,3] })
179
+ ```
180
+
181
+ #### QueryField
182
+
183
+ This module adds the `query_field` helper.
184
+ It adds an easy way of creating simple fields with resolvers.
185
+
186
+ To add this method, add
187
+ ```
188
+ extend GraphQR::QueryField
189
+ ```
190
+ to your `BaseObject`.
27
191
 
28
- ## Usage
192
+ Read more about its use in the [documentation](https://qulturerocks.github.io/graphqr/GraphQR/QueryField.html)
29
193
 
30
- [Documentation](https://qulturerocks.github.io/graphqr/)
31
- TODO: Write usage instructions here
32
194
 
33
195
  ## Development
34
196
 
data/lib/graphqr.rb CHANGED
@@ -8,6 +8,14 @@ require 'graphqr/configuration'
8
8
  # it contains helpers and integrations we need to keep our workflow as simple as possible.
9
9
  module GraphQR
10
10
  class << self
11
+ def use_pagination
12
+ GraphQR.config.use_pagination || true
13
+ end
14
+
15
+ def use_authorization
16
+ GraphQR.config.use_authorization || true
17
+ end
18
+
11
19
  def paginator
12
20
  GraphQR.config.paginator
13
21
  end
@@ -38,6 +46,7 @@ rescue NameError
38
46
  Kernel.warn 'Pagy not found'
39
47
  end
40
48
 
49
+ require 'graphqr/policies/authorize_graphql'
41
50
  begin
42
51
  require 'graphqr/policies/pundit_provider'
43
52
  rescue LoadError
@@ -46,7 +55,6 @@ end
46
55
 
47
56
  require 'graphqr/apply_scopes'
48
57
  require 'graphqr/authorized'
49
- require 'graphqr/base'
50
58
  require 'graphqr/pagination'
51
59
  require 'graphqr/permitted_fields_extension'
52
60
  require 'graphqr/query_field'
@@ -2,8 +2,15 @@
2
2
 
3
3
  module GraphQR
4
4
  ##
5
- # TODO: add documentation
5
+ # This module is the authorization extension created with our PolicyProvider.
6
+ #
7
+ # To use it add `extend GraphQR::Authorized` on the `GraphQL::Schema::Object` you want it,
8
+ # or add it on your `BaseObject`
6
9
  module Authorized
10
+ ##
11
+ # The `authorized? `method always runs before resolving an object.
12
+ #
13
+ # Our implementation adds a check on the `show?` method from the record Policy.
7
14
  def authorized?(object, context)
8
15
  policy_provider = context[:policy_provider]
9
16
 
@@ -2,12 +2,30 @@
2
2
 
3
3
  module GraphQR # rubocop:disable Style/Documentation
4
4
  ##
5
- # TODO: add documentation
5
+ # Module responsible for global configuration of the gem
6
6
  class Configuration
7
+ attr_writer :use_pagination, :use_authorization
8
+
7
9
  def configure
8
10
  yield self
9
11
  end
10
12
 
13
+ def use_pagination
14
+ if instance_variable_defined? :@use_pagination
15
+ @use_pagination
16
+ else
17
+ @use_pagination = true
18
+ end
19
+ end
20
+
21
+ def use_authorization
22
+ if instance_variable_defined? :@use_authorization
23
+ @use_authorization
24
+ else
25
+ @use_authorization = true
26
+ end
27
+ end
28
+
11
29
  ##
12
30
  # Returns the selected paginator.
13
31
  # If no paginator is selected, it tries to find the one used
@@ -18,8 +18,8 @@ module GraphQR
18
18
  class BaseField < GraphQL::Schema::Field
19
19
  def initialize(*args, paginate: false, **kwargs, &block)
20
20
  super(*args, **kwargs, &block)
21
- extension(Pagination::PaginationExtension) if paginate
22
- extension(PermittedFieldsExtension, null: kwargs[:null])
21
+ extension(Pagination::PaginationExtension) if paginate && GraphQR.use_pagination
22
+ extension(PermittedFieldsExtension, null: kwargs[:null]) if GraphQR.use_authorization
23
23
  end
24
24
  end
25
25
  end
@@ -2,7 +2,12 @@
2
2
 
3
3
  module GraphQR
4
4
  ##
5
- # TODO: add documentation
5
+ # This module adds the GraphQL pagination types.
6
+ #
7
+ # When a field is paginated, the field `page_info` is always included with some pagination information.
8
+ #
9
+ # To use this module use `extend GraphQR::Pagination` on the GraphQL::Schema::Object you want it,
10
+ # or in your `BaseObject`
6
11
  module Pagination
7
12
  def pagination_type
8
13
  @pagination_type ||= begin
@@ -3,9 +3,13 @@
3
3
  module GraphQR
4
4
  module Pagination
5
5
  ##
6
- # TODO: add documentation
6
+ # The PaginationExtension is used on the `GraphQR::Fields::BaseField`.
7
+ #
8
+ # It adds the `per` and `page` arguments to the paginated field and uses the selected paginator resolver to add
9
+ # `nodes`, `edges` and `page_info` on the response
7
10
  class PaginationExtension < GraphQL::Schema::FieldExtension
8
- DEFAULT_PAGINATION_ERROR = 'No paginator defined'
11
+ NO_PAGINATOR_ERROR = 'No paginator defined'
12
+ INVALID_PAGINATOR_ERROR = 'Invalid paginator'
9
13
 
10
14
  def apply
11
15
  field.argument :per, 'Int', required: false, default_value: 25,
@@ -23,9 +27,18 @@ module GraphQR
23
27
  end
24
28
 
25
29
  def after_resolve(value:, arguments:, **_kwargs)
26
- raise GraphQL::ExecutionError, DEFAULT_PAGINATION_ERROR unless GraphQR.paginator.present?
30
+ raise GraphQL::ExecutionError, NO_PAGINATOR_ERROR unless GraphQR.paginator.present?
27
31
 
28
- Resolvers::PagyResolver.new(value, items: arguments[:per], page: arguments[:page]) if GraphQR.use_pagy?
32
+ call_resolver(value, arguments)
33
+ end
34
+
35
+ def call_resolver(value, arguments)
36
+ case GraphQR.paginator
37
+ when :pagy
38
+ Resolvers::PagyResolver.new(value, items: arguments[:per], page: arguments[:page])
39
+ else
40
+ raise GraphQL::ExecutionError, INVALID_PAGINATOR_ERROR
41
+ end
29
42
  end
30
43
  end
31
44
  end
@@ -4,7 +4,7 @@ module GraphQR
4
4
  module Pagination
5
5
  module Resolvers
6
6
  ##
7
- # TODO: add documentation
7
+ # This is a resolver that uses `Pagy::Backend` and maps it to the GraphQL pagination structure.
8
8
  class PagyResolver
9
9
  include Pagy::Backend
10
10
 
@@ -4,7 +4,7 @@ module GraphQR
4
4
  module Pagination
5
5
  module Types
6
6
  ##
7
- # TODO: add documentation
7
+ # This defines the information about pagination in a connection.
8
8
  class PaginationPageInfoType < GraphQL::Schema::Object
9
9
  description 'Information about pagination in a connection.'
10
10
 
@@ -2,7 +2,10 @@
2
2
 
3
3
  module GraphQR
4
4
  ##
5
- # TODO: add documentation
5
+ # This is an extension used on the `GraphQR::Fields::BaseField`.
6
+ #
7
+ # It is responsible for authorizing each field within a query.
8
+ # It searches if the field is defined on the `permitted_fields` method of the policy
6
9
  class PermittedFieldsExtension < GraphQL::Schema::FieldExtension
7
10
  def resolve(object:, arguments:, context:)
8
11
  if authorized?(object, context)
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQR
4
+ module Policies
5
+ ##
6
+ # The AuthorizeGraphQL module defines a way of running the PolicyProvider authorization with a specific action
7
+ module AuthorizeGraphQL
8
+ DEFAULT_AUTHORIZATION_ERROR = 'You are not authorized to perform this action'
9
+
10
+ ##
11
+ # This method is a wrapper around the Pundit authorize, receiving the same arguments.
12
+ # The only difference is that it turns the Pundit::NotAuthorizedError into a GraphQL::ExecutionError
13
+ #
14
+ # ### Example:
15
+ #
16
+ # ```
17
+ # authorize_graphql User, :index?
18
+ # ```
19
+ def authorize_graphql(record, action, policy_class: nil)
20
+ args = { record: record, action: action, policy_class: policy_class }
21
+ raise GraphQL::ExecutionError, DEFAULT_AUTHORIZATION_ERROR unless policy_provider.allowed?(args)
22
+ end
23
+
24
+ def policy_provider
25
+ context[:policy_provider]
26
+ end
27
+ end
28
+ end
29
+ end
@@ -5,7 +5,17 @@ require 'pundit'
5
5
  module GraphQR
6
6
  module Policies
7
7
  ##
8
- # TODO: add documentation
8
+ # This is a wrapper around Pundit provided to keep all PolicyProviders with the same methods.
9
+ #
10
+ # If you want to use the Pundit integration with our extensions you should pass:
11
+ #
12
+ # ```
13
+ # {
14
+ # policy_provider: GraphQR::Policies::PunditProvider.new(policy_context: pundit_user)
15
+ # }
16
+ # ```
17
+ #
18
+ # To the Schema context.
9
19
  class PunditProvider
10
20
  attr_reader :policy_context
11
21
 
@@ -1,11 +1,46 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # rubocop:disable Metrics/ParameterLists
4
-
5
3
  module GraphQR
6
4
  ##
7
- # TODO: add documentation
5
+ # This extension adds the `query_field` method.
6
+ # A helper to create simple queries faster and easier
7
+ #
8
+ # To use this extension, add `extend Graphql::QueryField` on your `QueryType`
8
9
  module QueryField
10
+ # rubocop:disable Metrics/ParameterLists
11
+
12
+ ##
13
+ # The `query_field` method is a helper to create fields and resolver without effort.
14
+ #
15
+ # ### Arguments
16
+ #
17
+ # +field_name+ _(required)_: the GraphQL query name
18
+ #
19
+ # +active_record_class+ _(required)_: the model ActiveRecord class.
20
+ # It can be represented as an array if you want it to return a collection
21
+ #
22
+ # +type_class+ _(required)_: The GraphQL type class
23
+ #
24
+ # +scope_class+: A specific InputType that contains the possible scopes that can be applied to your collection.
25
+ # Similar to the [has_scope](https://github.com/plataformatec/has_scope/) gem.
26
+ # _This argument is required for collection fields._
27
+ #
28
+ # ### Examples
29
+ # ```
30
+ # query_type :user, User, type_class: UserType
31
+ # query_type :users, [User], type_class: UserType, scope_class: UserScopeInput
32
+ # ```
33
+ #
34
+ # ### Collention fields
35
+ #
36
+ # Collection fields are always paginated using the configured `paginator`
37
+ # Its resolver will look for the `index?` method on the model Policy.
38
+ # It'll have the optional `filter` argument with `scope_class` type
39
+ #
40
+ # ### Single fields
41
+ #
42
+ # Single fields have the required `id` argument to find the exact record searched.
43
+ # Its resolver will look for the `show?` method on the model Policy.
9
44
  def query_field(field_name, active_record_class, type_class:, scope_class: nil, **kwargs, &block)
10
45
  is_collection = active_record_class.is_a? Array
11
46
  if is_collection
@@ -17,6 +52,7 @@ module GraphQR
17
52
 
18
53
  field(field_name, paginate: is_collection, resolver: resolver, **kwargs, &block)
19
54
  end
55
+ # rubocop:enable Metrics/ParameterLists
20
56
 
21
57
  private
22
58
 
@@ -58,4 +94,3 @@ module GraphQR
58
94
  end
59
95
  end
60
96
  end
61
- # rubocop:enable Metrics/ParameterLists
@@ -2,8 +2,15 @@
2
2
 
3
3
  module GraphQR
4
4
  ##
5
- # TODO: add documentation
5
+ # This extension adds the PolicyProvider scope to the fields.
6
+ # When using the extension, ActiveRecord::Relation fields will be scoped.
7
+ #
8
+ # To use this extension add `extend GraphQR::ScopeItems` on the `GraphQL::Schema::Object` you want,
9
+ # or in your `BaseObject`
6
10
  module ScopeItems
11
+ ##
12
+ # The method checks whether the items are a ActiveRecord::Relation or not.
13
+ # If they are, it runs the PolicyProvider `authorized_records` scope.
7
14
  def scope_items(items, context)
8
15
  if scopable_items?(items)
9
16
  context[:policy_provider].authorized_records(records: items)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GraphQR
4
- VERSION = '0.0.4'
4
+ VERSION = '0.0.5'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphqr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Manuel Puyol
@@ -144,7 +144,6 @@ files:
144
144
  - lib/graphqr.rb
145
145
  - lib/graphqr/apply_scopes.rb
146
146
  - lib/graphqr/authorized.rb
147
- - lib/graphqr/base.rb
148
147
  - lib/graphqr/configuration.rb
149
148
  - lib/graphqr/fields/base_field.rb
150
149
  - lib/graphqr/hooks.rb
@@ -153,6 +152,7 @@ files:
153
152
  - lib/graphqr/pagination/resolvers/pagy_resolver.rb
154
153
  - lib/graphqr/pagination/types/pagination_page_info_type.rb
155
154
  - lib/graphqr/permitted_fields_extension.rb
155
+ - lib/graphqr/policies/authorize_graphql.rb
156
156
  - lib/graphqr/policies/pundit_provider.rb
157
157
  - lib/graphqr/query_field.rb
158
158
  - lib/graphqr/scope_items.rb
data/lib/graphqr/base.rb DELETED
@@ -1,32 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module GraphQR
4
- ##
5
- # The Base module defines some helper methods that can be used once it is included
6
- # it also includes the basic ApplyScopes library
7
- module Base
8
- include GraphQR::ApplyScopes
9
-
10
- DEFAULT_AUTHORIZATION_ERROR = 'You are not authorized to perform this action'
11
-
12
- ##
13
- # This method is a wrapper around the Pundit authorize, receiving the same arguments.
14
- # The only difference is that it turns the Pundit::NotAuthorizedError into a GraphQL::ExecutionError
15
- #
16
- # ### Example:
17
- #
18
- # ```
19
- # authorize_graphql User, :index?
20
- # ```
21
- def authorize_graphql(record, action, policy_class: nil)
22
- args = { record: record, action: action, policy_class: policy_class }
23
- raise GraphQL::ExecutionError, DEFAULT_AUTHORIZATION_ERROR unless policy_provider.allowed?(args)
24
- end
25
-
26
- ##
27
- # This is a helper method to get the policy provider from the context
28
- def policy_provider
29
- context[:policy_provider]
30
- end
31
- end
32
- end