Space code.

This commit is contained in:
José Valim
2011-12-01 07:42:08 +01:00
parent d72b66d4c5
commit 8bcb12289a

View File

@@ -36,7 +36,7 @@ h3. The Most Basic Serializer
A basic serializer is a simple Ruby object named after the model class it is serializing.
<ruby>
class PostSerializer
class PostSerializer
def initialize(post, scope)
@post, @scope = post, scope
end
@@ -44,7 +44,7 @@ class PostSerializer
def as_json
{ post: { title: @post.name, body: @post.body } }
end
end
end
</ruby>
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.
<ruby>
class PostsController < ApplicationController
class PostsController < ApplicationController
def show
@post = Post.find(params[:id])
render json: @post
end
end
end
</ruby>
Because +respond_with+ uses +render :json+ under the hood for JSON requests, Rails will automatically use your serializer when
@@ -71,7 +71,7 @@ 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.
<ruby>
class PostSerializer
class PostSerializer
def initialize(post, scope)
@post, @scope = post, scope
end
@@ -83,7 +83,7 @@ class PostSerializer
def as_json
{ post: serializable_hash }
end
end
end
</ruby>
h4. Authorization
@@ -92,7 +92,7 @@ Let's update our serializer to include the email address of the author of the po
access.
<ruby>
class PostSerializer
class PostSerializer
def initialize(post, scope)
@post, @scope = post, scope
end
@@ -107,7 +107,7 @@ class PostSerializer
hash
end
private
private
def post
{ title: @post.name, body: @post.body }
end
@@ -119,7 +119,7 @@ private
def super?
@scope.superuser?
end
end
end
</ruby>
h4. Testing
@@ -128,9 +128,9 @@ One benefit of encapsulating our objects this way is that it becomes extremely s
logic in isolation.
<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
# refactoring if we change that.
plebe = OpenStruct.new(super?: false)
@@ -156,7 +156,7 @@ class PostSerializerTest < ActiveSupport::TestCase
assert_equal post.email, hash.delete("email")
assert_empty hash
end
end
end
</ruby>
It's important to note that serializer objects define a clear interface specifically for serializing an existing object.
@@ -169,7 +169,7 @@ 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:
<ruby>
class PostSerializer
class PostSerializer
# @param [~body, ~title, ~email] post the post to serialize
# @param [~super] scope the authorization scope for this serializer
def initialize(post, scope)
@@ -177,7 +177,7 @@ class PostSerializer
end
# ...
end
end
</ruby>
h3. Attribute Sugar
@@ -190,7 +190,7 @@ JSON. In the above example, the +title+ and +body+ attributes were always includ
+ActiveModel::Serializer+ to simplify our post serializer.
<ruby>
class PostSerializer < ActiveModel::Serializer
class PostSerializer < ActiveModel::Serializer
attributes :title, :body
def initialize(post, scope)
@@ -203,7 +203,7 @@ class PostSerializer < ActiveModel::Serializer
hash
end
private
private
def super_data
{ email: @post.email }
end
@@ -211,7 +211,7 @@ private
def super?
@scope.superuser?
end
end
end
</ruby>
First, we specified the list of included attributes at the top of the class. This will create an instance method called
@@ -224,10 +224,10 @@ 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!
<ruby>
class PostSerializer < ActiveModel::Serializer
class PostSerializer < ActiveModel::Serializer
attributes :title, :body
private
private
def attributes
hash = super
hash.merge!(email: post.email) if super?
@@ -237,7 +237,7 @@ private
def super?
@scope.superuser?
end
end
end
</ruby>
The superclass provides a default +initialize+ method as well as a default +serializable_hash+ method, which uses
@@ -252,11 +252,11 @@ In most JSON APIs, you will want to include associated objects with your seriali
the comments with the current post.
<ruby>
class PostSerializer < ActiveModel::Serializer
class PostSerializer < ActiveModel::Serializer
attributes :title, :body
has_many :comments
private
private
def attributes
hash = super
hash.merge!(email: post.email) if super?
@@ -266,13 +266,13 @@ private
def super?
@scope.superuser?
end
end
end
</ruby>
The default +serializable_hash+ method will include the comments as embedded objects inside the post.
<javascript>
{
{
post: {
title: "Hello Blog!",
body: "This is my first post. Isn't it fabulous!",
@@ -283,7 +283,7 @@ The default +serializable_hash+ method will include the comments as embedded obj
}
]
}
}
}
</javascript>
Rails uses the same logic to generate embedded serializations as it does when you use +render :json+. In this case,
@@ -292,7 +292,7 @@ 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.
<ruby>
class CommentSerializer
class CommentSerializer
def initialize(comment, scope)
@comment, @scope = comment, scope
end
@@ -304,19 +304,19 @@ class CommentSerializer
def as_json
{ comment: serializable_hash }
end
end
end
</ruby>
If we define the above comment serializer, the outputted JSON will change to:
<javascript>
{
{
post: {
title: "Hello Blog!",
body: "This is my first post. Isn't it fabulous!",
comments: [{ title: "Awesome" }]
}
}
}
</javascript>
Let's imagine that our comment system allows an administrator to kill a comment, and we only want to allow
@@ -325,11 +325,11 @@ 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.
<ruby>
class PostSerializer < ActiveModel::Serializer
class PostSerializer < ActiveModel::Serializer
attributes :title. :body
has_many :comments
private
private
def attributes
hash = super
hash.merge!(email: post.email) if super?
@@ -343,7 +343,7 @@ private
def super?
@scope.superuser?
end
end
end
</ruby>
+ActiveModel::Serializer+ will still embed the comments, but this time it will use just the comments
@@ -360,7 +360,7 @@ build up the hash manually.
For example, let's say our front-end expects the posts and comments in the following format:
<plain>
{
{
post: {
id: 1
title: "Hello Blog!",
@@ -379,19 +379,19 @@ For example, let's say our front-end expects the posts and comments in the follo
body: "Why is it so short!"
}
]
}
}
</plain>
We could achieve this with a custom +as_json+ method. We will also need to define a serializer for comments.
<ruby>
class CommentSerializer < ActiveModel::Serializer
class CommentSerializer < ActiveModel::Serializer
attributes :id, :title, :body
# define any logic for dealing with authorization-based attributes here
end
end
class PostSerializer < ActiveModel::Serializer
class PostSerializer < ActiveModel::Serializer
attributes :title, :body
has_many :comments
@@ -405,7 +405,7 @@ class PostSerializer < ActiveModel::Serializer
post_hash
end
private
private
def attributes
hash = super
hash.merge!(email: post.email) if super?
@@ -419,7 +419,7 @@ private
def super?
@scope.superuser?
end
end
end
</ruby>
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.
<ruby>
class PostSerializer < ActiveModel::Serializer
class PostSerializer < ActiveModel::Serializer
class CommentSerializer < ActiveModel::Serializer
attributes :id, :title
end
# same as before
# ...
end
end
</ruby>
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.
<ruby>
class PostsController < ApplicationController
class PostsController < ApplicationController
serialization_scope :current_app
end
end
</ruby>
You can also implement an instance method called (no surprise) +serialization_scope+,
@@ -520,7 +520,7 @@ as the root).
For example, an Array of post objects would serialize as:
<plain>
{
{
posts: [
{
title: "FIRST POST!",
@@ -530,14 +530,14 @@ For example, an Array of post objects would serialize as:
body: "Zomg I made it to my second post"
}
]
}
}
</plain>
If you want to change the behavior of serialized Arrays, you need to create
a custom Array serializer.
<ruby>
class ArraySerializer < ActiveModel::ArraySerializer
class ArraySerializer < ActiveModel::ArraySerializer
def serializable_array
serializers.map do |serializer|
serializer.serializable_hash
@@ -549,7 +549,7 @@ class ArraySerializer < ActiveModel::ArraySerializer
hash.merge!(associations)
hash
end
end
end
</ruby>
When generating embedded associations using the +associations+ helper inside a