README.md in active_model_serializers-0.8.4 vs README.md in active_model_serializers-0.9.0.alpha1
- old
+ new
@@ -1,35 +1,55 @@
-[![Build Status](https://api.travis-ci.org/rails-api/active_model_serializers.png)](https://travis-ci.org/rails-api/active_model_serializers) [![Code Climate](https://codeclimate.com/github/rails-api/active_model_serializers.png)](https://codeclimate.com/github/rails-api/active_model_serializers)
+[![Build Status](https://api.travis-ci.org/rails-api/active_model_serializers.png)](https://travis-ci.org/rails-api/active_model_serializers)
+[![Code Climate](https://codeclimate.com/github/rails-api/active_model_serializers.png)](https://codeclimate.com/github/rails-api/active_model_serializers)
+[![Coverage Status](https://coveralls.io/repos/rails-api/active_model_serializers/badge.png?branch=master)](https://coveralls.io/r/rails-api/active_model_serializers)
-# Purpose
+# ActiveModel::Serializers
-The purpose of `ActiveModel::Serializers` is to provide an object to
-encapsulate serialization of `ActiveModel` objects, including `ActiveRecord`
-objects.
+## Master - 0.9.0
+**master is under development, there are some incompatible changes with the current stable release.**
+
+If you want to read the stable documentation visit [0.8 README](https://github.com/rails-api/active_model_serializers/blob/0-8-stable/README.md)
+
+## Purpose
+
+`ActiveModel::Serializers` encapsulates the JSON serialization of objects.
+Objects that respond to read\_attribute\_for\_serialization
+(including `ActiveModel` and `ActiveRecord` objects) are supported.
+
Serializers know about both a model and the `current_user`, so you can
customize serialization based upon whether a user is authorized to see the
content.
In short, **serializers replace hash-driven development with object-oriented
development.**
-# Installing Serializers
+# Installing
The easiest way to install `ActiveModel::Serializers` is to add it to your
`Gemfile`:
```ruby
-gem "active_model_serializers", "~> 0.8.0"
+gem "active_model_serializers"
```
Then, install it on the command line:
```
$ bundle install
```
+#### Ruby 1.8 is no longer supported!
+
+If you must use a ruby 1.8 version (MRI 1.8.7, REE, Rubinius 1.8, or JRuby 1.8), you need to use version 0.8.x.
+Versions after 0.9.0 do not support ruby 1.8. To specify version 0.8, include this in your Gemfile:
+
+```ruby
+gem "active_model_serializers", "~> 0.8.0"
+```
+
+
# Creating a Serializer
The easiest way to create a new serializer is to generate a new resource, which
will generate a serializer at the same time:
@@ -43,39 +63,16 @@
```
$ rails g serializer post
```
-### Support for POROs and other ORMs.
+### Support for POROs
-Currently `ActiveModel::Serializers` adds serialization support to all models
-that descend from `ActiveRecord` or include `Mongoid::Document`. If you are:
+Currently `ActiveModel::Serializers` expects objects to implement
+read\_attribute\_for\_serialization. That's all you need to do to have
+your POROs supported.
-- using another ORM, or
-- using objects that are `ActiveModel` compliant but do not descend from
-`ActiveRecord` *or* include `Mongoid::Document`
-
-You must add an include statement for `ActiveModel::SerializerSupport` to
-make models serializable.
-
-If you also want to make collections serializable, you should include
-`ActiveModel::ArraySerializerSupport` into your ORM's
-relation/criteria class.
-
-Example model (`app/models/avatar.rb`):
-
-```ruby
-class Avatar
- include ActiveModel::SerializerSupport
- # etc, etc
-end
-```
-
-If your classes follow the naming conventions prescribed by `ActiveModel`,
-you don't need to do anything different in your controller to render the
-serialized json.
-
# ActiveModel::Serializer
All new serializers descend from ActiveModel::Serializer
# render :json
@@ -85,11 +82,11 @@
```ruby
class PostsController < ApplicationController
def show
@post = Post.find(params[:id])
- render :json => @post
+ render json: @post
end
end
```
In this case, Rails will look for a serializer named `PostSerializer`, and if
@@ -97,28 +94,17 @@
This also works with `respond_with`, which uses `to_json` under the hood. Also
note that any options passed to `render :json` will be passed to your
serializer and available as `@options` inside.
-To specify a custom serializer for an object, there are 2 options:
+To specify a custom serializer for an object, you can specify the
+serializer when you render the object:
-#### 1. Specify the serializer in your model:
-
```ruby
-class Post < ActiveRecord::Base
- def active_model_serializer
- FancyPostSerializer
- end
-end
+render json: @post, serializer: FancyPostSerializer
```
-#### 2. Specify the serializer when you render the object:
-
-```ruby
-render :json => @post, :serializer => FancyPostSerializer
-```
-
## Arrays
In your controllers, when you use `render :json` for an array of objects, AMS will
use `ActiveModel::ArraySerializer` (included in this project) as the base serializer,
and the individual `Serializer` for the objects contained in that array.
@@ -129,11 +115,11 @@
end
class PostsController < ApplicationController
def index
@posts = Post.all
- render :json => @posts
+ render json: @posts
end
end
```
Given the example above, the index action will return
@@ -150,11 +136,11 @@
By default, the root element is the name of the controller. For example, `PostsController`
generates a root element "posts". To change it:
```ruby
-render :json => @posts, :root => "some_posts"
+render json: @posts, root: "some_posts"
```
You may disable the root element for arrays at the top level, which will result in
more concise json. See the next section for ways on how to do this. Disabling the
root element of the array with any of those methods will produce
@@ -167,11 +153,11 @@
```
To specify a custom serializer for the items within an array:
```ruby
-render :json => @posts, :each_serializer => FancyPostSerializer
+render json: @posts, each_serializer: FancyPostSerializer
```
## Disabling the root element
You have 4 options to disable the root element, each with a slightly different scope:
@@ -179,34 +165,32 @@
#### 1. Disable root globally for all, or per class
In an initializer:
```ruby
-ActiveSupport.on_load(:active_model_serializers) do
- # Disable for all serializers (except ArraySerializer)
- ActiveModel::Serializer.root = false
-
- # Disable for ArraySerializer
- ActiveModel::ArraySerializer.root = false
-end
+# Disable for all serializers (except ArraySerializer)
+ActiveModel::Serializer.root = false
+
+# Disable for ArraySerializer
+ActiveModel::ArraySerializer.root = false
```
#### 2. Disable root per render call in your controller
```ruby
-render :json => @posts, :root => false
+render json: @posts, root: false
```
#### 3. Subclass the serializer, and specify using it
```ruby
class CustomArraySerializer < ActiveModel::ArraySerializer
self.root = false
end
# controller:
-render :json => @posts, :serializer => CustomArraySerializer
+render json: @posts, serializer: CustomArraySerializer
```
#### 4. Define default_serializer_options in your controller
If you define `default_serializer_options` method in your controller,
@@ -222,11 +206,11 @@
```
## Getting the old version
If you find that your project is already relying on the old rails to_json
-change `render :json` to `render :json => @your_object.to_json`.
+change `render :json` to `render json: @your_object.to_json`.
# Attributes and Associations
Once you have a serializer, you can specify which attributes and associations
you would like to include in the serialized form.
@@ -260,62 +244,70 @@
```
Within a serializer's methods, you can access the object being
serialized as `object`.
-You can also access the `current_user` method, which provides an
-authorization context to your serializer. By default, the context
-is the current user of your application, but this
-[can be customized](#customizing-scope).
+Since this shadows any attribute named `object`, you can include them through `object.object`. For example:
-Serializers will check for the presence of a method named
-`include_[ATTRIBUTE]?` to determine whether a particular attribute should be
-included in the output. This is typically used to customize output
-based on `current_user`. For example:
-
```ruby
-class PostSerializer < ActiveModel::Serializer
- attributes :id, :title, :body, :author
+class VersionSerializer < ActiveModel::Serializer
+ attribute :version_object, key: :object
- def include_author?
- current_user.admin?
+ def version_object
+ object.object
end
end
```
-The type of a computed attribute (like :full_name above) is not easily
-calculated without some sophisticated static code analysis. To specify the
-type of a computed attribute:
+You can also access the `scope` method, which provides an
+authorization context to your serializer. By default, the context
+is the current user of your application, but this
+[can be customized](#customizing-scope).
+Serializers provides a method named `filter`, which should return an array
+used to determine what attributes and associations should be included in the output.
+This is typically used to customize output based on `current_user`. For example:
+
```ruby
-class PersonSerializer < ActiveModel::Serializer
- attributes :first_name, :last_name, {:full_name => :string}
+class PostSerializer < ActiveModel::Serializer
+ attributes :id, :title, :body, :author
- def full_name
- "#{object.first_name} #{object.last_name}"
+ def filter(keys)
+ if scope.admin?
+ keys
+ else
+ keys - [:author]
+ end
end
end
```
+And it's also safe to mutate keys argument by doing keys.delete(:author)
+in case you want to avoid creating two extra arrays. Note that if you do an
+in-place modification, you still need to return the modified array.
+
If you would like the key in the outputted JSON to be different from its name
-in ActiveRecord, you can use the `:key` option to customize it:
+in ActiveRecord, you can declare the attribute with the different name
+and redefine that method:
```ruby
class PostSerializer < ActiveModel::Serializer
- attributes :id, :body
+ # look up subject on the model, but use title in the JSON
+ def title
+ object.subject
+ end
- # look up :subject on the model, but use +title+ in the JSON
- attribute :subject, :key => :title
+ attributes :id, :body, :title
has_many :comments
end
```
If you would like to add meta information to the outputted JSON, use the `:meta`
option:
```ruby
-render :json => @posts, :serializer => CustomArraySerializer, :meta => {:total => 10}
+render json: @posts, serializer: CustomArraySerializer, meta: {total: 10}
```
The above usage of `:meta` will produce the following:
```json
@@ -329,11 +321,11 @@
```
If you would like to change the meta key name you can use the `:meta_key` option:
```ruby
-render :json => @posts, :serializer => CustomArraySerializer, :meta => {:total => 10}, :meta_key => 'meta_object'
+render json: @posts, serializer: CustomArraySerializer, meta: {total: 10}, meta_key: 'meta_object'
```
The above usage of `:meta_key` will produce the following:
```json
@@ -344,20 +336,23 @@
{ "title": "Post 2", "body": "Goodbye!" }
]
}
```
+When using meta information, your serializer cannot have the `{ root: false }` option, as this would lead to
+invalid JSON. If you do not have a root key, the meta information will be ignored.
+
If you would like direct, low-level control of attribute serialization, you can
completely override the `attributes` method to return the hash you need:
```ruby
class PersonSerializer < ActiveModel::Serializer
attributes :first_name, :last_name
def attributes
hash = super
- if current_user.admin?
+ if scope.admin?
hash["ssn"] = object.ssn
hash["secret"] = object.mothers_maiden_name
end
hash
end
@@ -372,20 +367,20 @@
and use it to serialize the comment.
By default, serializers simply look up the association on the original object.
You can customize this behavior by implementing a method with the name of the
association and returning a different Array. Often, you will do this to
-customize the objects returned based on the current user.
+customize the objects returned based on the current user (scope).
```ruby
class PostSerializer < ActiveModel::Serializer
attributes :id, :title, :body
has_many :comments
# only let the user see comments he created.
def comments
- object.comments.where(:created_by => current_user)
+ object.comments.where(created_by: scope)
end
end
```
As with attributes, you can change the JSON key that the serializer should
@@ -394,54 +389,58 @@
```ruby
class PostSerializer < ActiveModel::Serializer
attributes :id, :title, :body
# look up comments, but use +my_comments+ as the key in JSON
- has_many :comments, :key => :my_comments
+ has_many :comments, root: :my_comments
end
```
-Also, as with attributes, serializers will check for the presence
-of a method named `include_[ASSOCIATION]?` to determine whether a particular association
-should be included in the output. For example:
+Also, as with attributes, serializers will execute a filter method to
+determine which associations should be included in the output. For
+example:
```ruby
class PostSerializer < ActiveModel::Serializer
attributes :id, :title, :body
has_many :comments
- def include_comments?
- !object.comments_disabled?
+ def filter(keys)
+ keys.delete :comments if object.comments_disabled?
+ keys
end
end
```
-If you would like lower-level control of association serialization, you can
-override `include_associations!` to specify which associations should be included:
+Or ...
```ruby
class PostSerializer < ActiveModel::Serializer
attributes :id, :title, :body
has_one :author
has_many :comments
- def include_associations!
- include! :author if current_user.admin?
- include! :comments unless object.comments_disabled?
+ def filter(keys)
+ keys.delete :author unless scope.admin?
+ keys.delete :comments if object.comments_disabled?
+ keys
end
end
```
You may also use the `:serializer` option to specify a custom serializer class and the `:polymorphic` option to specify an association that is polymorphic (STI), e.g.:
```ruby
- has_many :comments, :serializer => CommentShortSerializer
- has_one :reviewer, :polymorphic => true
+ has_many :comments, serializer: CommentShortSerializer
+ has_one :reviewer, polymorphic: true
```
Serializers are only concerned with multiplicity, and not ownership. `belongs_to` ActiveRecord associations can be included using `has_one` in your serializer.
+NOTE: polymorphic was removed because was only supported for has\_one
+associations and is in the TODO list of the project.
+
## Embedding Associations
By default, associations will be embedded inside the serialized object. So if
you have a post, the outputted JSON will look like:
@@ -521,11 +520,11 @@
You can specify that the data be included like this:
```ruby
class PostSerializer < ActiveModel::Serializer
- embed :ids, :include => true
+ embed :ids, include: true
attributes :id, :title, :body
has_many :comments
end
```
@@ -551,19 +550,23 @@
{ "id": 3, "name": "happy" }
]
}
```
+When side-loading data, your serializer cannot have the `{ root: false }` option,
+as this would lead to invalid JSON. If you do not have a root key, the `include`
+instruction will be ignored
+
You can also specify a different root for the embedded objects than the key
used to reference them:
```ruby
class PostSerializer < ActiveModel::Serializer
- embed :ids, :include => true
+ embed :ids, include: true
attributes :id, :title, :body
- has_many :comments, :key => :comment_ids, :root => :comment_objects
+ has_many :comments, key: :comment_ids, root: :comment_objects
end
```
This would generate JSON that would look like this:
@@ -584,14 +587,14 @@
You can also specify a different attribute to use rather than the ID of the
objects:
```ruby
class PostSerializer < ActiveModel::Serializer
- embed :ids, :include => true
+ embed :ids, include: true
attributes :id, :title, :body
- has_many :comments, :embed_key => :external_id
+ has_many :comments, embed_key: :external_id
end
```
This would generate JSON that would look like this:
@@ -628,22 +631,22 @@
class ApplicationController < ActionController::Base
serialization_scope :current_admin
end
```
-The above example will also change the scope name from `current_user` to
+The above example will also change the scope from `current_user` to
`current_admin`.
Please note that, until now, `serialization_scope` doesn't accept a second
object with options for specifying which actions should or should not take a
given scope in consideration.
To be clear, it's not possible, yet, to do something like this:
```ruby
class SomeController < ApplicationController
- serialization_scope :current_admin, :except => [:index, :show]
+ serialization_scope :current_admin, except: [:index, :show]
end
```
So, in order to have a fine grained control of what each action should take in
consideration for its scope, you may use something like this:
@@ -653,20 +656,91 @@
serialization_scope nil
def index
@cities = City.all
- render :json => @cities, :each_serializer => CitySerializer
+ render json: @cities, each_serializer: CitySerializer
end
def show
@city = City.find(params[:id])
- render :json => @city, :scope => current_admin, :scope_name => :current_admin
+ render json: @city, scope: current_admin
end
end
```
Assuming that the `current_admin` method needs to make a query in the database
for the current user, the advantage of this approach is that, by setting
`serialization_scope` to `nil`, the `index` action no longer will need to make
that query, only the `show` action will.
+
+## Testing
+
+In order to test a Serializer, you can just call `.new` on it, passing the object to serialize:
+
+### MiniTest
+
+```ruby
+class TestPostSerializer < Minitest::Test
+ def setup
+ @serializer = PostSerializer.new Post.new(id: 123, title: 'some title', body: 'some text')
+ end
+
+ def test_special_json_for_api
+ assert_equal '{"post":{"id":123,"title":"some title","body":"some text"}}', @serializer.to_json
+ end
+```
+
+### RSpec
+
+```ruby
+describe PostSerializer do
+ it "creates special JSON for the API" do
+ serializer = PostSerializer.new Post.new(id: 123, title: 'some title', body: 'some text')
+ expect(serializer.to_json).to eql('{"post":{"id":123,"title":"some title","body":"some text"}}')
+ end
+end
+```
+
+## Caching
+
+NOTE: This functionality was removed from AMS and it's in the TODO list.
+We need to re-think and re-design the caching strategy for the next
+version of AMS.
+
+To cache a serializer, call `cached` and define a `cache_key` method:
+
+```ruby
+class PostSerializer < ActiveModel::Serializer
+ cached # enables caching for this serializer
+
+ attributes :title, :body
+
+ def cache_key
+ [object, scope]
+ end
+end
+```
+
+The caching interface uses `Rails.cache` under the hood.
+
+# Design and Implementation Guidelines
+
+## Keep it Simple
+
+`ActiveModel::Serializers` is capable of producing complex JSON views/large object
+trees, and it may be tempting to design in this way so that your client can make
+fewer requests to get data and so that related querying can be optimized.
+However, keeping things simple in your serializers and controllers may
+significantly reduce complexity and maintenance over the long-term development
+of your application. Please consider reducing the complexity of the JSON views
+you provide via the serializers as you build out your application, so that
+controllers/services can be more easily reused without a lot of complexity
+later.
+
+## Performance
+
+As you develop your controllers or other code that utilizes serializers, try to
+avoid n+1 queries by ensuring that data loads in an optimal fashion, e.g. if you
+are using ActiveRecord, you might want to use query includes or joins as needed
+to make the data available that the serializer(s) need.