File: serializers.md

package info (click to toggle)
ruby-active-model-serializers 0.10.12-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 1,752 kB
  • sloc: ruby: 13,138; sh: 53; makefile: 6
file content (495 lines) | stat: -rw-r--r-- 14,168 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
[Back to Guides](../README.md)

# Serializers

Given a serializer class:

```ruby
class SomeSerializer < ActiveModel::Serializer
end
```

The following methods may be defined in it:

### Attributes

#### ::attributes

Serialization of the resource `title` and `body`

| In Serializer               | #attributes |
|---------------------------- |-------------|
| `attributes :title, :body`  | `{ title: 'Some Title', body: 'Some Body' }`
| `attributes :title, :body`<br>`def body "Special #{object.body}" end` | `{ title: 'Some Title', body: 'Special Some Body' }`


#### ::attribute

Serialization of the resource `title`

| In Serializer               | #attributes |
|---------------------------- |-------------|
| `attribute :title`          | `{ title: 'Some Title' } `
| `attribute :title, key: :name` | `{ name: 'Some Title' } `
| `attribute(:title) { 'A Different Title'}` | `{ title: 'A Different Title' } `
| `attribute :title`<br>`def title 'A Different Title' end` | `{ title: 'A Different Title' }`

An `if` or `unless` option can make an attribute conditional. It takes a symbol of a method name on the serializer, or a lambda literal.

e.g.

```ruby
attribute :private_data, if: :is_current_user?
attribute :another_private_data, if: -> { scope.admin? }

def is_current_user?
  object.id == current_user.id
end
```

### Associations

The interface for associations is, generically:

> `association_type(association_name, options, &block)`

Where:

- `association_type` may be `has_one`, `has_many`, `belongs_to`.
- `association_name` is a method name the serializer calls.
- optional: `options` may be:
  - `key:` The name used for the serialized association.
  - `serializer:`
  - `if:`
  - `unless:`
  - `virtual_value:`
  - `polymorphic:` defines if polymorphic relation type should be nested in serialized association.
  - `type:` the resource type as used by JSON:API, especially on a `belongs_to` relationship.
  - `class_name:` the (String) model name used to determine `type`, when `type` is not given. e.g. `class_name: "Comment"` would imply the type `comments`
  - `foreign_key:` used by JSON:API on a `belongs_to` relationship to avoid unnecessarily loading the association object.
  - `namespace:` used when looking up the serializer and `serializer` is not given.  Falls back to the parent serializer's `:namespace` instance options, which, when present, comes from the render options. See [Rendering#namespace](rendering.md#namespace] for more details.
- optional: `&block` is a context that returns the association's attributes.
  - prevents `association_name` method from being called.
  - return value of block is used as the association value.
  - yields the `serializer` to the block.
  - `include_data false` prevents the `data` key from being rendered in the JSON API relationship.

#### ::has_one

e.g.

```ruby
has_one :bio
has_one :blog, key: :site
has_one :blog, class_name: "Blog"
has_one :maker, virtual_value: { id: 1 }

has_one :blog do |serializer|
  serializer.cached_blog
end

def cached_blog
  cache_store.fetch("cached_blog:#{object.updated_at}") do
    Blog.find(object.blog_id)
  end
end
```

```ruby
has_one :blog, if: :show_blog?
# you can also use a string or lambda
# has_one :blog, if: 'scope.admin?'
# has_one :blog, if: -> (serializer) { serializer.scope.admin? }
# has_one :blog, if: -> { scope.admin? }

def show_blog?
  scope.admin?
end
```

#### ::has_many

e.g.

```ruby
has_many :comments
has_many :comments, key: :reviews
has_many :comments, serializer: CommentPreviewSerializer
has_many :comments, class_name: "Comment"
has_many :reviews, virtual_value: [{ id: 1 }, { id: 2 }]
has_many :comments, key: :last_comments do
  last(1)
end
```

#### ::belongs_to

e.g.

```ruby
belongs_to :author, serializer: AuthorPreviewSerializer
belongs_to :author, key: :writer
belongs_to :author, class_name: "Author"
belongs_to :post
belongs_to :blog
def blog
  Blog.new(id: 999, name: 'Custom blog')
end
```

### Polymorphic Relationships

Polymorphic relationships are serialized by specifying the relationship, like any other association. For example:

```ruby
class PictureSerializer < ActiveModel::Serializer
  has_one :imageable
end
```

You can specify the serializers by [overriding serializer_for](serializers.md#overriding-association-serializer-lookup). For more context about polymorphic relationships, see the [tests](../../test/adapter/polymorphic_test.rb) for each adapter.

### Caching

#### ::cache

e.g.

```ruby
cache key: 'post', expires_in: 0.1, skip_digest: true
cache expires_in: 1.day, skip_digest: true
cache key: 'writer', skip_digest: true
cache only: [:name], skip_digest: true
cache except: [:content], skip_digest: true
cache key: 'blog'
cache only: [:id]
```

#### #cache_key

e.g.

```ruby
# Uses a custom non-time-based cache key
def cache_key
  "#{self.class.name.downcase}/#{self.id}"
end
```

### Other

#### ::type

When using the `:json_api` adapter, the `::type` method defines the JSONAPI [type](http://jsonapi.org/format/#document-resource-object-identification) that will be rendered for this serializer.

When using the `:json` adapter, the `::type` method defines the name of the root element.

It either takes a `String` or `Symbol` as parameter.

Note: This method is useful only when using the `:json_api` or `:json` adapter.

Examples:
```ruby
class UserProfileSerializer < ActiveModel::Serializer
  type 'profile'

  attribute :name
end
class AuthorProfileSerializer < ActiveModel::Serializer
  type :profile

  attribute :name
end
```

With the `:json_api` adapter, the previous serializers would be rendered as:

``` json
{
  "data": {
    "id": "1",
    "type": "profile",
    "attributes": {
      "name": "Julia"
    }
  }
}
```

With the `:json` adapter, the previous serializer would be rendered as:

``` json
{
  "profile": {
    "name": "Julia"
  }
}
```

#### ::link

```ruby
link :self do
  href "https://example.com/link_author/#{object.id}"
end
link(:author) { link_author_url(object) }
link(:link_authors) { link_authors_url }
link :other, 'https://example.com/resource'
link(:posts) { link_author_posts_url(object) }
```

Just like attributes, links also support conditions in options
```ruby
link(:secret, if: :internal?) { object.secret_link }

def internal?
  instance_options[:context] == :internal
end
```

#### #object

The object being serialized.

#### #root

Resource root which is included in `JSON` adapter. As you can see at [Adapters Document](adapters.md), `Attribute` adapter (default) and `JSON API` adapter does not include root at top level.
By default, the resource root comes from the `model_name` of the serialized object's class.

There are several ways to specify root:
* [Overriding the root key](rendering.md#overriding-the-root-key)
* [Setting `type`](serializers.md#type)
* Specifying the `root` option, e.g. `root: 'specific_name'`, during the serializer's initialization:

```ruby
ActiveModelSerializers::SerializableResource.new(foo, root: 'bar')
```

#### #scope

Allows you to include in the serializer access to an external method.

It's intended to provide an authorization context to the serializer, so that
you may e.g. show an admin all comments on a post, else only published comments.

- `scope` is a method on the serializer instance that comes from `options[:scope]`. It may be nil.
- `scope_name` is an option passed to the new serializer (`options[:scope_name]`).  The serializer
  defines a method with that name that calls the `scope`, e.g. `def current_user; scope; end`.
  Note: it does not define the method if the serializer instance responds to it.

That's a lot of words, so here's some examples:

First, let's assume the serializer is instantiated in the controller, since that's the usual scenario.
We'll refer to the serialization context as `controller`.

| options | `Serializer#scope` | method definition |
|-------- | ------------------|--------------------|
| `scope: current_user, scope_name: :current_user` | `current_user` | `Serializer#current_user` calls `controller.current_user`
| `scope: view_context, scope_name: :view_context` | `view_context` | `Serializer#view_context` calls `controller.view_context`

We can take advantage of the scope to customize the objects returned based
on the current user (scope).

For example, we can limit the posts the current user sees to those they created:

```ruby
class PostSerializer < ActiveModel::Serializer
  attributes :id, :title, :body

  # scope comments to those created_by the current user
  has_many :comments do
    object.comments.where(created_by: current_user)
  end
end
```

Whether you write the method as above or as `object.comments.where(created_by: scope)`
is a matter of preference (assuming `scope_name` has been set).

Keep in mind that the scope can be set to any available controller reference. This can be utilized to provide access to any other data scopes or presentation helpers.

##### Controller Authorization Context

In the controller, the scope/scope_name options are equal to
the [`serialization_scope`method](https://github.com/rails-api/active_model_serializers/blob/d02cd30fe55a3ea85e1d351b6e039620903c1871/lib/action_controller/serialization.rb#L13-L20),
which is `:current_user`, by default.

Specifically, the `scope_name` is defaulted to `:current_user`, and may be set as
`serialization_scope :view_context`.  The `scope` is set to `send(scope_name)` when `scope_name` is
present and the controller responds to `scope_name`.

Thus, in a serializer, the controller provides `current_user` as the
current authorization scope when you call `render :json`.

**IMPORTANT**: Since the scope is set at render, you may want to customize it so that `current_user` isn't
called on every request.  This was [also a problem](https://github.com/rails-api/active_model_serializers/pull/1252#issuecomment-159810477)
in [`0.9`](https://github.com/rails-api/active_model_serializers/tree/0-9-stable#customizing-scope).

We can change the scope from `current_user` to `view_context`, which is included in subclasses of `ActionController::Base`.

```diff
class SomeController < ActionController::Base
+  serialization_scope :view_context

  def current_user
    User.new(id: 2, name: 'Bob', admin: true)
  end

  def edit
    user = User.new(id: 1, name: 'Pete')
    render json: user, serializer: AdminUserSerializer, adapter: :json_api
  end
end
```

We could then use the controller method `view_context` in our serializer, like so:

```diff
class AdminUserSerializer < ActiveModel::Serializer
  attributes :id, :name, :can_edit

  def can_edit?
+    view_context.current_user.admin?
  end
end
```

So that when we render the `#edit` action, we'll get

```json
{"data":{"id":"1","type":"users","attributes":{"name":"Pete","can_edit":true}}}
```

Where `can_edit` is `view_context.current_user.admin?` (true).

You can also tell what to set as `serialization_scope` for specific actions.

For example, use `admin_user` only for `Admin::PostSerializer` and `current_user` for rest.

```ruby
class PostsController < ActionController::Base

  before_action only: :edit do
    self.class.serialization_scope :admin_user
  end

  def show
    render json: @post, serializer: PostSerializer
  end

  def edit
    @post.save
    render json: @post, serializer: Admin::PostSerializer
  end

  private

  def admin_user
    User.new(id: 2, name: 'Bob', admin: true)
  end

  def current_user
    User.new(id: 2, name: 'Bob', admin: false)
  end
end
```
Note that any controller reference which provides the desired scope is acceptable, such as another controller method for loading a different resource or reference to helpers. For example, `ActionController::API` does not include `ActionView::ViewContext`, and would need a different reference for passing any helpers into a serializer via `serialization_scope`.

#### #read_attribute_for_serialization(key)

The serialized value for a given key. e.g. `read_attribute_for_serialization(:title) #=> 'Hello World'`

#### #links

Allows you to modify the `links` node. By default, this node will be populated with the attributes set using the [::link](#link) method. Using `links: nil` will remove the `links` node.

```ruby
ActiveModelSerializers::SerializableResource.new(
  @post,
  adapter: :json_api,
  links: {
    self: {
      href: 'http://example.com/posts',
      meta: {
        stuff: 'value'
      }
    }
  }
)
```

#### #json_key

Returns the key used by the adapter as the resource root. See [root](#root) for more information.

## Examples

Given two models, a `Post(title: string, body: text)` and a
`Comment(name: string, body: text, post_id: integer)`, you will have two
serializers:

```ruby
class PostSerializer < ActiveModel::Serializer
  cache key: 'posts', expires_in: 3.hours
  attributes :title, :body

  has_many :comments
end
```

and

```ruby
class CommentSerializer < ActiveModel::Serializer
  attributes :name, :body

  belongs_to :post
end
```

Generally speaking, you, as a user of ActiveModelSerializers, will write (or generate) these
serializer classes.

## More Info

For more information, see [the Serializer class on GitHub](https://github.com/rails-api/active_model_serializers/blob/0-10-stable/lib/active_model/serializer.rb)

## Overriding association methods

To override an association, call `has_many`, `has_one` or `belongs_to` with a block:

```ruby
class PostSerializer < ActiveModel::Serializer
  has_many :comments do
    object.comments.active
  end
end
```

## Overriding attribute methods

To override an attribute, call `attribute` with a block:

```ruby
class PostSerializer < ActiveModel::Serializer
  attribute :body do
    object.body.downcase
  end
end
```

## Overriding association serializer lookup

If you want to define a specific serializer lookup for your associations, you can override
the `ActiveModel::Serializer.serializer_for` method to return a serializer class based on defined conditions.

```ruby
class MySerializer < ActiveModel::Serializer
  def self.serializer_for(model, options)
    return SparseAdminSerializer if model.class.name == 'Admin'
    super
  end

  # the rest of the serializer
end
```