pragma-operation 1.6.3 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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