pragma-operation 1.6.3 → 2.0.0

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.
@@ -1,154 +0,0 @@
1
- # Contracts
2
-
3
- Operations integrate with [Pragma::Contract](https://github.com/pragmarb/pragma-contract). You can
4
- specify the contract to use with `#contract` and get access to `#validate` and `#validate!` in your
5
- operations:
6
-
7
- ```ruby
8
- module API
9
- module V1
10
- module Post
11
- module Operation
12
- class Create < Pragma::Operation::Base
13
- contract API::V1::Post::Contract::Create
14
-
15
- def call
16
- post = Post.new
17
-
18
- validate! contract
19
- contract.save
20
-
21
- respond_with status: :created, resource: post
22
- end
23
- end
24
- end
25
- end
26
- end
27
- end
28
- ```
29
-
30
- You can also pass a block to compute the contract class dynamically. If the block returns `nil`,
31
- validation will be skipped:
32
-
33
- ```ruby
34
- module API
35
- module V1
36
- module Post
37
- module Operation
38
- class Create < Pragma::Operation::Base
39
- contract do |context|
40
- # ...
41
- end
42
-
43
- def call
44
- # ...
45
- end
46
- end
47
- end
48
- end
49
- end
50
- end
51
- ```
52
-
53
- If the contract passes validation, then all is good. If not, an error is raised:
54
-
55
- ```ruby
56
- result1 = API::V1::Post::Operation::Create.call(params: {
57
- title: 'My First Post',
58
- body: 'Hello everyone, this is my first post!'
59
- })
60
-
61
- result1.status # => :created
62
- result1.resource
63
- # => {
64
- # 'title' => 'My First Post'
65
- # 'body' => 'Hello everyone, this is my first post!'
66
- # }
67
-
68
- result2 = API::V1::Post::Operation::Create.call(params: {
69
- title: 'My First Post'
70
- })
71
-
72
- result2.status # => :forbidden
73
- result2.resource
74
- # => {
75
- # 'error_type' => 'unprocessable_entity',
76
- # 'error_message' => 'The contract for this operation was not respected.',
77
- # 'meta' => {
78
- # 'errors' => {
79
- # 'body' => ["can't be blank"]
80
- # }
81
- # }
82
- # }
83
- ```
84
-
85
- You can also use the non-bang method `#validate` to implement your own logic:
86
-
87
- ```ruby
88
- module API
89
- module V1
90
- module Post
91
- module Operation
92
- class Create < Pragma::Operation::Base
93
- contract API::V1::Post::Contract::Create
94
-
95
- def call
96
- post = Post.new
97
- contract = build_contract(post)
98
-
99
- unless validate(contract)
100
- respond_with!(
101
- status: :unprocessable_entity,
102
- resource: nil
103
- )
104
- end
105
-
106
- contract.save
107
-
108
- respond_with status: :created, resource: post
109
- end
110
- end
111
- end
112
- end
113
- end
114
- end
115
- ```
116
-
117
- If you want to run some logic after validation, you can override the `#after_validation` method
118
- in your operation. It takes the result of the validation as its first argument:
119
-
120
- ```ruby
121
- module API
122
- module V1
123
- module Post
124
- module Operation
125
- class Create < Pragma::Operation::Base
126
- contract API::V1::Post::Contract::Create
127
-
128
- def call
129
- post = Post.new
130
- contract = build_contract(post)
131
-
132
- unless validate(contract)
133
- respond_with!(
134
- status: :unprocessable_entity,
135
- resource: nil
136
- )
137
- end
138
-
139
- contract.save
140
-
141
- respond_with status: :created, resource: post
142
- end
143
-
144
- protected
145
-
146
- def after_validation(result)
147
- # ...
148
- end
149
- end
150
- end
151
- end
152
- end
153
- end
154
- ```
@@ -1,179 +0,0 @@
1
- # Policies
2
-
3
- Operations integrate with [Pragma::Policy](https://github.com/pragmarb/pragma-policy). All you have
4
- to do is specify the policy class with `#policy`:
5
-
6
- ```ruby
7
- module API
8
- module V1
9
- module Post
10
- module Operation
11
- class Create < Pragma::Operation::Base
12
- policy API::V1::Post::Policy
13
-
14
- def call
15
- post = Post.new(params)
16
- authorize! post
17
-
18
- post.save!
19
-
20
- respond_with status: :created, resource: post
21
- end
22
- end
23
- end
24
- end
25
- end
26
- end
27
- ```
28
-
29
- You can also pass a block to compute the policy class dynamically. If the block returns `nil`,
30
- authorization will be skipped:
31
-
32
- ```ruby
33
- module API
34
- module V1
35
- module Post
36
- module Operation
37
- class Create < Pragma::Operation::Base
38
- policy do |context|
39
- # ...
40
- end
41
-
42
- def call
43
- # ...
44
- end
45
- end
46
- end
47
- end
48
- end
49
- end
50
- ```
51
-
52
- Of course, you will now have to pass the user performing the operation in addition to the usual
53
- parameters:
54
-
55
- ```ruby
56
- result1 = API::V1::Post::Operation::Create.call(
57
- params: {
58
- title: 'My First Post',
59
- body: 'Hello everyone, this is my first post!'
60
- },
61
- current_user: authorized_user
62
- )
63
-
64
- result1.status # => :created
65
- result1.resource
66
- # => {
67
- # 'title' => 'My First Post'
68
- # 'body' => 'Hello everyone, this is my first post!'
69
- # }
70
-
71
- result2 = API::V1::Post::Operation::Create.call(
72
- params: {
73
- title: 'My First Post',
74
- body: 'Hello everyone, this is my first post!'
75
- },
76
- current_user: unauthorized_user
77
- )
78
-
79
- result2.status # => :forbidden
80
- result2.resource
81
- # => {
82
- # 'error_type' => 'forbidden',
83
- # 'error_message' => 'You are not authorized to perform this operation.'
84
- # }
85
- ```
86
-
87
- If you want to customize how you handle authorization, you can use the non-bang method `#authorize`:
88
-
89
- ```ruby
90
- module API
91
- module V1
92
- module Post
93
- module Operation
94
- class Create < Pragma::Operation::Base
95
- policy API::V1::Post::Policy
96
-
97
- def call
98
- post = Post.new(params)
99
-
100
- unless authorize(post)
101
- respond_with!(
102
- status: :forbidden,
103
- resource: nil # if you don't need error info
104
- )
105
- end
106
-
107
- post.save!
108
-
109
- respond_with status: :created, resource: post
110
- end
111
- end
112
- end
113
- end
114
- end
115
- end
116
- ```
117
-
118
- If you want to run some logic after authorization, you can override the `#after_authorization` method
119
- in your operation. It takes the result of the authorization as its first argument:
120
-
121
- ```ruby
122
- module API
123
- module V1
124
- module Post
125
- module Operation
126
- class Create < Pragma::Operation::Base
127
- policy API::V1::Post::Policy
128
-
129
- def call
130
- post = Post.new(params)
131
-
132
- unless authorize(post)
133
- respond_with!(
134
- status: :forbidden,
135
- resource: nil # if you don't need error info
136
- )
137
- end
138
-
139
- post.save!
140
-
141
- respond_with status: :created, resource: post
142
- end
143
-
144
- protected
145
-
146
- def after_authorization(result)
147
- # ...
148
- end
149
- end
150
- end
151
- end
152
- end
153
- end
154
- ```
155
-
156
- ## Authorizing collections
157
-
158
- To authorize a collection, use `#authorize_collection`. This will call `.accessible_by` on the
159
- policy class with the current user and the provided collection and return an authorized collection
160
- containing only records accessible by the user.
161
-
162
- ```ruby
163
- module API
164
- module V1
165
- module Post
166
- module Operation
167
- class Index < Pragma::Operation::Base
168
- policy API::V1::Post::Policy
169
-
170
- def call
171
- posts = authorize_collection Post.all
172
- respond_with status: :ok, resource: posts
173
- end
174
- end
175
- end
176
- end
177
- end
178
- end
179
- ```
@@ -1,50 +0,0 @@
1
- # Decorators
2
-
3
- Operations integrate with [Pragma::Decorator](https://github.com/pragmarb/pragma-decorator). All you
4
- have to do is specify the decorator class with `#decorator`. This will give you access to
5
- `#decorate`:
6
-
7
- ```ruby
8
- module API
9
- module V1
10
- module Post
11
- module Operation
12
- class Show < Pragma::Operation::Base
13
- decorator API::V1::Post::Decorator
14
-
15
- def call
16
- post = Post.find(params[:id])
17
- respond_with status: :ok, resource: decorate(post)
18
- end
19
- end
20
- end
21
- end
22
- end
23
- end
24
- ```
25
-
26
- You can also pass a block to compute the decorator class dynamically. If the block returns `nil`,
27
- decoration will be skipped:
28
-
29
- ```ruby
30
- module API
31
- module V1
32
- module Post
33
- module Operation
34
- class Show < Pragma::Operation::Base
35
- decorator do |context|
36
- # ...
37
- end
38
-
39
- def call
40
- # ...
41
- end
42
- end
43
- end
44
- end
45
- end
46
- end
47
- ```
48
-
49
- Note that `#decorate` works with both singular resources and collections, as it uses the decorator's
50
- [`.represent`](http://trailblazer.to/gems/representable/3.0/api.html) method.
@@ -1,130 +0,0 @@
1
- # frozen_string_literal: true
2
- module Pragma
3
- module Operation
4
- # Provides integration with {https://github.com/pragmarb/pragma-policy Pragma::Policy}.
5
- #
6
- # @author Alessandro Desantis
7
- module Authorization
8
- def self.included(base)
9
- base.extend ClassMethods
10
- base.include InstanceMethods
11
- end
12
-
13
- module ClassMethods # :nodoc:
14
- # Sets the policy to use for authorizing this operation.
15
- #
16
- # @param klass [Class] a subclass of +Pragma::Policy::Base+
17
- #
18
- # @yield A block which will be called with the operation's context which should return
19
- # the policy class. The block can also return +nil+ if authorization should be skipped.
20
- def policy(klass = nil, &block)
21
- if !klass && !block_given?
22
- fail ArgumentError, 'You must pass either a policy class or a block'
23
- end
24
-
25
- @policy = klass || block
26
- end
27
-
28
- # Returns the policy class.
29
- #
30
- # @return [Class]
31
- def policy_klass
32
- @policy
33
- end
34
- end
35
-
36
- module InstanceMethods # :nodoc:
37
- # Builds the policy for the current user and the given resource, using the previously
38
- # defined policy class.
39
- #
40
- # @param resource [Object]
41
- #
42
- # @return [Pragma::Policy::Base]
43
- #
44
- # @see .policy
45
- # @see .build_policy
46
- def build_policy(resource)
47
- policy_klass = compute_policy_klass
48
- return resource unless policy_klass
49
-
50
- policy_klass.new(user: current_user, resource: resource)
51
- end
52
-
53
- # Authorizes this operation on the provided resource or policy.
54
- #
55
- # If no policy was defined, simply returns true.
56
- #
57
- # @param authorizable [Pragma::Policy::Base|Object] resource or policy
58
- #
59
- # @return [Boolean] whether the operation is authorized
60
- def authorize(authorizable)
61
- return true unless compute_policy_klass
62
-
63
- # rubocop:disable Metrics/LineLength
64
- policy = if Object.const_defined?('Pragma::Policy::Base') && authorizable.is_a?(Pragma::Policy::Base)
65
- authorizable
66
- else
67
- build_policy(authorizable)
68
- end
69
- # rubocop:enable Metrics/LineLength
70
-
71
- if Object.const_defined?('Pragma::Contract::Base') && authorizable.is_a?(Pragma::Contract::Base)
72
- authorizable.deserialize(params)
73
- end
74
-
75
- policy.send("#{self.class.operation_name}?").tap do |result|
76
- after_authorization result
77
- end
78
- end
79
-
80
- # Authorizes this operation on the provided resource or policy. If the user is not
81
- # authorized to perform the operation, responds with 403 Forbidden and an error body and
82
- # halts the execution.
83
- #
84
- # @param authorizable [Pragma::Policy::Base|Object] resource or policy
85
- def authorize!(authorizable)
86
- return if authorize(authorizable)
87
-
88
- respond_with!(
89
- status: :forbidden,
90
- resource: {
91
- error_type: :forbidden,
92
- error_message: 'You are not authorized to perform this operation.'
93
- }
94
- )
95
- end
96
-
97
- # Runs after authorization is done.
98
- #
99
- # @param result [Boolean] the result of the authorization
100
- def after_authorization(result)
101
- end
102
-
103
- # Scopes the provided collection.
104
- #
105
- # If no policy class is defined, simply returns the collection.
106
- #
107
- # @param collection [Enumerable]
108
- #
109
- # @return [Pragma::Decorator::Base|Enumerable]
110
- def authorize_collection(collection)
111
- policy_klass = compute_policy_klass
112
- return collection unless policy_klass
113
-
114
- policy_klass.accessible_by(
115
- user: current_user,
116
- scope: collection
117
- )
118
- end
119
-
120
- def compute_policy_klass
121
- if self.class.policy_klass.is_a?(Proc)
122
- self.class.policy_klass.call(context)
123
- else
124
- self.class.policy_klass
125
- end
126
- end
127
- end
128
- end
129
- end
130
- end