diff --git a/README.textile b/README.textile
index e376227b..0fd02ebc 100644
--- a/README.textile
+++ b/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.
-class PostSerializer
- def initialize(post, scope)
- @post, @scope = post, scope
- end
+ class PostSerializer
+ def initialize(post, scope)
+ @post, @scope = post, scope
+ end
- def as_json
- { post: { title: @post.name, body: @post.body } }
- end
-end
+ def as_json
+ { post: { title: @post.name, body: @post.body } }
+ end
+ end
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.
-class PostsController < ApplicationController
- def show
- @post = Post.find(params[:id])
- render json: @post
- end
-end
+ class PostsController < ApplicationController
+ def show
+ @post = Post.find(params[:id])
+ render json: @post
+ end
+ end
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.
-class PostSerializer
- def initialize(post, scope)
- @post, @scope = post, scope
- end
+ class PostSerializer
+ def initialize(post, scope)
+ @post, @scope = post, scope
+ end
- def serializable_hash
- { title: @post.name, body: @post.body }
- end
+ def serializable_hash
+ { title: @post.name, body: @post.body }
+ end
- def as_json
- { post: serializable_hash }
- end
-end
+ def as_json
+ { post: serializable_hash }
+ end
+ end
h4. Authorization
@@ -92,34 +92,34 @@ Let's update our serializer to include the email address of the author of the po
access.
-class PostSerializer
- def initialize(post, scope)
- @post, @scope = post, scope
- end
+ class PostSerializer
+ def initialize(post, scope)
+ @post, @scope = post, scope
+ end
- def as_json
- { post: serializable_hash }
- end
+ def as_json
+ { post: serializable_hash }
+ end
- def serializable_hash
- hash = post
- hash.merge!(super_data) if super?
- hash
- end
+ def serializable_hash
+ hash = post
+ hash.merge!(super_data) if super?
+ hash
+ end
-private
- def post
- { title: @post.name, body: @post.body }
- end
+ private
+ def post
+ { title: @post.name, body: @post.body }
+ end
- def super_data
- { email: @post.email }
- end
+ def super_data
+ { email: @post.email }
+ end
- def super?
- @scope.superuser?
- end
-end
+ def super?
+ @scope.superuser?
+ end
+ end
h4. Testing
@@ -128,35 +128,35 @@ One benefit of encapsulating our objects this way is that it becomes extremely s
logic in isolation.
-require "ostruct"
+ require "ostruct"
-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)
- god = OpenStruct.new(super?: true)
+ 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)
+ 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
- json = PostSerializer.new(post, plebe).to_json
- hash = JSON.parse(json)
+ test "a regular user sees just the title and body" do
+ json = PostSerializer.new(post, plebe).to_json
+ hash = JSON.parse(json)
- assert_equal post.title, hash.delete("title")
- assert_equal post.body, hash.delete("body")
- assert_empty hash
- end
+ assert_equal post.title, hash.delete("title")
+ assert_equal post.body, hash.delete("body")
+ assert_empty hash
+ end
- test "a superuser sees the title, body and email" do
- json = PostSerializer.new(post, god).to_json
- hash = JSON.parse(json)
+ test "a superuser sees the title, body and email" do
+ json = PostSerializer.new(post, god).to_json
+ hash = JSON.parse(json)
- assert_equal post.title, hash.delete("title")
- assert_equal post.body, hash.delete("body")
- assert_equal post.email, hash.delete("email")
- assert_empty hash
- end
-end
+ assert_equal post.title, hash.delete("title")
+ assert_equal post.body, hash.delete("body")
+ assert_equal post.email, hash.delete("email")
+ assert_empty hash
+ end
+ end
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:
-class PostSerializer
- # @param [~body, ~title, ~email] post the post to serialize
- # @param [~super] scope the authorization scope for this serializer
- def initialize(post, scope)
- @post, @scope = post, scope
- end
+ class PostSerializer
+ # @param [~body, ~title, ~email] post the post to serialize
+ # @param [~super] scope the authorization scope for this serializer
+ def initialize(post, scope)
+ @post, @scope = post, scope
+ end
- # ...
-end
+ # ...
+ end
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.
-class PostSerializer < ActiveModel::Serializer
- attributes :title, :body
+ class PostSerializer < ActiveModel::Serializer
+ attributes :title, :body
- def initialize(post, scope)
- @post, @scope = post, scope
- end
+ def initialize(post, scope)
+ @post, @scope = post, scope
+ end
- def serializable_hash
- hash = attributes
- hash.merge!(super_data) if super?
- hash
- end
+ def serializable_hash
+ hash = attributes
+ hash.merge!(super_data) if super?
+ hash
+ end
-private
- def super_data
- { email: @post.email }
- end
+ private
+ def super_data
+ { email: @post.email }
+ end
- def super?
- @scope.superuser?
- end
-end
+ def super?
+ @scope.superuser?
+ end
+ end
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!
-class PostSerializer < ActiveModel::Serializer
- attributes :title, :body
+ class PostSerializer < ActiveModel::Serializer
+ attributes :title, :body
-private
- def attributes
- hash = super
- hash.merge!(email: post.email) if super?
- hash
- end
+ private
+ def attributes
+ hash = super
+ hash.merge!(email: post.email) if super?
+ hash
+ end
- def super?
- @scope.superuser?
- end
-end
+ def super?
+ @scope.superuser?
+ end
+ end
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.
-class PostSerializer < ActiveModel::Serializer
- attributes :title, :body
- has_many :comments
+ class PostSerializer < ActiveModel::Serializer
+ attributes :title, :body
+ has_many :comments
-private
- def attributes
- hash = super
- hash.merge!(email: post.email) if super?
- hash
- end
+ private
+ def attributes
+ hash = super
+ hash.merge!(email: post.email) if super?
+ hash
+ end
- def super?
- @scope.superuser?
- end
-end
+ def super?
+ @scope.superuser?
+ end
+ end
The default +serializable_hash+ method will include the comments as embedded objects inside the post.
-{
- post: {
- title: "Hello Blog!",
- body: "This is my first post. Isn't it fabulous!",
- comments: [
- {
- title: "Awesome",
- body: "Your first post is great"
+ {
+ post: {
+ title: "Hello Blog!",
+ body: "This is my first post. Isn't it fabulous!",
+ comments: [
+ {
+ title: "Awesome",
+ body: "Your first post is great"
+ }
+ ]
}
- ]
- }
-}
+ }
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.
-class CommentSerializer
- def initialize(comment, scope)
- @comment, @scope = comment, scope
- end
+ class CommentSerializer
+ def initialize(comment, scope)
+ @comment, @scope = comment, scope
+ end
- def serializable_hash
- { title: @comment.title }
- end
+ def serializable_hash
+ { title: @comment.title }
+ end
- def as_json
- { comment: serializable_hash }
- end
-end
+ def as_json
+ { comment: serializable_hash }
+ end
+ end
If we define the above comment serializer, the outputted JSON will change to:
-{
- post: {
- title: "Hello Blog!",
- body: "This is my first post. Isn't it fabulous!",
- comments: [{ title: "Awesome" }]
- }
-}
+ {
+ post: {
+ title: "Hello Blog!",
+ body: "This is my first post. Isn't it fabulous!",
+ comments: [{ title: "Awesome" }]
+ }
+ }
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.
-class PostSerializer < ActiveModel::Serializer
- attributes :title. :body
- has_many :comments
+ class PostSerializer < ActiveModel::Serializer
+ attributes :title. :body
+ has_many :comments
-private
- def attributes
- hash = super
- hash.merge!(email: post.email) if super?
- hash
- end
+ private
+ def attributes
+ hash = super
+ hash.merge!(email: post.email) if super?
+ hash
+ end
- def comments
- post.comments_for(scope)
- end
+ def comments
+ post.comments_for(scope)
+ end
- def super?
- @scope.superuser?
- end
-end
+ def super?
+ @scope.superuser?
+ end
+ end
+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:
-{
- post: {
- id: 1
- title: "Hello Blog!",
- body: "This is my first post. Isn't it fabulous!",
- comments: [1,2]
- },
- comments: [
{
- id: 1
- title: "Awesome",
- body: "Your first post is great"
- },
- {
- id: 2
- title: "Not so awesome",
- body: "Why is it so short!"
+ post: {
+ id: 1
+ title: "Hello Blog!",
+ body: "This is my first post. Isn't it fabulous!",
+ comments: [1,2]
+ },
+ comments: [
+ {
+ id: 1
+ title: "Awesome",
+ body: "Your first post is great"
+ },
+ {
+ id: 2
+ title: "Not so awesome",
+ body: "Why is it so short!"
+ }
+ ]
}
- ]
-}
We could achieve this with a custom +as_json+ method. We will also need to define a serializer for comments.
-class CommentSerializer < ActiveModel::Serializer
- attributes :id, :title, :body
+ class CommentSerializer < ActiveModel::Serializer
+ attributes :id, :title, :body
- # define any logic for dealing with authorization-based attributes here
-end
+ # define any logic for dealing with authorization-based attributes here
+ end
-class PostSerializer < ActiveModel::Serializer
- attributes :title, :body
- has_many :comments
+ class PostSerializer < ActiveModel::Serializer
+ attributes :title, :body
+ has_many :comments
- def as_json
- { post: serializable_hash }.merge!(associations)
- end
+ def as_json
+ { post: serializable_hash }.merge!(associations)
+ end
- def serializable_hash
- post_hash = attributes
- post_hash.merge!(association_ids)
- post_hash
- end
+ def serializable_hash
+ post_hash = attributes
+ post_hash.merge!(association_ids)
+ post_hash
+ end
-private
- def attributes
- hash = super
- hash.merge!(email: post.email) if super?
- hash
- end
+ private
+ def attributes
+ hash = super
+ hash.merge!(email: post.email) if super?
+ hash
+ end
- def comments
- post.comments_for(scope)
- end
+ def comments
+ post.comments_for(scope)
+ end
- def super?
- @scope.superuser?
- end
-end
+ def super?
+ @scope.superuser?
+ end
+ end
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.
-class PostSerializer < ActiveModel::Serializer
- class CommentSerializer < ActiveModel::Serializer
- attributes :id, :title
- end
+ class PostSerializer < ActiveModel::Serializer
+ class CommentSerializer < ActiveModel::Serializer
+ attributes :id, :title
+ end
- # same as before
- # ...
-end
+ # same as before
+ # ...
+ end
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.
-class PostsController < ApplicationController
- serialization_scope :current_app
-end
+ class PostsController < ApplicationController
+ serialization_scope :current_app
+ end
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:
-{
- posts: [
{
- title: "FIRST POST!",
- body: "It's my first pooooost"
- },
- { title: "Second post!",
- body: "Zomg I made it to my second post"
+ posts: [
+ {
+ title: "FIRST POST!",
+ body: "It's my first pooooost"
+ },
+ { title: "Second post!",
+ body: "Zomg I made it to my second post"
+ }
+ ]
}
- ]
-}
If you want to change the behavior of serialized Arrays, you need to create
a custom Array serializer.
-class ArraySerializer < ActiveModel::ArraySerializer
- def serializable_array
- serializers.map do |serializer|
- serializer.serializable_hash
- end
- end
+ class ArraySerializer < ActiveModel::ArraySerializer
+ def serializable_array
+ serializers.map do |serializer|
+ serializer.serializable_hash
+ end
+ end
- def as_json
- hash = { root => serializable_array }
- hash.merge!(associations)
- hash
- end
-end
+ def as_json
+ hash = { root => serializable_array }
+ hash.merge!(associations)
+ hash
+ end
+ end
When generating embedded associations using the +associations+ helper inside a