Skip to content

Commit 26b089c

Browse files
committed
Add serialization_scope example [ci skip]
1 parent 2f27d6c commit 26b089c

File tree

1 file changed

+92
-1
lines changed

1 file changed

+92
-1
lines changed

docs/general/serializers.md

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,98 @@ PR please :)
156156
157157
#### #scope
158158
159-
PR please :)
159+
Allows you to include in the serializer access to an external method.
160+
161+
It's intended to provide an authorization context to the serializer, so that
162+
you may e.g. show an admin all comments on a post, else only published comments.
163+
164+
- `scope` is a method on the serializer instance that comes from `options[:scope]`. It may be nil.
165+
- `scope_name` is an option passed to the new serializer (`options[:scope_name]`). The serializer
166+
defines a method with that name that calls the `scope`, e.g. `def current_user; scope; end`.
167+
Note: it does not define the method if the serializer instance responds to it.
168+
169+
That's a lot of words, so here's some examples:
170+
171+
First, let's assume the serializer is instantiated in the controller, since that's the usual scenario.
172+
We'll refer to the serialization context as `controller`.
173+
174+
| options | `Serializer#scope` | method definition |
175+
|-------- | ------------------|--------------------|
176+
| `scope: current_user, scope_name: :current_user` | `current_user` | `Serializer#current_user` calls `controller.current_user`
177+
| `scope: view_context, scope_name: :view_context` | `view_context` | `Serializer#view_context` calls `controller.view_context`
178+
179+
We can take advantage of the scope to customize the objects returned based
180+
on the current user (scope).
181+
182+
For example, we can limit the posts the current user sees to those they created:
183+
184+
```ruby
185+
class PostSerializer < ActiveModel::Serializer
186+
attributes :id, :title, :body
187+
188+
# scope comments to those created_by the current user
189+
has_many :comments do
190+
object.comments.where(created_by: current_user)
191+
end
192+
end
193+
```
194+
195+
Whether you write the method as above or as `object.comments.where(created_by: scope)`
196+
is a matter of preference (assuming `scope_name` has been set).
197+
198+
##### Controller Authorization Context
199+
200+
In the controller, the scope/scope_name options are equal to
201+
the [`serialization_scope`method](https://github.com/rails-api/active_model_serializers/blob/d02cd30fe55a3ea85e1d351b6e039620903c1871/lib/action_controller/serialization.rb#L13-L20),
202+
which is `:current_user`, by default.
203+
204+
Specfically, the `scope_name` is defaulted to `:current_user`, and may be set as
205+
`serialization_scope :view_context`. The `scope` is set to `send(scope_name)` when `scope_name` is
206+
present and the controller responds to `scope_name`.
207+
208+
Thus, in a serializer, the controller provides `current_user` as the
209+
current authorization scope when you call `render :json`.
210+
211+
**IMPORTANT**: Since the scope is set at render, you may want to customize it so that `current_user` isn't
212+
called on every request. This was [also a problem](https://github.com/rails-api/active_model_serializers/pull/1252#issuecomment-159810477)
213+
in [`0.9`](https://github.com/rails-api/active_model_serializers/tree/0-9-stable#customizing-scope).
214+
215+
We can change the scope from `current_user` to `view_context`.
216+
217+
```diff
218+
class SomeController < ActionController::Base
219+
+ serialization_scope :view_context
220+
221+
def current_user
222+
User.new(id: 2, name: 'Bob', admin: true)
223+
end
224+
225+
def edit
226+
user = User.new(id: 1, name: 'Pete')
227+
render json: user, serializer: AdminUserSerializer, adapter: :json_api
228+
end
229+
end
230+
```
231+
232+
We could then use the controller method `view_context` in our serializer, like so:
233+
234+
```diff
235+
class AdminUserSerializer < ActiveModel::Serializer
236+
attributes :id, :name, :can_edit
237+
238+
def can_edit?
239+
+ view_context.current_user.admin?
240+
end
241+
end
242+
```
243+
244+
So that when we render the `#edit` action, we'll get
245+
246+
```json
247+
{"data":{"id":"1","type":"users","attributes":{"name":"Pete","can_edit":true}}}
248+
```
249+
250+
Where `can_edit` is `view_context.current_user.admin?` (true).
160251

161252
#### #read_attribute_for_serialization(key)
162253

0 commit comments

Comments
 (0)