diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..d87d4be66
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,17 @@
+*.gem
+*.rbc
+.bundle
+.config
+.yardoc
+Gemfile.lock
+InstalledFiles
+_yardoc
+coverage
+doc/
+lib/bundler/man
+pkg
+rdoc
+spec/reports
+test/tmp
+test/version_tmp
+tmp
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 000000000..8845621d2
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,4 @@
+source 'http://rubygems.org'
+
+# Specify your gem's dependencies in active_model_serializers.gemspec
+gemspec
diff --git a/README.textile b/README.textile
new file mode 100644
index 000000000..e376227b5
--- /dev/null
+++ b/README.textile
@@ -0,0 +1,561 @@
+h2. Rails Serializers
+
+This guide describes how to use Active Model serializers to build non-trivial JSON services in Rails. By reading this guide, you will learn:
+
+* When to use the built-in Active Model serialization
+* When to use a custom serializer for your models
+* How to use serializers to encapsulate authorization concerns
+* How to create serializer templates to describe the application-wide structure of your serialized JSON
+* How to build resources not backed by a single database table for use with JSON services
+
+This guide covers an intermediate topic and assumes familiarity with Rails conventions. It is suitable for applications that expose a
+JSON API that may return different results based on the authorization status of the user.
+
+h3. Serialization
+
+By default, Active Record objects can serialize themselves into JSON by using the `to_json` method. This method takes a series of additional
+parameter to control which properties and associations Rails should include in the serialized output.
+
+When building a web application that uses JavaScript to retrieve JSON data from the server, this mechanism has historically been the primary
+way that Rails developers prepared their responses. This works great for simple cases, as the logic for serializing an Active Record object
+is neatly encapsulated in Active Record itself.
+
+However, this solution quickly falls apart in the face of serialization requirements based on authorization. For instance, a web service
+may choose to expose additional information about a resource only if the user is entitled to access it. In addition, a JavaScript front-end
+may want information that is not neatly described in terms of serializing a single Active Record object, or in a different format than.
+
+In addition, neither the controller nor the model seems like the correct place for logic that describes how to serialize an model object
+*for the current user*.
+
+Serializers solve these problems by encapsulating serialization in an object designed for this purpose. If the default +to_json+ semantics,
+with at most a few configuration options serve your needs, by all means continue to use the built-in +to_json+. If you find yourself doing
+hash-driven-development in your controllers, juggling authorization logic and other concerns, serializers are for you!
+
+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
+
+ 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
+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.
+
+
+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
+you use +respond_with+ as well.
+
+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.
+
+
+class PostSerializer
+ def initialize(post, scope)
+ @post, @scope = post, scope
+ end
+
+ def serializable_hash
+ { title: @post.name, body: @post.body }
+ end
+
+ def as_json
+ { post: serializable_hash }
+ end
+end
+
+
+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.
+
+
+class PostSerializer
+ def initialize(post, scope)
+ @post, @scope = post, scope
+ end
+
+ def as_json
+ { post: serializable_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
+
+ def super_data
+ { email: @post.email }
+ end
+
+ def super?
+ @scope.superuser?
+ end
+end
+
+
+h4. Testing
+
+One benefit of encapsulating our objects this way is that it becomes extremely straight-forward to test the serialization
+logic in isolation.
+
+
+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)
+
+ 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)
+
+ 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)
+
+ 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.
+In this case, the serializer expects to receive a post object with +name+, +body+ and +email+ attributes and an authorization
+scope with a +super?+ method.
+
+By defining a clear interface, it's must easier to ensure that your authorization logic is behaving correctly. In this case,
+the serializer doesn't need to concern itself with how the authorization scope decides whether to set the +super?+ flag, just
+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:
+
+
+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
+
+
+h3. Attribute Sugar
+
+To simplify this process for a number of common cases, Rails provides a default superclass named +ActiveModel::Serializer+
+that you can use to implement your serializers.
+
+For example, you will sometimes want to simply include a number of existing attributes from the source model into the outputted
+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.
+
+
+class PostSerializer < ActiveModel::Serializer
+ attributes :title, :body
+
+ def initialize(post, scope)
+ @post, @scope = post, scope
+ end
+
+ def serializable_hash
+ hash = attributes
+ hash.merge!(super_data) if super?
+ hash
+ end
+
+private
+ def super_data
+ { email: @post.email }
+ 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
++attributes+ that extracts those attributes from the post model.
+
+NOTE: Internally, +ActiveModel::Serializer+ uses +read_attribute_for_serialization+, which defaults to +read_attribute+, which defaults to +send+. So if you're rolling your own models for use with the serializer, you can use simple Ruby accessors for your attributes if you like.
+
+Next, we use the attributes methood in our +serializable_hash+ method, which allowed us to eliminate the +post+ method we hand-rolled
+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!
+
+
+class PostSerializer < ActiveModel::Serializer
+ attributes :title, :body
+
+private
+ def attributes
+ hash = super
+ hash.merge!(email: post.email) if super?
+ hash
+ end
+
+ def super?
+ @scope.superuser?
+ end
+end
+
+
+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
+attributes we want to use.
+
+NOTE: +ActiveModel::Serializer+ will create an accessor matching the name of the current class for the resource you pass in. In this case, because we have defined a PostSerializer, we can access the resource with the +post+ accessor.
+
+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.
+
+
+class PostSerializer < ActiveModel::Serializer
+ attributes :title, :body
+ has_many :comments
+
+private
+ def attributes
+ hash = super
+ hash.merge!(email: post.email) if super?
+ hash
+ 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"
+ }
+ ]
+ }
+}
+
+
+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.
+
+
+class CommentSerializer
+ def initialize(comment, scope)
+ @comment, @scope = comment, scope
+ end
+
+ def serializable_hash
+ { title: @comment.title }
+ 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" }]
+ }
+}
+
+
+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.
+
+
+class PostSerializer < ActiveModel::Serializer
+ attributes :title. :body
+ has_many :comments
+
+private
+ def attributes
+ hash = super
+ hash.merge!(email: post.email) if super?
+ hash
+ end
+
+ def comments
+ post.comments_for(scope)
+ end
+
+ def super?
+ @scope.superuser?
+ end
+end
+
+
++ActiveModel::Serializer+ will still embed the comments, but this time it will use just the comments
+for the current user.
+
+NOTE: The logic for deciding which comments a user should see still belongs in the model layer. In general, you should encapsulate concerns that require making direct Active Record queries in scopes or public methods on your models.
+
+h3. Customizing Associations
+
+Not all front-ends expect embedded documents in the same form. In these cases, you can override the
+default +serializable_hash+, and use conveniences provided by +ActiveModel::Serializer+ to avoid having to
+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!"
+ }
+ ]
+}
+
+
+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
+
+ # define any logic for dealing with authorization-based attributes here
+end
+
+class PostSerializer < ActiveModel::Serializer
+ attributes :title, :body
+ has_many :comments
+
+ def as_json
+ { post: serializable_hash }.merge!(associations)
+ 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
+
+ def comments
+ post.comments_for(scope)
+ end
+
+ def super?
+ @scope.superuser?
+ end
+end
+
+
+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
+serializers. The second, +association_ids+, generates a hash whose key is the association
+name and whose value is an Array of the association's keys.
+
+The +association_ids+ helper will use the overridden version of the association, so in
+this case, +association_ids+ will only include the ids of the comments provided by the
++comments+ method.
+
+h3. Special Association Serializers
+
+So far, associations defined in serializers use either the +as_json+ method on the model
+or the defined serializer for the association type. Sometimes, you may want to serialize
+associated models differently when they are requested as part of another resource than
+when they are requested on their own.
+
+For instance, we might want to provide the full comment when it is requested directly,
+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.
+
+
+class PostSerializer < ActiveModel::Serializer
+ class CommentSerializer < ActiveModel::Serializer
+ attributes :id, :title
+ end
+
+ # same as before
+ # ...
+end
+
+
+In other words, if a +PostSerializer+ is trying to serialize comments, it will first
+look for +PostSerializer::CommentSerializer+ before falling back to +CommentSerializer+
+and finally +comment.as_json+.
+
+h3. Overriding the Defaults
+
+h4. Authorization Scope
+
+By default, the authorization scope for serializers is +:current_user+. This means
+that when you call +render json: @post+, the controller will automatically call
+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.
+
+
+class PostsController < ApplicationController
+ serialization_scope :current_app
+end
+
+
+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.
+
+WARNING: If you use different objects as authorization scopes, make sure that they all implement whatever interface you use in your serializers to control what the outputted JSON looks like.
+
+h3. Using Serializers Outside of a Request
+
+The serialization API encapsulates the concern of generating a JSON representation of
+a particular model for a particular user. As a result, you should be able to easily use
+serializers, whether you define them yourself or whether you use +ActiveModel::Serializer+
+outside a request.
+
+For instance, if you want to generate the JSON representation of a post for a user outside
+of a request:
+
+
+user = get_user # some logic to get the user in question
+PostSerializer.new(post, user).to_json # reliably generate JSON output
+
+
+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:
+
+
+user = User.new # create a new anonymous user
+PostSerializer.new(post, user).to_json
+
+
+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,
+if you use an authorization library like Cancan, which uses a uniform +user.can?(action, model)+,
+the authorization interface can very easily be replaced by a plain Ruby object for
+testing or usage outside the context of a request.
+
+h3. Collections
+
+So far, we've talked about serializing individual model objects. By default, Rails
+will serialize collections, including when using the +associations+ helper, by
+looping over each element of the collection, calling +serializable_hash+ on the element,
+and then grouping them by their type (using the plural version of their class name
+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"
+ }
+ ]
+}
+
+
+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
+
+ def as_json
+ hash = { root => serializable_array }
+ hash.merge!(associations)
+ hash
+ end
+end
+
+
+When generating embedded associations using the +associations+ helper inside a
+regular serializer, it will create a new ArraySerializer
with the
+associated content and call its +serializable_array+ method. In this case, those
+embedded associations will not recursively include associations.
+
+When generating an Array using +render json: posts+, the controller will invoke
+the +as_json+ method, which will include its associations and its root.
diff --git a/Rakefile b/Rakefile
new file mode 100644
index 000000000..f57ae68a8
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,2 @@
+#!/usr/bin/env rake
+require "bundler/gem_tasks"
diff --git a/active_model_serializers.gemspec b/active_model_serializers.gemspec
new file mode 100644
index 000000000..a6fa3dbc4
--- /dev/null
+++ b/active_model_serializers.gemspec
@@ -0,0 +1,17 @@
+# -*- encoding: utf-8 -*-
+Gem::Specification.new do |gem|
+ gem.authors = ["José Valim"]
+ gem.email = ["jose.valim@gmail.com"]
+ gem.description = %q{TODO: Write a gem description}
+ gem.summary = %q{TODO: Write a gem summary}
+ gem.homepage = ""
+
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
+ gem.files = `git ls-files`.split("\n")
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
+ gem.name = "active_model_serializers"
+ gem.require_paths = ["lib"]
+ gem.version = "0.0.1"
+
+ gem.add_dependency "rails", "~> 3.0"
+end
diff --git a/lib/action_controller/serialization.rb b/lib/action_controller/serialization.rb
new file mode 100644
index 000000000..7a1e4c911
--- /dev/null
+++ b/lib/action_controller/serialization.rb
@@ -0,0 +1,51 @@
+module ActionController
+ # Action Controller Serialization
+ #
+ # Overrides render :json to check if the given object implements +active_model_serializer+
+ # as a method. If so, use the returned serializer instead of calling +to_json+ in the object.
+ #
+ # This module also provides a serialization_scope method that allows you to configure the
+ # +serialization_scope+ of the serializer. Most apps will likely set the +serialization_scope+
+ # to the current user:
+ #
+ # class ApplicationController < ActionController::Base
+ # serialization_scope :current_user
+ # end
+ #
+ # If you need more complex scope rules, you can simply override the serialization_scope:
+ #
+ # class ApplicationController < ActionController::Base
+ # private
+ #
+ # def serialization_scope
+ # current_user
+ # end
+ # end
+ #
+ module Serialization
+ extend ActiveSupport::Concern
+
+ include ActionController::Renderers
+
+ included do
+ class_attribute :_serialization_scope
+ end
+
+ def serialization_scope
+ send(_serialization_scope)
+ end
+
+ def _render_option_json(json, options)
+ if json.respond_to?(:active_model_serializer) && (serializer = json.active_model_serializer)
+ json = serializer.new(json, serialization_scope)
+ end
+ super
+ end
+
+ module ClassMethods
+ def serialization_scope(scope)
+ self._serialization_scope = scope
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb
new file mode 100644
index 000000000..0e23df2f2
--- /dev/null
+++ b/lib/active_model/serializer.rb
@@ -0,0 +1,253 @@
+require "active_support/core_ext/class/attribute"
+require "active_support/core_ext/string/inflections"
+require "active_support/core_ext/module/anonymous"
+require "set"
+
+module ActiveModel
+ # Active Model Array Serializer
+ #
+ # It serializes an array checking if each element that implements
+ # the +active_model_serializer+ method passing down the current scope.
+ class ArraySerializer
+ attr_reader :object, :scope
+
+ def initialize(object, scope)
+ @object, @scope = object, scope
+ end
+
+ def serializable_array
+ @object.map do |item|
+ if item.respond_to?(:active_model_serializer) && (serializer = item.active_model_serializer)
+ serializer.new(item, scope)
+ else
+ item
+ end
+ end
+ end
+
+ def as_json(*args)
+ serializable_array.as_json(*args)
+ end
+ end
+
+ # Active Model Serializer
+ #
+ # Provides a basic serializer implementation that allows you to easily
+ # control how a given object is going to be serialized. On initialization,
+ # it expects to object as arguments, a resource and a scope. For example,
+ # one may do in a controller:
+ #
+ # PostSerializer.new(@post, current_user).to_json
+ #
+ # The object to be serialized is the +@post+ and the scope is +current_user+.
+ #
+ # We use the scope to check if a given attribute should be serialized or not.
+ # For example, some attributes maybe only be returned if +current_user+ is the
+ # author of the post:
+ #
+ # class PostSerializer < ActiveModel::Serializer
+ # attributes :title, :body
+ # has_many :comments
+ #
+ # private
+ #
+ # def attributes
+ # hash = super
+ # hash.merge!(:email => post.email) if author?
+ # hash
+ # end
+ #
+ # def author?
+ # post.author == scope
+ # end
+ # end
+ #
+ class Serializer
+ module Associations #:nodoc:
+ class Config < Struct.new(:name, :options) #:nodoc:
+ def serializer
+ options[:serializer]
+ end
+ end
+
+ class HasMany < Config #:nodoc:
+ def serialize(collection, scope)
+ collection.map do |item|
+ serializer.new(item, scope).serializable_hash
+ end
+ end
+
+ def serialize_ids(collection, scope)
+ # use named scopes if they are present
+ # return collection.ids if collection.respond_to?(:ids)
+
+ collection.map do |item|
+ item.read_attribute_for_serialization(:id)
+ end
+ end
+ end
+
+ class HasOne < Config #:nodoc:
+ def serialize(object, scope)
+ object && serializer.new(object, scope).serializable_hash
+ end
+
+ def serialize_ids(object, scope)
+ object && object.read_attribute_for_serialization(:id)
+ end
+ end
+ end
+
+ class_attribute :_attributes
+ self._attributes = Set.new
+
+ class_attribute :_associations
+ self._associations = []
+
+ class_attribute :_root
+ class_attribute :_embed
+ self._embed = :objects
+ class_attribute :_root_embed
+
+ class << self
+ # Define attributes to be used in the serialization.
+ def attributes(*attrs)
+ self._attributes += attrs
+ end
+
+ def associate(klass, attrs) #:nodoc:
+ options = attrs.extract_options!
+ self._associations += attrs.map do |attr|
+ unless method_defined?(attr)
+ class_eval "def #{attr}() object.#{attr} end", __FILE__, __LINE__
+ end
+
+ options[:serializer] ||= const_get("#{attr.to_s.camelize}Serializer")
+ klass.new(attr, options)
+ end
+ end
+
+ # Defines an association in the object should be rendered.
+ #
+ # The serializer object should implement the association name
+ # as a method which should return an array when invoked. If a method
+ # with the association name does not exist, the association name is
+ # dispatched to the serialized object.
+ def has_many(*attrs)
+ associate(Associations::HasMany, attrs)
+ end
+
+ # Defines an association in the object should be rendered.
+ #
+ # The serializer object should implement the association name
+ # as a method which should return an object when invoked. If a method
+ # with the association name does not exist, the association name is
+ # dispatched to the serialized object.
+ def has_one(*attrs)
+ associate(Associations::HasOne, attrs)
+ end
+
+ # Define how associations should be embedded.
+ #
+ # embed :objects # Embed associations as full objects
+ # embed :ids # Embed only the association ids
+ # embed :ids, :include => true # Embed the association ids and include objects in the root
+ #
+ def embed(type, options={})
+ self._embed = type
+ self._root_embed = true if options[:include]
+ end
+
+ # Defines the root used on serialization. If false, disables the root.
+ def root(name)
+ self._root = name
+ end
+
+ def inherited(klass) #:nodoc:
+ return if klass.anonymous?
+
+ name = klass.name.demodulize.underscore.sub(/_serializer$/, '')
+
+ klass.class_eval do
+ alias_method name.to_sym, :object
+ root name.to_sym unless self._root == false
+ end
+ end
+ end
+
+ attr_reader :object, :scope
+
+ def initialize(object, scope)
+ @object, @scope = object, scope
+ end
+
+ # Returns a json representation of the serializable
+ # object including the root.
+ def as_json(*)
+ if _root
+ hash = { _root => serializable_hash }
+ hash.merge!(associations) if _root_embed
+ hash
+ else
+ serializable_hash
+ end
+ end
+
+ # Returns a hash representation of the serializable
+ # object without the root.
+ def serializable_hash
+ if _embed == :ids
+ attributes.merge(association_ids)
+ elsif _embed == :objects
+ attributes.merge(associations)
+ else
+ attributes
+ end
+ end
+
+ # Returns a hash representation of the serializable
+ # object associations.
+ def associations
+ hash = {}
+
+ _associations.each do |association|
+ associated_object = send(association.name)
+ hash[association.name] = association.serialize(associated_object, scope)
+ end
+
+ hash
+ end
+
+ # Returns a hash representation of the serializable
+ # object associations ids.
+ def association_ids
+ hash = {}
+
+ _associations.each do |association|
+ associated_object = send(association.name)
+ hash[association.name] = association.serialize_ids(associated_object, scope)
+ end
+
+ hash
+ end
+
+ # Returns a hash representation of the serializable
+ # object attributes.
+ def attributes
+ hash = {}
+
+ _attributes.each do |name|
+ hash[name] = @object.read_attribute_for_serialization(name)
+ end
+
+ hash
+ end
+ end
+end
+
+class Array
+ # Array uses ActiveModel::ArraySerializer.
+ def active_model_serializer
+ ActiveModel::ArraySerializer
+ end
+end
\ No newline at end of file
diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb
new file mode 100644
index 000000000..a6c2cf99c
--- /dev/null
+++ b/lib/active_model_serializers.rb
@@ -0,0 +1,28 @@
+require "active_model"
+require "active_model/serializer"
+
+ActiveModel::Serialization.class_eval do
+ extend ActiveSupport::Concern
+
+ module ClassMethods #:nodoc:
+ def active_model_serializer
+ return @active_model_serializer if defined?(@active_model_serializer)
+ @active_model_serializer = "#{self.name}Serializer".safe_constantize
+ end
+ end
+
+ # Returns a model serializer for this object considering its namespace.
+ def active_model_serializer
+ self.class.active_model_serializer
+ end
+end
+
+require "action_controller"
+
+module ActionController
+ autoload :Serialization, "action_controller/serialization"
+end
+
+ActiveSupport.on_load(:action_controller) do
+ include ::ActionController::Serialization
+end
\ No newline at end of file
diff --git a/lib/rails/generators/rails/serializer/USAGE b/lib/rails/generators/rails/serializer/USAGE
new file mode 100644
index 000000000..a49f7ea1f
--- /dev/null
+++ b/lib/rails/generators/rails/serializer/USAGE
@@ -0,0 +1,9 @@
+Description:
+ Generates a serializer for the given resource with tests.
+
+Example:
+ `rails generate serializer Account name created_at`
+
+ For TestUnit it creates:
+ Serializer: app/serializers/account_serializer.rb
+ TestUnit: test/unit/account_serializer_test.rb
diff --git a/lib/rails/generators/rails/serializer/serializer_generator.rb b/lib/rails/generators/rails/serializer/serializer_generator.rb
new file mode 100644
index 000000000..211890622
--- /dev/null
+++ b/lib/rails/generators/rails/serializer/serializer_generator.rb
@@ -0,0 +1,39 @@
+module Rails
+ module Generators
+ class SerializerGenerator < NamedBase
+ check_class_collision :suffix => "Serializer"
+
+ argument :attributes, :type => :array, :default => [], :banner => "field:type field:type"
+
+ class_option :parent, :type => :string, :desc => "The parent class for the generated serializer"
+
+ def create_serializer_file
+ template 'serializer.rb', File.join('app/serializers', class_path, "#{file_name}_serializer.rb")
+ end
+
+ hook_for :test_framework
+
+ private
+
+ def attributes_names
+ attributes.select { |attr| !attr.reference? }.map { |a| a.name.to_sym }
+ end
+
+ def association_names
+ attributes.select { |attr| attr.reference? }.map { |a| a.name.to_sym }
+ end
+
+ def parent_class_name
+ if options[:parent]
+ options[:parent]
+ elsif (n = Rails::Generators.namespace) && n.const_defined?(:ApplicationSerializer)
+ "ApplicationSerializer"
+ elsif Object.const_defined?(:ApplicationSerializer)
+ "ApplicationSerializer"
+ else
+ "ActiveModel::Serializer"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rails/generators/rails/serializer/templates/serializer.rb b/lib/rails/generators/rails/serializer/templates/serializer.rb
new file mode 100644
index 000000000..30c058c7e
--- /dev/null
+++ b/lib/rails/generators/rails/serializer/templates/serializer.rb
@@ -0,0 +1,9 @@
+<% module_namespacing do -%>
+class <%= class_name %>Serializer < <%= parent_class_name %>
+<% if attributes.any? -%> attributes <%= attributes_names.map(&:inspect).join(", ") %>
+<% end -%>
+<% association_names.each do |attribute| -%>
+ has_one :<%= attribute %>
+<% end -%>
+end
+<% end -%>
\ No newline at end of file
diff --git a/lib/rails/generators/test_unit/serializer/serializer_generator.rb b/lib/rails/generators/test_unit/serializer/serializer_generator.rb
new file mode 100644
index 000000000..533c032c3
--- /dev/null
+++ b/lib/rails/generators/test_unit/serializer/serializer_generator.rb
@@ -0,0 +1,13 @@
+require 'rails/generators/test_unit'
+
+module TestUnit
+ module Generators
+ class SerializerGenerator < Base
+ check_class_collision :suffix => "SerializerTest"
+
+ def create_test_files
+ template 'unit_test.rb', File.join('test/unit', class_path, "#{file_name}_serializer_test.rb")
+ end
+ end
+ end
+end
diff --git a/lib/rails/generators/test_unit/serializer/templates/unit_test.rb b/lib/rails/generators/test_unit/serializer/templates/unit_test.rb
new file mode 100644
index 000000000..0b1bbdcaa
--- /dev/null
+++ b/lib/rails/generators/test_unit/serializer/templates/unit_test.rb
@@ -0,0 +1,9 @@
+require 'test_helper'
+
+<% module_namespacing do -%>
+class <%= class_name %>SerializerTest < ActiveSupport::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end
+<% end -%>