jsonapi-authorization 1.0.0.alpha6 → 1.0.0.beta1

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