jsonapi-authorization 1.0.0.alpha6 → 1.0.0.beta1

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: 1740e99c89b7ed5a7872ac09f90e25de714f2b3c
4
- data.tar.gz: 4c7d58578b5814ba853ff871917df33904c61eb8
3
+ metadata.gz: bcd1ac1cfc1372b4ffeeabb8e1ae5bab6cf48da3
4
+ data.tar.gz: 13de8f3337c711b0454b5f1ca953cf99bd77be8b
5
5
  SHA512:
6
- metadata.gz: 5b0202b9e8f5a0fa11f594ff35f62173ae7e4c326dff6011783c4300ac0165b0be779beb1d5e8268000fa597417ebf9bd2c48fc59975eef95ecbb769a53236d1
7
- data.tar.gz: 6846fa8b449885c8addfbfa11e36ab5fd4e6f4f62249bbabe70772a5ecad4d65ad5c5005b739f90812cb1e2ecd0d7e8ac39bf6b1cb4d539199083d24186ab4b6
6
+ metadata.gz: ba7c459632d6809cc751358a1e2943e3ef7b03c646305133eb2e7129ae59b529edfc7ced44d3d6249c34f85f38f74e70ddb6ccfe96403d709cbf22f5ed8d5e55
7
+ data.tar.gz: 76aa072e610ad4949ede3da5b9264bbc53a3e5c4527bf2078cf6cdd428d24dc1fb9229ed548fba7221d5cfd1b16a2b90c2cc38cbe2b21eb3a4f4c7d3e828be58
@@ -115,6 +115,18 @@
115
115
  "code",
116
116
  "test"
117
117
  ]
118
+ },
119
+ {
120
+ "login": "nruth",
121
+ "name": "Nicholas Rutherford",
122
+ "avatar_url": "https://avatars1.githubusercontent.com/u/26158?v=4",
123
+ "profile": "http://www.google.co.uk/profiles/nick.rutherford",
124
+ "contributions": [
125
+ "code",
126
+ "test",
127
+ "infra"
128
+ ]
118
129
  }
119
- ]
130
+ ],
131
+ "repoType": "github"
120
132
  }
data/.gitignore CHANGED
@@ -9,3 +9,4 @@
9
9
  /tmp/
10
10
  /spec/dummy/tmp/
11
11
  *.orig
12
+ .ruby-version
@@ -1,4 +1,5 @@
1
1
  AllCops:
2
+ TargetRubyVersion: 2.1
2
3
  Exclude:
3
4
  - 'bin/*'
4
5
  - 'spec/dummy/db/schema.rb'
@@ -1,12 +1,14 @@
1
1
  language: ruby
2
- cache: bundler
3
- sudo: false
4
2
  env:
5
- - JSONAPI_RESOURCES_VERSION=0.9 RAILS_VERSION=4.1.0
6
3
  - JSONAPI_RESOURCES_VERSION=0.9 RAILS_VERSION=4.2.0
4
+ - JSONAPI_RESOURCES_VERSION=0.9 RAILS_VERSION=5.0.0
5
+ - JSONAPI_RESOURCES_VERSION=0.9 RAILS_VERSION=5.1.0
6
+ - JSONAPI_RESOURCES_VERSION=0.9 RAILS_VERSION=5.2.0
7
+ install:
8
+ - bundle install
7
9
  rvm:
8
- - 2.1.2
9
- before_install: gem install bundler -v 1.11.2
10
+ - 2.3
11
+ before_install: gem install bundler -v 1.16.2
10
12
  notifications:
11
13
  email: false
12
14
  script:
data/Gemfile CHANGED
@@ -2,8 +2,6 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
- gem 'sqlite3', '1.3.10'
6
-
7
5
  rails_version = ENV['RAILS_VERSION'] || 'default'
8
6
  jsonapi_resources_version = ENV['JSONAPI_RESOURCES_VERSION'] || 'default'
9
7
 
data/README.md CHANGED
@@ -14,17 +14,26 @@ branch. This may contain information that is not relevant to the release you are
14
14
  [jr]: https://github.com/cerebris/jsonapi-resources "A resource-focused Rails library for developing JSON API compliant servers."
15
15
  [pundit]: https://github.com/elabs/pundit "Minimal authorization through OO design and pure Ruby classes"
16
16
 
17
+ The core design principle of `JSONAPI::Authorization` is:
18
+
19
+ **Prefer being overly restrictive rather than too permissive by accident.**
20
+
21
+ What follows is that we want to have:
22
+
23
+ 1. Whitelist over blacklist -approach for authorization
24
+ 2. Fall back on a more strict authorization
25
+
17
26
  ## Caveats
18
27
 
19
28
  Make sure to test for authorization in your application, too. We should have coverage of all operations, though. If that isn't the case, please [open an issue][issues].
20
29
 
21
30
  If you're using custom processors, make sure that they extend `JSONAPI::Authorization::AuthorizingProcessor`, or authorization will not be performed for that resource.
22
31
 
23
- This gem should work out-of-the box for simple cases. The default authorizer might be overly restrictive for [more complex cases][complex-case].
32
+ This gem should work out-of-the box for simple cases. The default authorizer might be overly restrictive for cases where you are touching relationships.
24
33
 
25
- The API is subject to change between minor version bumps until we reach v1.0.0.
34
+ **If you are modifying relationships**, you should read the [relationship authorization documentation](docs/relationship-authorization.md).
26
35
 
27
- [complex-case]: https://github.com/venuu/jsonapi-authorization/issues/15
36
+ The API is subject to change between minor version bumps until we reach v1.0.0.
28
37
 
29
38
  ## Installation
30
39
 
@@ -97,8 +106,7 @@ To check whether an action is allowed JSONAPI::Authorization calls the respectiv
97
106
  (`index?`, `show?`, `create?`, `update?`, `destroy?`).
98
107
 
99
108
  For relationship operations by default `update?` is being called for all affected resources.
100
- For a finer grained control you can define `add_to_<relation>?`, `replace_<relation>?`, and `remove_from_<relation>?`
101
- as the following example shows.
109
+ For a finer grained control you can define methods to authorize relationship changes. For example:
102
110
 
103
111
  ```ruby
104
112
  class ArticlePolicy
@@ -120,9 +128,7 @@ class ArticlePolicy
120
128
  end
121
129
  ```
122
130
 
123
- Caveat: In case a relationship is modifiable through multiple ways it is your responsibility to ensure consistency.
124
- For example if you have a many-to-many relationship with users and projects make sure that
125
- `ProjectPolicy#add_to_users?(users)` and `UserPolicy#add_to_projects?(projects)` match up.
131
+ For thorough documentation about custom policy methods, check out the [relationship authorization docs](docs/relationship-authorization.md).
126
132
 
127
133
  ## Configuration
128
134
 
@@ -177,9 +183,10 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/venuu/
177
183
  Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)):
178
184
 
179
185
  <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
180
- | [<img src="https://avatars.githubusercontent.com/u/482561?v=3" width="100px;"/><br /><sub>Vesa Laakso</sub>](http://vesalaakso.com)<br />[💻](https://github.com/Venuu/jsonapi-authorization/commits?author=valscion) [📖](https://github.com/Venuu/jsonapi-authorization/commits?author=valscion) 🚇 [⚠️](https://github.com/Venuu/jsonapi-authorization/commits?author=valscion) [🐛](https://github.com/Venuu/jsonapi-authorization/issues?q=author%3Avalscion) 💬 👀 | [<img src="https://avatars.githubusercontent.com/u/562204?v=3" width="100px;"/><br /><sub>Emil Sågfors</sub>](https://github.com/lime)<br />[💻](https://github.com/Venuu/jsonapi-authorization/commits?author=lime) [📖](https://github.com/Venuu/jsonapi-authorization/commits?author=lime) 🚇 [⚠️](https://github.com/Venuu/jsonapi-authorization/commits?author=lime) [🐛](https://github.com/Venuu/jsonapi-authorization/issues?q=author%3Alime) 💬 👀 | [<img src="https://avatars.githubusercontent.com/u/1591161?v=3" width="100px;"/><br /><sub>Matthias Grundmann</sub>](https://github.com/matthias-g)<br />[💻](https://github.com/Venuu/jsonapi-authorization/commits?author=matthias-g) [📖](https://github.com/Venuu/jsonapi-authorization/commits?author=matthias-g) [⚠️](https://github.com/Venuu/jsonapi-authorization/commits?author=matthias-g) 💬 | [<img src="https://avatars.githubusercontent.com/u/1322?v=3" width="100px;"/><br /><sub>Thibaud Guillaume-Gentil</sub>](http://thibaud.gg)<br />[💻](https://github.com/Venuu/jsonapi-authorization/commits?author=thibaudgg) | [<img src="https://avatars.githubusercontent.com/u/71660?v=3" width="100px;"/><br /><sub>Daniel Schweighöfer</sub>](http://netsteward.net)<br />[💻](https://github.com/Venuu/jsonapi-authorization/commits?author=acid) | [<img src="https://avatars.githubusercontent.com/u/5076967?v=3" width="100px;"/><br /><sub>Bruno Sofiato</sub>](https://github.com/bsofiato)<br />[💻](https://github.com/Venuu/jsonapi-authorization/commits?author=bsofiato) | [<img src="https://avatars.githubusercontent.com/u/1896026?v=3" width="100px;"/><br /><sub>Adam Robertson</sub>](https://github.com/arcreative)<br />[📖](https://github.com/Venuu/jsonapi-authorization/commits?author=arcreative) |
186
+ <!-- prettier-ignore -->
187
+ | [<img src="https://avatars.githubusercontent.com/u/482561?v=3" width="100px;"/><br /><sub><b>Vesa Laakso</b></sub>](http://vesalaakso.com)<br />[💻](https://github.com/Venuu/jsonapi-authorization/commits?author=valscion "Code") [📖](https://github.com/Venuu/jsonapi-authorization/commits?author=valscion "Documentation") [🚇](#infra-valscion "Infrastructure (Hosting, Build-Tools, etc)") [⚠️](https://github.com/Venuu/jsonapi-authorization/commits?author=valscion "Tests") [🐛](https://github.com/Venuu/jsonapi-authorization/issues?q=author%3Avalscion "Bug reports") [💬](#question-valscion "Answering Questions") [👀](#review-valscion "Reviewed Pull Requests") | [<img src="https://avatars.githubusercontent.com/u/562204?v=3" width="100px;"/><br /><sub><b>Emil Sågfors</b></sub>](https://github.com/lime)<br />[💻](https://github.com/Venuu/jsonapi-authorization/commits?author=lime "Code") [📖](https://github.com/Venuu/jsonapi-authorization/commits?author=lime "Documentation") [🚇](#infra-lime "Infrastructure (Hosting, Build-Tools, etc)") [⚠️](https://github.com/Venuu/jsonapi-authorization/commits?author=lime "Tests") [🐛](https://github.com/Venuu/jsonapi-authorization/issues?q=author%3Alime "Bug reports") [💬](#question-lime "Answering Questions") [👀](#review-lime "Reviewed Pull Requests") | [<img src="https://avatars.githubusercontent.com/u/1591161?v=3" width="100px;"/><br /><sub><b>Matthias Grundmann</b></sub>](https://github.com/matthias-g)<br />[💻](https://github.com/Venuu/jsonapi-authorization/commits?author=matthias-g "Code") [📖](https://github.com/Venuu/jsonapi-authorization/commits?author=matthias-g "Documentation") [⚠️](https://github.com/Venuu/jsonapi-authorization/commits?author=matthias-g "Tests") [💬](#question-matthias-g "Answering Questions") | [<img src="https://avatars.githubusercontent.com/u/1322?v=3" width="100px;"/><br /><sub><b>Thibaud Guillaume-Gentil</b></sub>](http://thibaud.gg)<br />[💻](https://github.com/Venuu/jsonapi-authorization/commits?author=thibaudgg "Code") | [<img src="https://avatars.githubusercontent.com/u/71660?v=3" width="100px;"/><br /><sub><b>Daniel Schweighöfer</b></sub>](http://netsteward.net)<br />[💻](https://github.com/Venuu/jsonapi-authorization/commits?author=acid "Code") | [<img src="https://avatars.githubusercontent.com/u/5076967?v=3" width="100px;"/><br /><sub><b>Bruno Sofiato</b></sub>](https://github.com/bsofiato)<br />[💻](https://github.com/Venuu/jsonapi-authorization/commits?author=bsofiato "Code") | [<img src="https://avatars.githubusercontent.com/u/1896026?v=3" width="100px;"/><br /><sub><b>Adam Robertson</b></sub>](https://github.com/arcreative)<br />[📖](https://github.com/Venuu/jsonapi-authorization/commits?author=arcreative "Documentation") |
181
188
  | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
182
- | [<img src="https://avatars3.githubusercontent.com/u/4742306?v=3" width="100px;"/><br /><sub>Greg Fisher</sub>](https://github.com/gnfisher)<br />[💻](https://github.com/Venuu/jsonapi-authorization/commits?author=gnfisher) [⚠️](https://github.com/Venuu/jsonapi-authorization/commits?author=gnfisher) | [<img src="https://avatars3.githubusercontent.com/u/370182?v=3" width="100px;"/><br /><sub>Sam</sub>](http://samlh.com)<br />[💻](https://github.com/Venuu/jsonapi-authorization/commits?author=handlers) [⚠️](https://github.com/Venuu/jsonapi-authorization/commits?author=handlers) | [<img src="https://avatars0.githubusercontent.com/u/2738630?v=3" width="100px;"/><br /><sub>Justas Palumickas</sub>](https://jpalumickas.com)<br />[🐛](https://github.com/Venuu/jsonapi-authorization/issues?q=author%3Ajpalumickas) [💻](https://github.com/Venuu/jsonapi-authorization/commits?author=jpalumickas) [⚠️](https://github.com/Venuu/jsonapi-authorization/commits?author=jpalumickas) |
189
+ | [<img src="https://avatars3.githubusercontent.com/u/4742306?v=3" width="100px;"/><br /><sub><b>Greg Fisher</b></sub>](https://github.com/gnfisher)<br />[💻](https://github.com/Venuu/jsonapi-authorization/commits?author=gnfisher "Code") [⚠️](https://github.com/Venuu/jsonapi-authorization/commits?author=gnfisher "Tests") | [<img src="https://avatars3.githubusercontent.com/u/370182?v=3" width="100px;"/><br /><sub><b>Sam</b></sub>](http://samlh.com)<br />[💻](https://github.com/Venuu/jsonapi-authorization/commits?author=handlers "Code") [⚠️](https://github.com/Venuu/jsonapi-authorization/commits?author=handlers "Tests") | [<img src="https://avatars0.githubusercontent.com/u/2738630?v=3" width="100px;"/><br /><sub><b>Justas Palumickas</b></sub>](https://jpalumickas.com)<br />[🐛](https://github.com/Venuu/jsonapi-authorization/issues?q=author%3Ajpalumickas "Bug reports") [💻](https://github.com/Venuu/jsonapi-authorization/commits?author=jpalumickas "Code") [⚠️](https://github.com/Venuu/jsonapi-authorization/commits?author=jpalumickas "Tests") | [<img src="https://avatars1.githubusercontent.com/u/26158?v=4" width="100px;"/><br /><sub><b>Nicholas Rutherford</b></sub>](http://www.google.co.uk/profiles/nick.rutherford)<br />[💻](https://github.com/Venuu/jsonapi-authorization/commits?author=nruth "Code") [⚠️](https://github.com/Venuu/jsonapi-authorization/commits?author=nruth "Tests") [🚇](#infra-nruth "Infrastructure (Hosting, Build-Tools, etc)") |
183
190
  <!-- ALL-CONTRIBUTORS-LIST:END -->
184
191
 
185
- This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
192
+ This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
@@ -0,0 +1,601 @@
1
+ # <a name="doc-top"></a>Authorization of operations touching relationships
2
+
3
+ `JSONAPI::Authorization` (JA) is unique in the way it considers relationship changes to change the underlying models. Whenever an incoming request changes associated resources, JA will authorize those operations are OK.
4
+
5
+ As JA runs the authorization checks _before_ any changes are made (even to in-memory objects), Pundit policies don't have the information needed to authorize changes to relationships. This is why JA provides special hooks to authorize relationship changes and falls back to checking `#update?` on all the related records.
6
+
7
+ Caveat: In case a relationship is modifiable through multiple ways it is your responsibility to ensure consistency.
8
+ For example if you have a many-to-many relationship with users and projects make sure that
9
+ `ProjectPolicy#add_to_users?(users)` and `UserPolicy#add_to_projects?(projects)` match up.
10
+
11
+ **Table of contents**
12
+
13
+ * `has-one` relationships
14
+ - [Example setup for `has-one` examples](#example-setup-has-one)
15
+ - [`PATCH /articles/article-1/relationships/author`](#change-has-one-relationship-op)
16
+ * Changing a `has-one` relationship
17
+ - [`DELETE /articles/article-1/relationships/author`](#remove-has-one-relationship-op)
18
+ * Removing a `has-one` relationship
19
+ - [`PATCH /articles/article-1/` with different `author` relationship](#change-and-replace-has-one-resource-op)
20
+ * Changing resource and replacing a `has-one` relationship
21
+ - [`PATCH /articles/article-1/` with null `author` relationship](#change-and-remove-has-one-resource-op)
22
+ * Changing resource and removing a `has-one` relationship
23
+ - [`POST /articles` with an `author` relationship](#create-has-one-resource-op)
24
+ * Creating a resource with a `has-one` relationship
25
+ * `has-many` relationships
26
+ - [Example setup for `has-many` examples](#example-setup-has-many)
27
+ - [`POST /articles/article-1/relationships/comments`](#add-to-has-many-relationship-op)
28
+ * Adding to a `has-many` relationship
29
+ - [`DELETE /articles/article-1/relationships/comments`](#remove-from-has-many-relationship-op)
30
+ * Removing from a `has-many` relationship
31
+ - [`PATCH /articles/article-1/relationships/comments` with different `comments`](#replace-has-many-relationship-op)
32
+ * Replacing a `has-many` relationship
33
+ - [`PATCH /articles/article-1/relationships/comments` with empty `comments`](#remove-has-many-relationship-op)
34
+ * Removing a `has-many` relationship
35
+ - [`PATCH /articles/article-1` with different `comments` relationship](#change-and-replace-has-many-resource-op)
36
+ * Changing resource and replacing a `has-many` relationship
37
+ - [`PATCH /articles/article-1` with empty `comments` relationship](#change-and-remove-has-many-resource-op)
38
+ * Changing resource and removing a `has-many` relationship
39
+ - [`POST /articles` with a `comments` relationship](#create-has-many-resource-op)
40
+ * Creating a resource with a `has-many` relationship
41
+
42
+
43
+ <a name="example-setup-has-one"></a>
44
+
45
+ [back to top ↑](#doc-top)
46
+
47
+ ## `has-one` relationships
48
+
49
+ ### Example setup for `has-one` examples
50
+
51
+ The examples for `has-one` relationship authorization use these models and resources:
52
+
53
+ ```rb
54
+ class Article < ActiveRecord::Base
55
+ belongs_to :author, class_name: 'User'
56
+ end
57
+
58
+ class ArticleResource < JSONAPI::Resource
59
+ include JSONAPI::Authorization::PunditScopedResource
60
+ has_one :author, class_name: 'User'
61
+ end
62
+ ```
63
+
64
+ ```rb
65
+ class User < ActiveRecord::Base
66
+ has_many :articles, foreign_key: :author_id
67
+ end
68
+
69
+ class UserResource < JSONAPI::Resource
70
+ include JSONAPI::Authorization::PunditScopedResource
71
+ has_many :articles
72
+ end
73
+ ```
74
+
75
+ <a name="change-has-one-relationship-op"></a>
76
+
77
+ [back to top ↑](#doc-top)
78
+
79
+ ### `PATCH /articles/article-1/relationships/author`
80
+
81
+ _Changing a `has-one` relationship with a relationship operation_
82
+
83
+ Setup:
84
+
85
+ ```rb
86
+ user_1 = User.create(id: 'user-1')
87
+ article_1 = Article.create(id: 'article-1', author: user_1)
88
+ user_2 = User.create(id: 'user-2')
89
+ ```
90
+
91
+ > `PATCH /articles/article-1/relationships/author`
92
+ >
93
+ > ```json
94
+ > {
95
+ > "type": "users",
96
+ > "id": "user-2"
97
+ > }
98
+ > ```
99
+
100
+ #### Custom relationship authorization method
101
+
102
+ * `ArticlePolicy.new(current_user, article_1).replace_author?(user_2)`
103
+
104
+ #### Fallback
105
+
106
+ * `ArticlePolicy.new(current_user, article_1).update?`
107
+ * `UserPolicy.new(current_user, user_2).update?`
108
+
109
+ **Note:** Currently JA does not fallback to authorizing `UserPolicy#update?` on `user_1` that is about to be dissociated. This will likely be changed in the future.
110
+
111
+ <a name="remove-has-one-relationship-op"></a>
112
+
113
+ [back to top ↑](#doc-top)
114
+
115
+ ### `DELETE /articles/article-1/relationships/author`
116
+
117
+ _Removing a `has-one` relationship with a relationship operation_
118
+
119
+ Setup:
120
+
121
+ ```rb
122
+ user_1 = User.create(id: 'user-1')
123
+ article_1 = Article.create(id: 'article-1', author: user_1)
124
+ ```
125
+
126
+ > `DELETE /articles/article-1/relationships/author`
127
+ >
128
+ > (empty body)
129
+
130
+ #### Custom relationship authorization method
131
+
132
+ * `ArticlePolicy.new(current_user, article_1).remove_author?`
133
+
134
+ #### Fallback
135
+
136
+ * `ArticlePolicy.new(current_user, article_1).update?`
137
+
138
+ **Note:** Currently JA does not fallback to authorizing `UserPolicy#update?` on `user_1` that is about to be dissociated. This will likely be changed in the future.
139
+
140
+ <a name="change-and-replace-has-one-resource-op"></a>
141
+
142
+ [back to top ↑](#doc-top)
143
+
144
+ ### `PATCH /articles/article-1/` with different `author` relationship
145
+
146
+ _Changing resource and replacing a `has-one` relationship_
147
+
148
+ Setup:
149
+
150
+ ```rb
151
+ user_1 = User.create(id: 'user-1')
152
+ article_1 = Article.create(id: 'article-1', author: user_1)
153
+ user_2 = User.create(id: 'user-2')
154
+ ```
155
+
156
+ > `PATCH /articles/article-1`
157
+ >
158
+ > ```json
159
+ > {
160
+ > "type": "articles",
161
+ > "id": "article-1",
162
+ > "relationships": {
163
+ > "author": {
164
+ > "data": {
165
+ > "type": "users",
166
+ > "id": "user-2"
167
+ > }
168
+ > }
169
+ > }
170
+ > }
171
+ > ```
172
+
173
+ #### Always calls
174
+
175
+ * `ArticlePolicy.new(current_user, article_1).update?`
176
+
177
+ #### Custom relationship authorization method
178
+
179
+ * `ArticlePolicy.new(current_user, article_1).replace_author?(user_2)`
180
+
181
+ #### Fallback
182
+
183
+ * `ArticlePolicy.new(current_user, article_1).update?`
184
+ * `UserPolicy.new(current_user, user_2).update?`
185
+
186
+ **Note:** Currently JA does not fallback to authorizing `UserPolicy#update?` on `user_1` that is about to be dissociated. This will likely be changed in the future.
187
+
188
+ <a name="change-and-remove-has-one-resource-op"></a>
189
+
190
+ [back to top ↑](#doc-top)
191
+
192
+ ### `PATCH /articles/article-1/` with null `author` relationship
193
+
194
+ _Changing resource and removing a `has-one` relationship_
195
+
196
+ Setup:
197
+
198
+ ```rb
199
+ user_1 = User.create(id: 'user-1')
200
+ article_1 = Article.create(id: 'article-1', author: user_1)
201
+ ```
202
+
203
+ > `PATCH /articles/article-1`
204
+ >
205
+ > ```json
206
+ > {
207
+ > "type": "articles",
208
+ > "id": "article-1",
209
+ > "relationships": {
210
+ > "author": {
211
+ > "data": null
212
+ > }
213
+ > }
214
+ > }
215
+ > ```
216
+
217
+ #### Always calls
218
+
219
+ * `ArticlePolicy.new(current_user, article_1).update?`
220
+
221
+ #### Custom relationship authorization method
222
+
223
+ * `ArticlePolicy.new(current_user, article_1).remove_author?`
224
+
225
+ #### Fallback
226
+
227
+ * `ArticlePolicy.new(current_user, article_1).update?`
228
+
229
+ **Note:** Currently JA does not fallback to authorizing `UserPolicy#update?` on `user_1` that is about to be dissociated. This will likely be changed in the future.
230
+
231
+ <a name="create-has-one-resource-op"></a>
232
+
233
+ [back to top ↑](#doc-top)
234
+
235
+ ### `POST /articles` with an `author` relationship
236
+
237
+ _Creating a resource with a `has-one` relationship_
238
+
239
+ Setup:
240
+
241
+ ```rb
242
+ user_1 = User.create(id: 'user-1')
243
+ ```
244
+
245
+ > `POST /articles`
246
+ >
247
+ > ```json
248
+ > {
249
+ > "type": "articles",
250
+ > "relationships": {
251
+ > "author": {
252
+ > "data": {
253
+ > "type": "users",
254
+ > "id": "user-1"
255
+ > }
256
+ > }
257
+ > }
258
+ > }
259
+ > ```
260
+
261
+ #### Always calls
262
+
263
+ * `ArticlePolicy.new(current_user, Article).create?`
264
+
265
+ **Note:** The second parameter for the policy is the `Article` _class_, not the new record. This is because JA runs the authorization checks _before_ any changes are made, even changes to in-memory objects.
266
+
267
+ #### Custom relationship authorization method
268
+
269
+ * `ArticlePolicy.new(current_user, Article).create_with_author?(user_1)`
270
+
271
+ #### Fallback
272
+
273
+ * `UserPolicy.new(current_user, user_1).update?`
274
+
275
+
276
+ <a name="example-setup-has-many"></a>
277
+
278
+ [back to top ↑](#doc-top)
279
+
280
+ ## `has-many` relationships
281
+
282
+ ### Example setup for `has-many` examples
283
+
284
+ The examples for `has-many` relationship authorization use these models and resources:
285
+
286
+ ```rb
287
+ class Article < ActiveRecord::Base
288
+ has_many :comments
289
+ end
290
+
291
+ class ArticleResource < JSONAPI::Resource
292
+ include JSONAPI::Authorization::PunditScopedResource
293
+ # `acts_as_set` allows replacing all comments at once
294
+ has_many :comments, acts_as_set: true
295
+ end
296
+ ```
297
+
298
+ ```rb
299
+ class Comment < ActiveRecord::Base
300
+ belongs_to :article
301
+ end
302
+
303
+ class CommentResource < JSONAPI::Resource
304
+ include JSONAPI::Authorization::PunditScopedResource
305
+ has_one :article
306
+ end
307
+ ```
308
+
309
+ <a name="add-to-has-many-relationship-op"></a>
310
+
311
+ [back to top ↑](#doc-top)
312
+
313
+ ### `POST /articles/article-1/relationships/comments`
314
+
315
+ _Adding to a `has-many` relationship_
316
+
317
+ Setup:
318
+
319
+ ```rb
320
+ comment_1 = Comment.create(id: 'comment-1')
321
+ article_1 = Article.create(id: 'article-1', comments: [comment_1])
322
+ comment_2 = Comment.create(id: 'comment-2')
323
+ comment_3 = Comment.create(id: 'comment-3')
324
+ ```
325
+
326
+ > `POST /articles/article-1/relationships/comments`
327
+ >
328
+ > ```json
329
+ > {
330
+ > "data": [
331
+ > { "type": "comments", "id": "comment-2" },
332
+ > { "type": "comments", "id": "comment-3" }
333
+ > ]
334
+ > }
335
+ > ```
336
+
337
+ #### Custom relationship authorization method
338
+
339
+ * `ArticlePolicy.new(current_user, article_1).add_to_comments?([comment_2, comment_3])`
340
+
341
+ #### Fallback
342
+
343
+ * `ArticlePolicy.new(current_user, article_1).update?`
344
+ * `CommentPolicy.new(current_user, comment_2).update?`
345
+ * `CommentPolicy.new(current_user, comment_3).update?`
346
+
347
+ <a name="remove-from-has-many-relationship-op"></a>
348
+
349
+ [back to top ↑](#doc-top)
350
+
351
+ ### `DELETE /articles/article-1/relationships/comments`
352
+
353
+ _Removing from a `has-many` relationship_
354
+
355
+ Setup:
356
+
357
+ ```rb
358
+ comment_1 = Comment.create(id: 'comment-1')
359
+ comment_2 = Comment.create(id: 'comment-2')
360
+ comment_3 = Comment.create(id: 'comment-3')
361
+ article_1 = Article.create(id: 'article-1', comments: [comment_1, comment_2, comment_3])
362
+ ```
363
+
364
+ > `DELETE /articles/article-1/relationships/comments`
365
+ >
366
+ > ```json
367
+ > {
368
+ > "data": [
369
+ > { "type": "comments", "id": "comment-1" },
370
+ > { "type": "comments", "id": "comment-2" }
371
+ > ]
372
+ > }
373
+ > ```
374
+
375
+ #### Custom relationship authorization method
376
+
377
+ * `ArticlePolicy.new(current_user, article_1).remove_from_comments?([comment_1, comment_2])`
378
+
379
+ #### Fallback
380
+
381
+ * `ArticlePolicy.new(current_user, article_1).update?`
382
+ * `CommentPolicy.new(current_user, comment_1).update?`
383
+ * `CommentPolicy.new(current_user, comment_2).update?`
384
+
385
+ <a name="replace-has-many-relationship-op"></a>
386
+
387
+ [back to top ↑](#doc-top)
388
+
389
+ ### `PATCH /articles/article-1/relationships/comments` with different `comments`
390
+
391
+ _Replacing a `has-many` relationship_
392
+
393
+ Setup:
394
+
395
+ ```rb
396
+ comment_1 = Comment.create(id: 'comment-1')
397
+ article_1 = Article.create(id: 'article-1', comments: [comment_1])
398
+ comment_2 = Comment.create(id: 'comment-2')
399
+ comment_3 = Comment.create(id: 'comment-3')
400
+ ```
401
+
402
+ > `PATCH /articles/article-1/relationships/comments`
403
+ >
404
+ > ```json
405
+ > {
406
+ > "data": [
407
+ > { "type": "comments", "id": "comment-2" },
408
+ > { "type": "comments", "id": "comment-3" }
409
+ > ]
410
+ > }
411
+ > ```
412
+
413
+ #### Custom relationship authorization method
414
+
415
+ * `ArticlePolicy.new(current_user, article_1).replace_comments?([comment_2, comment_3])`
416
+
417
+ #### Fallback
418
+
419
+ * `ArticlePolicy.new(current_user, article_1).update?`
420
+ * `CommentPolicy.new(current_user, comment_2).update?`
421
+ * `CommentPolicy.new(current_user, comment_3).update?`
422
+
423
+ **Note:** Currently JA does not fallback to authorizing `CommentPolicy#update?` on `comment_1` that is about to be dissociated. This will likely be changed in the future.
424
+
425
+ <a name="remove-has-many-relationship-op"></a>
426
+
427
+ [back to top ↑](#doc-top)
428
+
429
+ ### `PATCH /articles/article-1/relationships/comments` with empty `comments`
430
+
431
+ _Removing a `has-many` relationship_
432
+
433
+ Setup:
434
+
435
+ ```rb
436
+ comment_1 = Comment.create(id: 'comment-1')
437
+ article_1 = Article.create(id: 'article-1', comments: [comment_1])
438
+ ```
439
+
440
+ > `PATCH /articles/article-1/relationships/comments`
441
+ >
442
+ > ```json
443
+ > {
444
+ > "data": []
445
+ > }
446
+ > ```
447
+
448
+ #### Custom relationship authorization method
449
+
450
+ * `ArticlePolicy.new(current_user, article_1).replace_comments?([])`
451
+
452
+ **TODO:** We should probably call `remove_comments?` (with no arguments) instead. See https://github.com/venuu/jsonapi-authorization/issues/73 for more details and implementation progress.
453
+
454
+ #### Fallback
455
+
456
+ * `ArticlePolicy.new(current_user, article_1).update?`
457
+
458
+ **Note:** Currently JA does not fallback to authorizing `CommentPolicy#update?` on `comment_1` that is about to be dissociated. This will likely be changed in the future.
459
+
460
+ <a name="change-and-replace-has-many-resource-op"></a>
461
+
462
+ [back to top ↑](#doc-top)
463
+
464
+ ### `PATCH /articles/article-1` with different `comments` relationship
465
+
466
+ _Changing resource and replacing a `has-many` relationship_
467
+
468
+ Setup:
469
+
470
+ ```rb
471
+ comment_1 = Comment.create(id: 'comment-1')
472
+ article_1 = Article.create(id: 'article-1', comments: [comment_1])
473
+ comment_2 = Comment.create(id: 'comment-2')
474
+ comment_3 = Comment.create(id: 'comment-3')
475
+ ```
476
+
477
+ > `PATCH /articles/article-1`
478
+ >
479
+ > ```json
480
+ > {
481
+ > "type": "articles",
482
+ > "id": "article-1",
483
+ > "relationships": {
484
+ > "comments": {
485
+ > "data": [
486
+ > { "type": "comments", "id": "comment-2" },
487
+ > { "type": "comments", "id": "comment-3" }
488
+ > ]
489
+ > }
490
+ > }
491
+ > }
492
+ > ```
493
+
494
+ #### Always calls
495
+
496
+ * `ArticlePolicy.new(current_user, article_1).update?`
497
+
498
+ #### Custom relationship authorization method
499
+
500
+ * `ArticlePolicy.new(current_user, article_1).replace_comments?([comment_2, comment_3])`
501
+
502
+ #### Fallback
503
+
504
+ * `ArticlePolicy.new(current_user, article_1).update?`
505
+ * `CommentPolicy.new(current_user, comment_2).update?`
506
+ * `CommentPolicy.new(current_user, comment_3).update?`
507
+
508
+ **Note:** Currently JA does not fallback to authorizing `CommentPolicy#update?` on `comment_1` that is about to be dissociated. This will likely be changed in the future.
509
+
510
+ <a name="change-and-remove-has-many-resource-op"></a>
511
+
512
+ [back to top ↑](#doc-top)
513
+
514
+ ### `PATCH /articles/article-1` with empty `comments` relationship
515
+
516
+ _Changing resource and removing a `has-many` relationship_
517
+
518
+ Setup:
519
+
520
+ ```rb
521
+ comment_1 = Comment.create(id: 'comment-1')
522
+ article_1 = Article.create(id: 'article-1', comments: [comment_1])
523
+ ```
524
+
525
+ > `PATCH /articles/article-1`
526
+ >
527
+ > ```json
528
+ > {
529
+ > "type": "articles",
530
+ > "id": "article-1",
531
+ > "relationships": {
532
+ > "comments": {
533
+ > "data": []
534
+ > }
535
+ > }
536
+ > }
537
+ > ```
538
+
539
+ #### Always calls
540
+
541
+ * `ArticlePolicy.new(current_user, article_1).update?`
542
+
543
+ #### Custom relationship authorization method
544
+
545
+ * `ArticlePolicy.new(current_user, article_1).replace_comments?([])`
546
+
547
+ **TODO:** We should probably call `remove_comments?` (with no arguments) instead. See https://github.com/venuu/jsonapi-authorization/issues/73 for more details and implementation progress.
548
+
549
+ #### Fallback
550
+
551
+ * `ArticlePolicy.new(current_user, article_1).update?`
552
+
553
+ **Note:** Currently JA does not fallback to authorizing `CommentPolicy#update?` on `comment_1` that is about to be dissociated. This will likely be changed in the future.
554
+
555
+ <a name="create-has-many-resource-op"></a>
556
+
557
+ [back to top ↑](#doc-top)
558
+
559
+ ### `POST /articles` with a `comments` relationship
560
+
561
+ _Creating a resource with a `has-many` relationship_
562
+
563
+ Setup:
564
+
565
+ ```rb
566
+ comment_1 = Comment.create(id: 'comment-1')
567
+ comment_2 = Comment.create(id: 'comment-2')
568
+ ```
569
+
570
+ > `POST /articles`
571
+ >
572
+ > ```json
573
+ > {
574
+ > "type": "articles",
575
+ > "relationships": {
576
+ > "comments": {
577
+ > "data": [
578
+ > { "type": "comments", "id": "comment-1" },
579
+ > { "type": "comments", "id": "comment-2" }
580
+ > ]
581
+ > }
582
+ > }
583
+ > }
584
+ > ```
585
+
586
+ #### Always calls
587
+
588
+ * `ArticlePolicy.new(current_user, Article).create?`
589
+
590
+ **Note:** The second parameter for the policy is the `Article` _class_, not the new record. This is because JA runs the authorization checks _before_ any changes are made, even changes to in-memory objects.
591
+
592
+ #### Custom relationship authorization method
593
+
594
+ * `ArticlePolicy.new(current_user, Article).create_with_comments?([comment_1, comment_2])`
595
+
596
+ #### Fallback
597
+
598
+ * `CommentPolicy.new(current_user, comment_1).update?`
599
+ * `CommentPolicy.new(current_user, comment_2).update?`
600
+
601
+ [back to top ↑](#doc-top)