Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Now @josevalim can stop to fight with textile #1

Merged
merged 1 commit into from
Dec 1, 2011
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 42 additions & 42 deletions README.textile
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ h3. The Most Basic Serializer

A basic serializer is a simple Ruby object named after the model class it is serializing.

<pre><code>
<pre lang="ruby">
class PostSerializer
def initialize(post, scope)
@post, @scope = post, scope
Expand All @@ -45,22 +45,22 @@ class PostSerializer
{ post: { title: @post.name, body: @post.body } }
end
end
</code></pre>
</pre>

A serializer is initialized with two parameters: the model object it should serialize and an authorization scope. By default, the
authorization scope is the current user (+current_user+) but you can use a different object if you want. The serializer also
implements an +as_json+ method, which returns a Hash that will be sent to the JSON encoder.

Rails will transparently use your serializer when you use +render :json+ in your controller.

<pre><code>
<pre lang="ruby">
class PostsController < ApplicationController
def show
@post = Post.find(params[:id])
render json: @post
end
end
</code></pre>
</pre>

Because +respond_with+ uses +render :json+ under the hood for JSON requests, Rails will automatically use your serializer when
you use +respond_with+ as well.
Expand All @@ -70,7 +70,7 @@ h4. +serializable_hash+
In general, you will want to implement +serializable_hash+ and +as_json+ to allow serializers to embed associated content
directly. The easiest way to implement these two methods is to have +as_json+ call +serializable_hash+ and insert the root.

<pre><code>
<pre lang="ruby">
class PostSerializer
def initialize(post, scope)
@post, @scope = post, scope
Expand All @@ -84,14 +84,14 @@ class PostSerializer
{ post: serializable_hash }
end
end
</code></pre>
</pre>

h4. Authorization

Let's update our serializer to include the email address of the author of the post, but only if the current user has superuser
access.

<pre><code>
<pre lang="ruby">
class PostSerializer
def initialize(post, scope)
@post, @scope = post, scope
Expand Down Expand Up @@ -120,14 +120,14 @@ private
@scope.superuser?
end
end
</code></pre>
</pre>

h4. Testing

One benefit of encapsulating our objects this way is that it becomes extremely straight-forward to test the serialization
logic in isolation.

<pre><code>
<pre lang="ruby">
require "ostruct"

class PostSerializerTest < ActiveSupport::TestCase
Expand Down Expand Up @@ -157,7 +157,7 @@ class PostSerializerTest < ActiveSupport::TestCase
assert_empty hash
end
end
</code></pre>
</pre>

It's important to note that serializer objects define a clear interface specifically for serializing an existing object.
In this case, the serializer expects to receive a post object with +name+, +body+ and +email+ attributes and an authorization
Expand All @@ -168,7 +168,7 @@ the serializer doesn't need to concern itself with how the authorization scope d
whether it is set. In general, you should document these requirements in your serializer files and programatically via tests.
The documentation library +YARD+ provides excellent tools for describing this kind of requirement:

<pre><code>
<pre lang="ruby">
class PostSerializer
# @param [~body, ~title, ~email] post the post to serialize
# @param [~super] scope the authorization scope for this serializer
Expand All @@ -178,7 +178,7 @@ class PostSerializer

# ...
end
</code></pre>
</pre>

h3. Attribute Sugar

Expand All @@ -189,7 +189,7 @@ For example, you will sometimes want to simply include a number of existing attr
JSON. In the above example, the +title+ and +body+ attributes were always included in the JSON. Let's see how to use
+ActiveModel::Serializer+ to simplify our post serializer.

<pre><code>
<pre lang="ruby">
class PostSerializer < ActiveModel::Serializer
attributes :title, :body

Expand All @@ -212,7 +212,7 @@ private
@scope.superuser?
end
end
</code></pre>
</pre>

First, we specified the list of included attributes at the top of the class. This will create an instance method called
+attributes+ that extracts those attributes from the post model.
Expand All @@ -223,7 +223,7 @@ Next, we use the attributes methood in our +serializable_hash+ method, which all
earlier. We could also eliminate the +as_json+ method, as +ActiveModel::Serializer+ provides a default +as_json+ method for
us that calls our +serializable_hash+ method and inserts a root. But we can go a step further!

<pre><code>
<pre lang="ruby">
class PostSerializer < ActiveModel::Serializer
attributes :title, :body

Expand All @@ -238,7 +238,7 @@ private
@scope.superuser?
end
end
</code></pre>
</pre>

The superclass provides a default +initialize+ method as well as a default +serializable_hash+ method, which uses
+attributes+. We can call +super+ to get the hash based on the attributes we declared, and then add in any additional
Expand All @@ -251,7 +251,7 @@ h3. Associations
In most JSON APIs, you will want to include associated objects with your serialized object. In this case, let's include
the comments with the current post.

<pre><code>
<pre lang="ruby">
class PostSerializer < ActiveModel::Serializer
attributes :title, :body
has_many :comments
Expand All @@ -267,11 +267,11 @@ private
@scope.superuser?
end
end
</code></pre>
</pre>

The default +serializable_hash+ method will include the comments as embedded objects inside the post.

<pre><code>
<pre lang="json">
{
post: {
title: "Hello Blog!",
Expand All @@ -284,14 +284,14 @@ The default +serializable_hash+ method will include the comments as embedded obj
]
}
}
</code></pre>
</pre>

Rails uses the same logic to generate embedded serializations as it does when you use +render :json+. In this case,
because you didn't define a +CommentSerializer+, Rails used the default +as_json+ on your comment object.

If you define a serializer, Rails will automatically instantiate it with the existing authorization scope.

<pre><code>
<pre lang="ruby">
class CommentSerializer
def initialize(comment, scope)
@comment, @scope = comment, scope
Expand All @@ -305,26 +305,26 @@ class CommentSerializer
{ comment: serializable_hash }
end
end
</code></pre>
</pre>

If we define the above comment serializer, the outputted JSON will change to:

<pre><code>
<pre lang="json">
{
post: {
title: "Hello Blog!",
body: "This is my first post. Isn't it fabulous!",
comments: [{ title: "Awesome" }]
}
}
</code></pre>
</pre>

Let's imagine that our comment system allows an administrator to kill a comment, and we only want to allow
users to see the comments they're entitled to see. By default, +has_many :comments+ will simply use the
+comments+ accessor on the post object. We can override the +comments+ accessor to limit the comments used
to just the comments we want to allow for the current user.

<pre><code>
<pre lang="ruby">
class PostSerializer < ActiveModel::Serializer
attributes :title. :body
has_many :comments
Expand All @@ -344,7 +344,7 @@ private
@scope.superuser?
end
end
</code></pre>
</pre>

+ActiveModel::Serializer+ will still embed the comments, but this time it will use just the comments
for the current user.
Expand All @@ -359,7 +359,7 @@ build up the hash manually.

For example, let's say our front-end expects the posts and comments in the following format:

<pre><code>
<pre lang="json">
{
post: {
id: 1
Expand All @@ -380,11 +380,11 @@ For example, let's say our front-end expects the posts and comments in the follo
}
]
}
</code></pre>
</pre>

We could achieve this with a custom +as_json+ method. We will also need to define a serializer for comments.

<pre><code>
<pre lang="ruby">
class CommentSerializer < ActiveModel::Serializer
attributes :id, :title, :body

Expand Down Expand Up @@ -420,7 +420,7 @@ private
@scope.superuser?
end
end
</code></pre>
</pre>

Here, we used two convenience methods: +associations+ and +association_ids+. The first,
+associations+, creates a hash of all of the define associations, using their defined
Expand All @@ -442,7 +442,7 @@ For instance, we might want to provide the full comment when it is requested dir
but only its title when requested as part of the post. To achieve this, you can define
a serializer for associated objects nested inside the main serializer.

<pre><code>
<pre lang="ruby">
class PostSerializer < ActiveModel::Serializer
class CommentSerializer < ActiveModel::Serializer
attributes :id, :title
Expand All @@ -451,7 +451,7 @@ class PostSerializer < ActiveModel::Serializer
# same as before
# ...
end
</code></pre>
</pre>

In other words, if a +PostSerializer+ is trying to serialize comments, it will first
look for +PostSerializer::CommentSerializer+ before falling back to +CommentSerializer+
Expand All @@ -468,11 +468,11 @@ its +current_user+ method and pass that along to the serializer's initializer.
If you want to change that behavior, simply use the +serialization_scope+ class
method.

<pre><code>
<pre lang="ruby">
class PostsController < ApplicationController
serialization_scope :current_app
end
</code></pre>
</pre>

You can also implement an instance method called (no surprise) +serialization_scope+,
which allows you to define a dynamic authorization scope based on the current request.
Expand All @@ -489,19 +489,19 @@ outside a request.
For instance, if you want to generate the JSON representation of a post for a user outside
of a request:

<pre><code>
<pre lang="ruby">
user = get_user # some logic to get the user in question
PostSerializer.new(post, user).to_json # reliably generate JSON output
</code></pre>
</pre>

If you want to generate JSON for an anonymous user, you should be able to use whatever
technique you use in your application to generate anonymous users outside of a request.
Typically, that means creating a new user and not saving it to the database:

<pre><code>
<pre lang="ruby">
user = User.new # create a new anonymous user
PostSerializer.new(post, user).to_json
</code></pre>
</pre>

In general, the better you encapsulate your authorization logic, the more easily you
will be able to use the serializer outside of the context of a request. For instance,
Expand All @@ -519,7 +519,7 @@ as the root).

For example, an Array of post objects would serialize as:

<pre><code>
<pre lang="json">
{
posts: [
{
Expand All @@ -531,12 +531,12 @@ For example, an Array of post objects would serialize as:
}
]
}
</code></pre>
</pre>

If you want to change the behavior of serialized Arrays, you need to create
a custom Array serializer.

<pre><code>
<pre lang="ruby">
class ArraySerializer < ActiveModel::ArraySerializer
def serializable_array
serializers.map do |serializer|
Expand All @@ -550,7 +550,7 @@ class ArraySerializer < ActiveModel::ArraySerializer
hash
end
end
</code></pre>
</pre>

When generating embedded associations using the +associations+ helper inside a
regular serializer, it will create a new <code>ArraySerializer</code> with the
Expand Down