graphqr 0.0.4 → 0.0.5

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