mirror of
https://github.com/ditkrg/active_model_serializers.git
synced 2026-01-22 22:06:50 +00:00
Space code.
This commit is contained in:
parent
d72b66d4c5
commit
8bcb12289a
488
README.textile
488
README.textile
@ -36,15 +36,15 @@ h3. The Most Basic Serializer
|
|||||||
A basic serializer is a simple Ruby object named after the model class it is serializing.
|
A basic serializer is a simple Ruby object named after the model class it is serializing.
|
||||||
|
|
||||||
<ruby>
|
<ruby>
|
||||||
class PostSerializer
|
class PostSerializer
|
||||||
def initialize(post, scope)
|
def initialize(post, scope)
|
||||||
@post, @scope = post, scope
|
@post, @scope = post, scope
|
||||||
end
|
end
|
||||||
|
|
||||||
def as_json
|
def as_json
|
||||||
{ post: { title: @post.name, body: @post.body } }
|
{ post: { title: @post.name, body: @post.body } }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
</ruby>
|
</ruby>
|
||||||
|
|
||||||
A serializer is initialized with two parameters: the model object it should serialize and an authorization scope. By default, the
|
A serializer is initialized with two parameters: the model object it should serialize and an authorization scope. By default, the
|
||||||
@ -54,12 +54,12 @@ implements an +as_json+ method, which returns a Hash that will be sent to the JS
|
|||||||
Rails will transparently use your serializer when you use +render :json+ in your controller.
|
Rails will transparently use your serializer when you use +render :json+ in your controller.
|
||||||
|
|
||||||
<ruby>
|
<ruby>
|
||||||
class PostsController < ApplicationController
|
class PostsController < ApplicationController
|
||||||
def show
|
def show
|
||||||
@post = Post.find(params[:id])
|
@post = Post.find(params[:id])
|
||||||
render json: @post
|
render json: @post
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
</ruby>
|
</ruby>
|
||||||
|
|
||||||
Because +respond_with+ uses +render :json+ under the hood for JSON requests, Rails will automatically use your serializer when
|
Because +respond_with+ uses +render :json+ under the hood for JSON requests, Rails will automatically use your serializer when
|
||||||
@ -71,19 +71,19 @@ In general, you will want to implement +serializable_hash+ and +as_json+ to allo
|
|||||||
directly. The easiest way to implement these two methods is to have +as_json+ call +serializable_hash+ and insert the root.
|
directly. The easiest way to implement these two methods is to have +as_json+ call +serializable_hash+ and insert the root.
|
||||||
|
|
||||||
<ruby>
|
<ruby>
|
||||||
class PostSerializer
|
class PostSerializer
|
||||||
def initialize(post, scope)
|
def initialize(post, scope)
|
||||||
@post, @scope = post, scope
|
@post, @scope = post, scope
|
||||||
end
|
end
|
||||||
|
|
||||||
def serializable_hash
|
def serializable_hash
|
||||||
{ title: @post.name, body: @post.body }
|
{ title: @post.name, body: @post.body }
|
||||||
end
|
end
|
||||||
|
|
||||||
def as_json
|
def as_json
|
||||||
{ post: serializable_hash }
|
{ post: serializable_hash }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
</ruby>
|
</ruby>
|
||||||
|
|
||||||
h4. Authorization
|
h4. Authorization
|
||||||
@ -92,34 +92,34 @@ Let's update our serializer to include the email address of the author of the po
|
|||||||
access.
|
access.
|
||||||
|
|
||||||
<ruby>
|
<ruby>
|
||||||
class PostSerializer
|
class PostSerializer
|
||||||
def initialize(post, scope)
|
def initialize(post, scope)
|
||||||
@post, @scope = post, scope
|
@post, @scope = post, scope
|
||||||
end
|
end
|
||||||
|
|
||||||
def as_json
|
def as_json
|
||||||
{ post: serializable_hash }
|
{ post: serializable_hash }
|
||||||
end
|
end
|
||||||
|
|
||||||
def serializable_hash
|
def serializable_hash
|
||||||
hash = post
|
hash = post
|
||||||
hash.merge!(super_data) if super?
|
hash.merge!(super_data) if super?
|
||||||
hash
|
hash
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def post
|
def post
|
||||||
{ title: @post.name, body: @post.body }
|
{ title: @post.name, body: @post.body }
|
||||||
end
|
end
|
||||||
|
|
||||||
def super_data
|
def super_data
|
||||||
{ email: @post.email }
|
{ email: @post.email }
|
||||||
end
|
end
|
||||||
|
|
||||||
def super?
|
def super?
|
||||||
@scope.superuser?
|
@scope.superuser?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
</ruby>
|
</ruby>
|
||||||
|
|
||||||
h4. Testing
|
h4. Testing
|
||||||
@ -128,35 +128,35 @@ One benefit of encapsulating our objects this way is that it becomes extremely s
|
|||||||
logic in isolation.
|
logic in isolation.
|
||||||
|
|
||||||
<ruby>
|
<ruby>
|
||||||
require "ostruct"
|
require "ostruct"
|
||||||
|
|
||||||
class PostSerializerTest < ActiveSupport::TestCase
|
class PostSerializerTest < ActiveSupport::TestCase
|
||||||
# For now, we use a very simple authorization structure. These tests will need
|
# For now, we use a very simple authorization structure. These tests will need
|
||||||
# refactoring if we change that.
|
# refactoring if we change that.
|
||||||
plebe = OpenStruct.new(super?: false)
|
plebe = OpenStruct.new(super?: false)
|
||||||
god = OpenStruct.new(super?: true)
|
god = OpenStruct.new(super?: true)
|
||||||
|
|
||||||
post = OpenStruct.new(title: "Welcome to my blog!", body: "Blah blah blah", email: "tenderlove@gmail.com")
|
post = OpenStruct.new(title: "Welcome to my blog!", body: "Blah blah blah", email: "tenderlove@gmail.com")
|
||||||
|
|
||||||
test "a regular user sees just the title and body" do
|
test "a regular user sees just the title and body" do
|
||||||
json = PostSerializer.new(post, plebe).to_json
|
json = PostSerializer.new(post, plebe).to_json
|
||||||
hash = JSON.parse(json)
|
hash = JSON.parse(json)
|
||||||
|
|
||||||
assert_equal post.title, hash.delete("title")
|
assert_equal post.title, hash.delete("title")
|
||||||
assert_equal post.body, hash.delete("body")
|
assert_equal post.body, hash.delete("body")
|
||||||
assert_empty hash
|
assert_empty hash
|
||||||
end
|
end
|
||||||
|
|
||||||
test "a superuser sees the title, body and email" do
|
test "a superuser sees the title, body and email" do
|
||||||
json = PostSerializer.new(post, god).to_json
|
json = PostSerializer.new(post, god).to_json
|
||||||
hash = JSON.parse(json)
|
hash = JSON.parse(json)
|
||||||
|
|
||||||
assert_equal post.title, hash.delete("title")
|
assert_equal post.title, hash.delete("title")
|
||||||
assert_equal post.body, hash.delete("body")
|
assert_equal post.body, hash.delete("body")
|
||||||
assert_equal post.email, hash.delete("email")
|
assert_equal post.email, hash.delete("email")
|
||||||
assert_empty hash
|
assert_empty hash
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
</ruby>
|
</ruby>
|
||||||
|
|
||||||
It's important to note that serializer objects define a clear interface specifically for serializing an existing object.
|
It's important to note that serializer objects define a clear interface specifically for serializing an existing object.
|
||||||
@ -169,15 +169,15 @@ whether it is set. In general, you should document these requirements in your se
|
|||||||
The documentation library +YARD+ provides excellent tools for describing this kind of requirement:
|
The documentation library +YARD+ provides excellent tools for describing this kind of requirement:
|
||||||
|
|
||||||
<ruby>
|
<ruby>
|
||||||
class PostSerializer
|
class PostSerializer
|
||||||
# @param [~body, ~title, ~email] post the post to serialize
|
# @param [~body, ~title, ~email] post the post to serialize
|
||||||
# @param [~super] scope the authorization scope for this serializer
|
# @param [~super] scope the authorization scope for this serializer
|
||||||
def initialize(post, scope)
|
def initialize(post, scope)
|
||||||
@post, @scope = post, scope
|
@post, @scope = post, scope
|
||||||
end
|
end
|
||||||
|
|
||||||
# ...
|
# ...
|
||||||
end
|
end
|
||||||
</ruby>
|
</ruby>
|
||||||
|
|
||||||
h3. Attribute Sugar
|
h3. Attribute Sugar
|
||||||
@ -190,28 +190,28 @@ JSON. In the above example, the +title+ and +body+ attributes were always includ
|
|||||||
+ActiveModel::Serializer+ to simplify our post serializer.
|
+ActiveModel::Serializer+ to simplify our post serializer.
|
||||||
|
|
||||||
<ruby>
|
<ruby>
|
||||||
class PostSerializer < ActiveModel::Serializer
|
class PostSerializer < ActiveModel::Serializer
|
||||||
attributes :title, :body
|
attributes :title, :body
|
||||||
|
|
||||||
def initialize(post, scope)
|
def initialize(post, scope)
|
||||||
@post, @scope = post, scope
|
@post, @scope = post, scope
|
||||||
end
|
end
|
||||||
|
|
||||||
def serializable_hash
|
def serializable_hash
|
||||||
hash = attributes
|
hash = attributes
|
||||||
hash.merge!(super_data) if super?
|
hash.merge!(super_data) if super?
|
||||||
hash
|
hash
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def super_data
|
def super_data
|
||||||
{ email: @post.email }
|
{ email: @post.email }
|
||||||
end
|
end
|
||||||
|
|
||||||
def super?
|
def super?
|
||||||
@scope.superuser?
|
@scope.superuser?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
</ruby>
|
</ruby>
|
||||||
|
|
||||||
First, we specified the list of included attributes at the top of the class. This will create an instance method called
|
First, we specified the list of included attributes at the top of the class. This will create an instance method called
|
||||||
@ -224,20 +224,20 @@ earlier. We could also eliminate the +as_json+ method, as +ActiveModel::Serializ
|
|||||||
us that calls our +serializable_hash+ method and inserts a root. But we can go a step further!
|
us that calls our +serializable_hash+ method and inserts a root. But we can go a step further!
|
||||||
|
|
||||||
<ruby>
|
<ruby>
|
||||||
class PostSerializer < ActiveModel::Serializer
|
class PostSerializer < ActiveModel::Serializer
|
||||||
attributes :title, :body
|
attributes :title, :body
|
||||||
|
|
||||||
private
|
private
|
||||||
def attributes
|
def attributes
|
||||||
hash = super
|
hash = super
|
||||||
hash.merge!(email: post.email) if super?
|
hash.merge!(email: post.email) if super?
|
||||||
hash
|
hash
|
||||||
end
|
end
|
||||||
|
|
||||||
def super?
|
def super?
|
||||||
@scope.superuser?
|
@scope.superuser?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
</ruby>
|
</ruby>
|
||||||
|
|
||||||
The superclass provides a default +initialize+ method as well as a default +serializable_hash+ method, which uses
|
The superclass provides a default +initialize+ method as well as a default +serializable_hash+ method, which uses
|
||||||
@ -252,38 +252,38 @@ In most JSON APIs, you will want to include associated objects with your seriali
|
|||||||
the comments with the current post.
|
the comments with the current post.
|
||||||
|
|
||||||
<ruby>
|
<ruby>
|
||||||
class PostSerializer < ActiveModel::Serializer
|
class PostSerializer < ActiveModel::Serializer
|
||||||
attributes :title, :body
|
attributes :title, :body
|
||||||
has_many :comments
|
has_many :comments
|
||||||
|
|
||||||
private
|
private
|
||||||
def attributes
|
def attributes
|
||||||
hash = super
|
hash = super
|
||||||
hash.merge!(email: post.email) if super?
|
hash.merge!(email: post.email) if super?
|
||||||
hash
|
hash
|
||||||
end
|
end
|
||||||
|
|
||||||
def super?
|
def super?
|
||||||
@scope.superuser?
|
@scope.superuser?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
</ruby>
|
</ruby>
|
||||||
|
|
||||||
The default +serializable_hash+ method will include the comments as embedded objects inside the post.
|
The default +serializable_hash+ method will include the comments as embedded objects inside the post.
|
||||||
|
|
||||||
<javascript>
|
<javascript>
|
||||||
{
|
{
|
||||||
post: {
|
post: {
|
||||||
title: "Hello Blog!",
|
title: "Hello Blog!",
|
||||||
body: "This is my first post. Isn't it fabulous!",
|
body: "This is my first post. Isn't it fabulous!",
|
||||||
comments: [
|
comments: [
|
||||||
{
|
{
|
||||||
title: "Awesome",
|
title: "Awesome",
|
||||||
body: "Your first post is great"
|
body: "Your first post is great"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
</javascript>
|
</javascript>
|
||||||
|
|
||||||
Rails uses the same logic to generate embedded serializations as it does when you use +render :json+. In this case,
|
Rails uses the same logic to generate embedded serializations as it does when you use +render :json+. In this case,
|
||||||
@ -292,31 +292,31 @@ because you didn't define a +CommentSerializer+, Rails used the default +as_json
|
|||||||
If you define a serializer, Rails will automatically instantiate it with the existing authorization scope.
|
If you define a serializer, Rails will automatically instantiate it with the existing authorization scope.
|
||||||
|
|
||||||
<ruby>
|
<ruby>
|
||||||
class CommentSerializer
|
class CommentSerializer
|
||||||
def initialize(comment, scope)
|
def initialize(comment, scope)
|
||||||
@comment, @scope = comment, scope
|
@comment, @scope = comment, scope
|
||||||
end
|
end
|
||||||
|
|
||||||
def serializable_hash
|
def serializable_hash
|
||||||
{ title: @comment.title }
|
{ title: @comment.title }
|
||||||
end
|
end
|
||||||
|
|
||||||
def as_json
|
def as_json
|
||||||
{ comment: serializable_hash }
|
{ comment: serializable_hash }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
</ruby>
|
</ruby>
|
||||||
|
|
||||||
If we define the above comment serializer, the outputted JSON will change to:
|
If we define the above comment serializer, the outputted JSON will change to:
|
||||||
|
|
||||||
<javascript>
|
<javascript>
|
||||||
{
|
{
|
||||||
post: {
|
post: {
|
||||||
title: "Hello Blog!",
|
title: "Hello Blog!",
|
||||||
body: "This is my first post. Isn't it fabulous!",
|
body: "This is my first post. Isn't it fabulous!",
|
||||||
comments: [{ title: "Awesome" }]
|
comments: [{ title: "Awesome" }]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</javascript>
|
</javascript>
|
||||||
|
|
||||||
Let's imagine that our comment system allows an administrator to kill a comment, and we only want to allow
|
Let's imagine that our comment system allows an administrator to kill a comment, and we only want to allow
|
||||||
@ -325,25 +325,25 @@ users to see the comments they're entitled to see. By default, +has_many :commen
|
|||||||
to just the comments we want to allow for the current user.
|
to just the comments we want to allow for the current user.
|
||||||
|
|
||||||
<ruby>
|
<ruby>
|
||||||
class PostSerializer < ActiveModel::Serializer
|
class PostSerializer < ActiveModel::Serializer
|
||||||
attributes :title. :body
|
attributes :title. :body
|
||||||
has_many :comments
|
has_many :comments
|
||||||
|
|
||||||
private
|
private
|
||||||
def attributes
|
def attributes
|
||||||
hash = super
|
hash = super
|
||||||
hash.merge!(email: post.email) if super?
|
hash.merge!(email: post.email) if super?
|
||||||
hash
|
hash
|
||||||
end
|
end
|
||||||
|
|
||||||
def comments
|
def comments
|
||||||
post.comments_for(scope)
|
post.comments_for(scope)
|
||||||
end
|
end
|
||||||
|
|
||||||
def super?
|
def super?
|
||||||
@scope.superuser?
|
@scope.superuser?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
</ruby>
|
</ruby>
|
||||||
|
|
||||||
+ActiveModel::Serializer+ will still embed the comments, but this time it will use just the comments
|
+ActiveModel::Serializer+ will still embed the comments, but this time it will use just the comments
|
||||||
@ -360,66 +360,66 @@ build up the hash manually.
|
|||||||
For example, let's say our front-end expects the posts and comments in the following format:
|
For example, let's say our front-end expects the posts and comments in the following format:
|
||||||
|
|
||||||
<plain>
|
<plain>
|
||||||
{
|
|
||||||
post: {
|
|
||||||
id: 1
|
|
||||||
title: "Hello Blog!",
|
|
||||||
body: "This is my first post. Isn't it fabulous!",
|
|
||||||
comments: [1,2]
|
|
||||||
},
|
|
||||||
comments: [
|
|
||||||
{
|
{
|
||||||
id: 1
|
post: {
|
||||||
title: "Awesome",
|
id: 1
|
||||||
body: "Your first post is great"
|
title: "Hello Blog!",
|
||||||
},
|
body: "This is my first post. Isn't it fabulous!",
|
||||||
{
|
comments: [1,2]
|
||||||
id: 2
|
},
|
||||||
title: "Not so awesome",
|
comments: [
|
||||||
body: "Why is it so short!"
|
{
|
||||||
|
id: 1
|
||||||
|
title: "Awesome",
|
||||||
|
body: "Your first post is great"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2
|
||||||
|
title: "Not so awesome",
|
||||||
|
body: "Why is it so short!"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
|
||||||
}
|
|
||||||
</plain>
|
</plain>
|
||||||
|
|
||||||
We could achieve this with a custom +as_json+ method. We will also need to define a serializer for comments.
|
We could achieve this with a custom +as_json+ method. We will also need to define a serializer for comments.
|
||||||
|
|
||||||
<ruby>
|
<ruby>
|
||||||
class CommentSerializer < ActiveModel::Serializer
|
class CommentSerializer < ActiveModel::Serializer
|
||||||
attributes :id, :title, :body
|
attributes :id, :title, :body
|
||||||
|
|
||||||
# define any logic for dealing with authorization-based attributes here
|
# define any logic for dealing with authorization-based attributes here
|
||||||
end
|
end
|
||||||
|
|
||||||
class PostSerializer < ActiveModel::Serializer
|
class PostSerializer < ActiveModel::Serializer
|
||||||
attributes :title, :body
|
attributes :title, :body
|
||||||
has_many :comments
|
has_many :comments
|
||||||
|
|
||||||
def as_json
|
def as_json
|
||||||
{ post: serializable_hash }.merge!(associations)
|
{ post: serializable_hash }.merge!(associations)
|
||||||
end
|
end
|
||||||
|
|
||||||
def serializable_hash
|
def serializable_hash
|
||||||
post_hash = attributes
|
post_hash = attributes
|
||||||
post_hash.merge!(association_ids)
|
post_hash.merge!(association_ids)
|
||||||
post_hash
|
post_hash
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def attributes
|
def attributes
|
||||||
hash = super
|
hash = super
|
||||||
hash.merge!(email: post.email) if super?
|
hash.merge!(email: post.email) if super?
|
||||||
hash
|
hash
|
||||||
end
|
end
|
||||||
|
|
||||||
def comments
|
def comments
|
||||||
post.comments_for(scope)
|
post.comments_for(scope)
|
||||||
end
|
end
|
||||||
|
|
||||||
def super?
|
def super?
|
||||||
@scope.superuser?
|
@scope.superuser?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
</ruby>
|
</ruby>
|
||||||
|
|
||||||
Here, we used two convenience methods: +associations+ and +association_ids+. The first,
|
Here, we used two convenience methods: +associations+ and +association_ids+. The first,
|
||||||
@ -443,14 +443,14 @@ but only its title when requested as part of the post. To achieve this, you can
|
|||||||
a serializer for associated objects nested inside the main serializer.
|
a serializer for associated objects nested inside the main serializer.
|
||||||
|
|
||||||
<ruby>
|
<ruby>
|
||||||
class PostSerializer < ActiveModel::Serializer
|
class PostSerializer < ActiveModel::Serializer
|
||||||
class CommentSerializer < ActiveModel::Serializer
|
class CommentSerializer < ActiveModel::Serializer
|
||||||
attributes :id, :title
|
attributes :id, :title
|
||||||
end
|
end
|
||||||
|
|
||||||
# same as before
|
# same as before
|
||||||
# ...
|
# ...
|
||||||
end
|
end
|
||||||
</ruby>
|
</ruby>
|
||||||
|
|
||||||
In other words, if a +PostSerializer+ is trying to serialize comments, it will first
|
In other words, if a +PostSerializer+ is trying to serialize comments, it will first
|
||||||
@ -469,9 +469,9 @@ If you want to change that behavior, simply use the +serialization_scope+ class
|
|||||||
method.
|
method.
|
||||||
|
|
||||||
<ruby>
|
<ruby>
|
||||||
class PostsController < ApplicationController
|
class PostsController < ApplicationController
|
||||||
serialization_scope :current_app
|
serialization_scope :current_app
|
||||||
end
|
end
|
||||||
</ruby>
|
</ruby>
|
||||||
|
|
||||||
You can also implement an instance method called (no surprise) +serialization_scope+,
|
You can also implement an instance method called (no surprise) +serialization_scope+,
|
||||||
@ -520,36 +520,36 @@ as the root).
|
|||||||
For example, an Array of post objects would serialize as:
|
For example, an Array of post objects would serialize as:
|
||||||
|
|
||||||
<plain>
|
<plain>
|
||||||
{
|
|
||||||
posts: [
|
|
||||||
{
|
{
|
||||||
title: "FIRST POST!",
|
posts: [
|
||||||
body: "It's my first pooooost"
|
{
|
||||||
},
|
title: "FIRST POST!",
|
||||||
{ title: "Second post!",
|
body: "It's my first pooooost"
|
||||||
body: "Zomg I made it to my second post"
|
},
|
||||||
|
{ title: "Second post!",
|
||||||
|
body: "Zomg I made it to my second post"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
|
||||||
}
|
|
||||||
</plain>
|
</plain>
|
||||||
|
|
||||||
If you want to change the behavior of serialized Arrays, you need to create
|
If you want to change the behavior of serialized Arrays, you need to create
|
||||||
a custom Array serializer.
|
a custom Array serializer.
|
||||||
|
|
||||||
<ruby>
|
<ruby>
|
||||||
class ArraySerializer < ActiveModel::ArraySerializer
|
class ArraySerializer < ActiveModel::ArraySerializer
|
||||||
def serializable_array
|
def serializable_array
|
||||||
serializers.map do |serializer|
|
serializers.map do |serializer|
|
||||||
serializer.serializable_hash
|
serializer.serializable_hash
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def as_json
|
def as_json
|
||||||
hash = { root => serializable_array }
|
hash = { root => serializable_array }
|
||||||
hash.merge!(associations)
|
hash.merge!(associations)
|
||||||
hash
|
hash
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
</ruby>
|
</ruby>
|
||||||
|
|
||||||
When generating embedded associations using the +associations+ helper inside a
|
When generating embedded associations using the +associations+ helper inside a
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user