9.2 KiB
Serializers
Given a serializer class:
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, :bodydef 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 :titledef title 'A Different Title' end |
{ title: 'A Different Title' } |
PR please for conditional attributes:)
Associations
The interface for associations is, generically:
association_type(association_name, options, &block)
Where:
association_typemay behas_one,has_many,belongs_to.association_nameis a method name the serializer calls.- optional:
optionsmay be:key:The name used for the serialized association.serializer:if:unless:virtual_value:
- optional:
&blockis a context that returns the association's attributes.- prevents
association_namemethod from being called. - return value of block is used as the association value.
- yields the
serializerto the block. include_data falseprevents thedatakey from being rendered in the JSON API relationship.
- prevents
::has_one
e.g.
has_one :bio
has_one :blog, key: :site
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?
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 :reviews, virtual_value: [{ id: 1 }, { id: 2 }]
has_many :comments, key: :last_comments do
last(1)
end
::belongs_to
e.g.
belongs_to :author, serializer: AuthorPreviewSerializer
belongs_to :author, key: :writer
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:
class PictureSerializer < ActiveModel::Serializer
has_one :imageable
end
For more context, see the tests for each adapter.
Caching
::cache
e.g.
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.
# Uses a custom non-time-based cache key
def cache_key
"#{self.class.name.downcase}/#{self.id}"
end
Other
::type
The ::type method defines the JSONAPI type that will be rendered for this serializer.
It either takes a String or Symbol as parameter.
Note: This method is useful only when using the :json_api adapter.
Examples:
class UserProfileSerializer < ActiveModel::Serializer
type 'profile'
end
class AuthorProfileSerializer < ActiveModel::Serializer
type :profile
end
With the :json_api adapter, the previous serializers would be rendered as:
{
"data": {
"id": "1",
"type": "profile"
}
}
::link
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) }
#object
The object being serialized.
#root
PR please :)
#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.
scopeis a method on the serializer instance that comes fromoptions[:scope]. It may be nil.scope_nameis an option passed to the new serializer (options[:scope_name]). The serializer defines a method with that name that calls thescope, 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:
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).
Controller Authorization Context
In the controller, the scope/scope_name options are equal to
the serialization_scopemethod,
which is :current_user, by default.
Specfically, 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
in 0.9.
We can change the scope from current_user to view_context.
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:
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
{"data":{"id":"1","type":"users","attributes":{"name":"Pete","can_edit":true}}}
Where can_edit is view_context.current_user.admin? (true).
#read_attribute_for_serialization(key)
The serialized value for a given key. e.g. read_attribute_for_serialization(:title) #=> 'Hello World'
#links
PR please :)
#json_key
PR please :)
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:
class PostSerializer < ActiveModel::Serializer
cache key: 'posts', expires_in: 3.hours
attributes :title, :body
has_many :comments
end
and
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
Overriding association methods
To override an association, call has_many, has_one or belongs_to with a block:
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:
class PostSerializer < ActiveModel::Serializer
attribute :body do
object.body.downcase
end
end