Merge branch 'rewrite'

This commit is contained in:
Santiago Pastorino 2013-10-18 17:52:36 -02:00
commit c65d387705
61 changed files with 1681 additions and 4216 deletions

2
.gitignore vendored
View File

@ -3,7 +3,7 @@
.bundle
.config
.yardoc
Gemfile.lock
*.lock
InstalledFiles
_yardoc
coverage

View File

@ -6,10 +6,8 @@ rvm:
- rbx-19mode
gemfile:
- Gemfile
- Gemfile.rails3
- Gemfile.edge
matrix:
allow_failures:
- gemfile: Gemfile.edge
notifications:
email: false
campfire:

View File

@ -1,46 +1,20 @@
# UNRELEASED
# VERSION 0.9.0.pre
* ActiveModel::Serializable was created it has the shared code between
AM::Serializer and AM::ArraySerializer. Basically enable objects to be
serializable by implementing an options method to handle the options
of the serialization and a serialize method that returns an object to
be converted to json by the module. This also removes duplicate code.
https://github.com/rails-api/active_model_serializers/commit/6c6bc8872d3b0f040a200854fa5530a775824dbf
* The following methods were removed
- Model#active\_model\_serializer
- Serializer#include!
- Serializer#include?
- Serializer#attr\_disabled=
- Serializer#cache
- Serializer#perform\_caching
- Serializer#schema (needs more discussion)
- Serializer#attribute
- Serializer#include\_#{name}? (filter method added)
- Serializer#attributes (took a hash)
* ActiveModel::Serializer::Caching module was created it enables
Serializers to be able to cache to\_json and serialize calls. This
also helps removing duplicate code.
https://github.com/rails-api/active_model_serializers/commit/3e27110df78696ac48cafd1568f72216f348a188
* We got rid of the Association.refine method which generated
subclasses.
https://github.com/rails-api/active_model_serializers/commit/24923722d4f215c7cfcdf553fd16582e28e3801b
* Associations doesn't know anymore about the source serializer.
That didn't make any sense.
https://github.com/rails-api/active_model_serializers/commit/2252e8fe6dbf45660c6a35f35e2423792f2c3abf
https://github.com/rails-api/active_model_serializers/commit/87eadd09b9a988bc1d9b30d9a501ef7e3fc6bb87
https://github.com/rails-api/active_model_serializers/commit/79a6e13e8f7fae2eb4f48e83a9633e74beb6739e
* Passing options[:hash] is not public API of include!. That was
removed.
https://github.com/rails-api/active_model_serializers/commit/5cbf9317051002a32c90c3f995b8b2f126f70d0c
* ActiveModel::Serializer::Associations::Config is now
ActiveModel::Serializer::Association but it's an internal
thing so shouldn't bother.
ActiveModel::Serializer::Associations::Has\* are now
ActiveModel::Serializer::Association::Has\* and inherit from
ActiveModel::Serializer::Association
https://github.com/rails-api/active_model_serializers/commit/f5de334ddf1f3b9764d914a717311532021785d2
https://github.com/rails-api/active_model_serializers/commit/3dd422d99e8c57f113880da34f6abe583c4dadf9
* serialize\_ids call methods on the corresponding serializer if they
are defined, instead of talking directly with the serialized object.
Serializers are decorators so we shouldn't talk directly with
serialized objects.
* Array items are not wrapped anymore in root element.
* The following things were added
- Serializer#filter method
- SETTINGS object
* Remove support for ruby 1.8 versions.
@ -107,7 +81,7 @@
* Allow serialization_scope to be disabled with serialization_scope nil
* Array serializer should support pure ruby objects besides serializers
# VERSION 0.5.0 (May 16, 2012)
# VERSION 0.5.0
* First tagged version
* Changes generators to always generate an ApplicationSerializer

View File

@ -3,4 +3,8 @@ source 'https://rubygems.org'
# Specify gem dependencies in active_model_serializers.gemspec
gemspec
gem "coveralls", require: false
gem 'sqlite3'
gem 'coveralls', :require => false
gem 'simplecov', :require => false
gem 'rails', "~> 4.0.0"

View File

@ -1,9 +1,13 @@
source 'http://rubygems.org'
source 'https://rubygems.org'
gemspec
gem 'sqlite3'
gem 'coveralls', :require => false
gem 'simplecov', :require => false
# Use Rails master
gem 'rails', github: 'rails/rails'
# Current dependencies of edge rails
gem 'journey', github: 'rails/journey'
gem 'activerecord-deprecated_finders' , github: 'rails/activerecord-deprecated_finders'
gem 'arel', github: 'rails/arel'

11
Gemfile.rails3 Normal file
View File

@ -0,0 +1,11 @@
source 'https://rubygems.org'
# Specify gem dependencies in active_model_serializers.gemspec
gemspec
gem 'sqlite3'
gem 'coveralls', :require => false
gem 'simplecov', :require => false
gem 'minitest', '~> 4.0'
gem 'rails', '~> 3.2'

109
README.md
View File

@ -3,8 +3,9 @@
# Purpose
The purpose of `ActiveModel::Serializers` is to provide an object to
encapsulate serialization of `ActiveModel` objects, including `ActiveRecord`
objects.
encapsulate serialization of objects which respond to
read\_attribute\_for\_serialization like ActiveModel ones and including
`ActiveRecord` objects.
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
@ -55,17 +56,11 @@ the serializer generator:
$ 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
using another ORM, or if you are 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.
Currently `ActiveModel::Serializers` expects objects to implement
read\_attribute\_for\_serialization. That's all you need to do to have
your POROs supported.
# ActiveModel::Serializer
@ -92,19 +87,8 @@ 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:
#### 1. Specify the serializer in your model:
```ruby
class Post < ActiveRecord::Base
def active_model_serializer
FancyPostSerializer
end
end
```
#### 2. Specify the serializer when you render the object:
To specify a custom serializer for an object, you can specify the
serializer when you render the object:
```ruby
render json: @post, serializer: FancyPostSerializer
@ -267,49 +251,44 @@ class VersionSerializer < ActiveModel::Serializer
end
```
You can also access the `current_user` method, which provides an
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 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:
Serializers provides a method named `filter` 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 PostSerializer < ActiveModel::Serializer
attributes :id, :title, :body, :author
def include_author?
current_user.admin?
def filter(keys)
if scope.admin?
keys
else
keys - [:author]
end
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:
```ruby
class PersonSerializer < ActiveModel::Serializer
attributes :first_name, :last_name, {full_name: :string}
def full_name
"#{object.first_name} #{object.last_name}"
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.
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
```
@ -360,7 +339,7 @@ class PersonSerializer < ActiveModel::Serializer
def attributes
hash = super
if current_user.admin?
if scope.admin?
hash["ssn"] = object.ssn
hash["secret"] = object.mothers_maiden_name
end
@ -405,23 +384,23 @@ class PostSerializer < ActiveModel::Serializer
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
@ -429,9 +408,10 @@ class PostSerializer < ActiveModel::Serializer
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 current_user.admin?
keys.delete :comments if object.comments_disabled?
keys
end
end
```
@ -445,6 +425,9 @@ You may also use the `:serializer` option to specify a custom serializer class a
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
@ -635,7 +618,7 @@ class ApplicationController < ActionController::Base
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
@ -678,6 +661,10 @@ that query, only the `show` action will.
## 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

View File

@ -10,9 +10,4 @@ Rake::TestTask.new(:test) do |t|
t.verbose = true
end
desc 'Benchmark'
task :bench do
load 'bench/perf.rb'
end
task default: :test
task :default => :test

View File

@ -4,15 +4,15 @@ $:.unshift File.expand_path("../lib", __FILE__)
require "active_model/serializer/version"
Gem::Specification.new do |gem|
gem.authors = ["José Valim", "Yehuda Katz"]
gem.email = ["jose.valim@gmail.com", "wycats@gmail.com"]
gem.authors = ["José Valim", "Yehuda Katz", "Santiago Pastorino"]
gem.email = ["jose.valim@gmail.com", "wycats@gmail.com", "santiago@wyeworks.com"]
gem.description = %q{Making it easy to serialize models for client-side use}
gem.summary = %q{Bringing consistency and object orientation to model serialization. Works great for client-side MVC frameworks!}
gem.homepage = "https://github.com/rails-api/active_model_serializers"
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.files = Dir['README.md', 'CHANGELOG.md', 'CONTRIBUTING.md', 'DESIGN.textile', 'MIT-LICENSE', 'lib/**/*', 'test/**/*']
gem.test_files = Dir['test/**/*']
gem.name = "active_model_serializers"
gem.require_paths = ["lib"]
gem.version = ActiveModel::Serializer::VERSION
@ -20,9 +20,5 @@ Gem::Specification.new do |gem|
gem.required_ruby_version = ">= 1.9.3"
gem.add_dependency "activemodel", ">= 3.2"
gem.add_development_dependency "rails", ">= 3.2"
gem.add_development_dependency "pry"
gem.add_development_dependency "simplecov"
gem.add_development_dependency "coveralls"
end

View File

@ -1,43 +0,0 @@
require "rubygems"
require "bundler/setup"
require "active_model_serializers"
require "active_support/json"
require "benchmark"
class User < Struct.new(:id,:name,:age,:about)
include ActiveModel::SerializerSupport
def fast_hash
h = {
id: read_attribute_for_serialization(:id),
name: read_attribute_for_serialization(:name),
about: read_attribute_for_serialization(:about)
}
h[:age] = read_attribute_for_serialization(:age) if age > 18
h
end
end
class UserSerializer < ActiveModel::Serializer
attributes :id, :name, :age, :about
def include_age?
object.age > 18
end
end
u = User.new(1, "sam", 10, "about")
s = UserSerializer.new(u)
n = 100000
Benchmark.bmbm {|x|
x.report("init") { n.times { UserSerializer.new(u) } }
x.report("fast_hash") { n.times { u.fast_hash } }
x.report("attributes") { n.times { UserSerializer.new(u).attributes } }
x.report("serializable_hash") { n.times { UserSerializer.new(u).serializable_hash } }
}

View File

@ -1,19 +0,0 @@
As of Ruby 1.9.3, it is impossible to dynamically generate a Symbol
through interpolation without generating garbage. Theoretically, Ruby
should be able to take care of this by building up the String in C and
interning the C String.
Because of this, we avoid generating dynamic Symbols at runtime. For
example, instead of generating the instrumentation event dynamically, we
have a constant with a Hash of events:
```ruby
INSTRUMENT = {
serialize: :"serialize.serializer",
associations: :"associations.serializer"
}
```
If Ruby ever fixes this issue and avoids generating garbage with dynamic
symbols, this code can be removed.

View File

@ -1,3 +1,5 @@
require 'active_support/core_ext/class/attribute'
module ActionController
# Action Controller Serialization
#
@ -32,27 +34,46 @@ module ActionController
self._serialization_scope = :current_user
end
def serialization_scope
send(_serialization_scope) if _serialization_scope && respond_to?(_serialization_scope, true)
end
def default_serializer_options
end
def _render_option_json(resource, options)
json = ActiveModel::Serializer.build_json(self, resource, options)
if json
super(json, options)
else
super
end
end
module ClassMethods
def serialization_scope(scope)
self._serialization_scope = scope
end
end
def _render_option_json(resource, options)
serializer = build_json_serializer(resource, options)
if serializer
super(serializer, options)
else
super
end
end
private
def default_serializer_options
{}
end
def serialization_scope
_serialization_scope = self.class._serialization_scope
send(_serialization_scope) if _serialization_scope && respond_to?(_serialization_scope, true)
end
def build_json_serializer(resource, options)
options = default_serializer_options.merge(options || {})
serializer =
options.delete(:serializer) ||
ActiveModel::Serializer.serializer_for(resource)
return unless serializer
options[:scope] = serialization_scope unless options.has_key?(:scope)
options[:resource_name] = self.controller_name if resource.respond_to?(:to_ary)
serializer.new(resource, options)
end
end
end

View File

@ -1,65 +1,35 @@
require 'active_model/default_serializer'
require 'active_model/serializable'
require 'active_model/serializer/caching'
require "active_support/core_ext/class/attribute"
require 'active_support/dependencies'
require 'active_support/descendants_tracker'
require 'active_model/serializer'
module ActiveModel
# Active Model Array Serializer
#
# Serializes an Array, checking if each element implements
# the +active_model_serializer+ method.
#
# To disable serialization of root elements:
#
# ActiveModel::ArraySerializer.root = false
#
class ArraySerializer
extend ActiveSupport::DescendantsTracker
include ActiveModel::Serializable
include ActiveModel::Serializer::Caching
attr_reader :object, :options
class_attribute :root
class_attribute :cache
class_attribute :perform_caching
include Serializable
class << self
# set perform caching like root
def cached(value = true)
self.perform_caching = value
end
attr_accessor :_root
alias root _root=
alias root= _root=
end
def initialize(object, options={})
@object = object
@options = options
end
def serialize_object
serializable_array
@object = object
@root = options[:root]
@root = self.class._root if @root.nil?
@root = options[:resource_name] if @root.nil?
@meta_key = options[:meta_key] || :meta
@meta = options[@meta_key]
@each_serializer = options[:each_serializer]
@options = options.merge(root: nil)
end
attr_accessor :object, :root, :meta_key, :meta
def serializable_array
object.map do |item|
if options.has_key? :each_serializer
serializer = options[:each_serializer]
elsif item.respond_to?(:active_model_serializer)
serializer = item.active_model_serializer
end
serializer ||= DefaultSerializer
serializable = serializer.new(item, options.merge(root: nil))
if serializable.respond_to?(:serializable_hash)
serializable.serializable_hash
else
serializable.as_json
end
@object.map do |item|
serializer = @each_serializer || Serializer.serializer_for(item) || DefaultSerializer
serializer.new(item, @options).serializable_object
end
end
alias serializable_object serializable_array
end
end

View File

@ -0,0 +1,17 @@
module ActiveModel
# DefaultSerializer
#
# Provides a constant interface for all items
class DefaultSerializer
attr_reader :object
def initialize(object, options=nil)
@object = object
end
def serializable_hash(*)
@object.as_json
end
alias serializable_object serializable_hash
end
end

View File

@ -1,49 +1,21 @@
require 'active_support/core_ext/object/to_json'
module ActiveModel
# Enable classes to Classes including this module to serialize themselves by implementing a serialize method and an options method.
#
# Example:
#
# require 'active_model_serializers'
#
# class MySerializer
# include ActiveModel::Serializable
#
# def initialize
# @options = {}
# end
#
# attr_reader :options
#
# def serialize
# { a: 1 }
# end
# end
#
# puts MySerializer.new.to_json
module Serializable
def as_json(args={})
if root = args[:root] || options[:root]
options[:hash] = hash = {}
options[:unique_values] = {}
hash.merge!(root => serialize)
include_meta hash
def as_json(options={})
if root = options[:root] || self.root
hash = { root => serializable_object }
hash.merge!(serializable_data)
hash
else
serialize
serializable_object
end
end
private
def include_meta(hash)
hash[meta_key] = options[:meta] if options.has_key?(:meta)
end
def meta_key
options[:meta_key].try(:to_sym) || :meta
def serializable_data
if respond_to?(:meta) && meta
{ meta_key => meta }
else
{}
end
end
end
end

View File

@ -1,124 +1,81 @@
require 'active_model/array_serializer'
require 'active_model/serializable'
require 'active_model/serializer/caching'
require "active_support/core_ext/class/attribute"
require "active_support/core_ext/module/anonymous"
require 'active_support/dependencies'
require 'active_support/descendants_tracker'
require 'active_model/serializer/associations'
require 'active_model/serializer/settings'
module ActiveModel
# 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 two objects as arguments, a resource and options. For example,
# one may do in a controller:
#
# PostSerializer.new(@post, scope: current_user).to_json
#
# The object to be serialized is the +@post+ and the current user is passed
# in for authorization purposes.
#
# We use the scope to check if a given attribute should be serialized or not.
# For example, some attributes may 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
extend ActiveSupport::DescendantsTracker
include ActiveModel::Serializable
include ActiveModel::Serializer::Caching
INCLUDE_METHODS = {}
INSTRUMENT = { serialize: :"serialize.serializer", associations: :"associations.serializer" }
class IncludeError < StandardError
attr_reader :source, :association
def initialize(source, association)
@source, @association = source, association
end
def to_s
"Cannot serialize #{association} when #{source} does not have a root!"
end
end
class_attribute :_attributes
self._attributes = {}
class_attribute :_associations
self._associations = {}
class_attribute :_root
class_attribute :_embed
self._embed = :objects
class_attribute :_root_embed
class_attribute :cache
class_attribute :perform_caching
include Serializable
class << self
def cached(value = true)
self.perform_caching = value
def inherited(base)
base._attributes = []
base._associations = {}
end
# Define attributes to be used in the serialization.
def attributes(*attrs)
def setup
yield SETTINGS
end
self._attributes = _attributes.dup
def embed(type, options={})
SETTINGS[:embed] = type
SETTINGS[:include] = true if options[:include]
end
if RUBY_VERSION >= '2.0'
def serializer_for(resource)
if resource.respond_to?(:to_ary)
ArraySerializer
else
begin
Object.const_get "#{resource.class.name}Serializer"
rescue NameError
nil
end
end
end
else
def serializer_for(resource)
if resource.respond_to?(:to_ary)
ArraySerializer
else
"#{resource.class.name}Serializer".safe_constantize
end
end
end
attr_accessor :_root, :_attributes, :_associations
alias root _root=
alias root= _root=
def root_name
name.demodulize.underscore.sub(/_serializer$/, '') if name
end
def attributes(*attrs)
@_attributes.concat attrs
attrs.each do |attr|
if Hash === attr
attr.each {|attr_real, key| attribute(attr_real, key: key) }
else
attribute attr
unless method_defined?(attr)
define_method attr do
object.read_attribute_for_serialization(attr)
end
end
end
end
def attribute(attr, options={})
self._attributes = _attributes.merge(attr.is_a?(Hash) ? attr : {attr => options[:key] || attr.to_s.gsub(/\?$/, '').to_sym})
attr = attr.keys[0] if attr.is_a? Hash
unless method_defined?(attr)
define_method attr do
object.read_attribute_for_serialization(attr.to_sym)
end
end
define_include_method attr
# protect inheritance chains and open classes
# if a serializer inherits from another OR
# attributes are added later in a classes lifecycle
# poison the cache
define_method :_fast_attributes do
raise NameError
end
def has_one(*attrs)
associate(Association::HasOne, *attrs)
end
def associate(klass, attrs) #:nodoc:
def has_many(*attrs)
associate(Association::HasMany, *attrs)
end
private
def associate(klass, *attrs)
options = attrs.extract_options!
self._associations = _associations.dup
attrs.each do |attr|
unless method_defined?(attr)
@ -127,349 +84,91 @@ module ActiveModel
end
end
define_include_method attr
self._associations[attr] = [klass, options]
@_associations[attr] = klass.new(attr, options)
end
end
def define_include_method(name)
method = "include_#{name}?".to_sym
INCLUDE_METHODS[name] = method
unless method_defined?(method)
define_method method do
true
end
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(Association::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(Association::HasOne, attrs)
end
# Return a schema hash for the current serializer. This information
# can be used to generate clients for the serialized output.
#
# The schema hash has two keys: +attributes+ and +associations+.
#
# The +attributes+ hash looks like this:
#
# { name: :string, age: :integer }
#
# The +associations+ hash looks like this:
# { posts: { has_many: :posts } }
#
# If :key is used:
#
# class PostsSerializer < ActiveModel::Serializer
# has_many :posts, key: :my_posts
# end
#
# the hash looks like this:
#
# { my_posts: { has_many: :posts }
#
# This information is extracted from the serializer's model class,
# which is provided by +SerializerClass.model_class+.
#
# The schema method uses the +columns_hash+ and +reflect_on_association+
# methods, provided by default by ActiveRecord. You can implement these
# methods on your custom models if you want the serializer's schema method
# to work.
#
# TODO: This is currently coupled to Active Record. We need to
# figure out a way to decouple those two.
def schema
klass = model_class
columns = klass.columns_hash
attrs = {}
_attributes.each do |name, key|
if column = columns[name.to_s]
attrs[key] = column.type
else
# Computed attribute (method on serializer or model). We cannot
# infer the type, so we put nil, unless specified in the attribute declaration
if name != key
attrs[name] = key
else
attrs[key] = nil
end
end
end
associations = {}
_associations.each do |attr, (association_class, options)|
association = association_class.new(attr, options)
if model_association = klass.reflect_on_association(association.name)
# Real association.
associations[association.key] = { model_association.macro => model_association.name }
else
# Computed association. We could infer has_many vs. has_one from
# the association class, but that would make it different from
# real associations, which read has_one vs. belongs_to from the
# model.
associations[association.key] = nil
end
end
{ attributes: attrs, associations: associations }
end
# The model class associated with this serializer.
def model_class
name.sub(/Serializer$/, '').constantize
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
alias_method :root=, :root
# Used internally to create a new serializer object based on controller
# settings and options for a given resource. These settings are typically
# set during the request lifecycle or by the controller class, and should
# not be manually defined for this method.
def build_json(controller, resource, options)
default_options = controller.send(:default_serializer_options) || {}
options = default_options.merge(options || {})
serializer = options.delete(:serializer) ||
(resource.respond_to?(:active_model_serializer) &&
resource.active_model_serializer)
return serializer unless serializer
if resource.respond_to?(:to_ary)
unless serializer <= ActiveModel::ArraySerializer
raise ArgumentError.new("#{serializer.name} is not an ArraySerializer. " +
"You may want to use the :each_serializer option instead.")
end
if options[:root] != false && serializer.root != false
# the serializer for an Array is ActiveModel::ArraySerializer
options[:root] ||= serializer.root || controller.controller_name
end
end
options[:scope] = controller.serialization_scope unless options.has_key?(:scope)
options[:scope_name] = controller._serialization_scope unless options.has_key?(:scope_name)
options[:url_options] = controller.url_options
serializer.new(resource, options)
end
end
attr_reader :object, :options
def initialize(object, options={})
@object, @options = object, options
@object = object
@scope = options[:scope]
self.root = options[:root]
@meta_key = options[:meta_key] || :meta
@meta = options[@meta_key]
end
attr_accessor :object, :scope, :meta_key, :meta
attr_reader :root
scope_name = @options[:scope_name]
if scope_name && !respond_to?(scope_name)
self.class.class_eval do
define_method scope_name, lambda { scope }
end
end
def root=(root)
@root = root
@root = self.class._root if @root.nil?
@root = self.class.root_name if @root == true || @root.nil?
end
def root_name
return false if self._root == false
class_name = self.class.name.demodulize.underscore.sub(/_serializer$/, '').to_sym unless self.class.name.blank?
if self._root == true
class_name
else
self._root || class_name
end
end
def url_options
@options[:url_options] || {}
end
# Returns a json representation of the serializable
# object including the root.
def as_json(args={})
super(root: args.fetch(:root, options.fetch(:root, root_name)))
end
def serialize_object
serializable_hash
end
# Returns a hash representation of the serializable
# object without the root.
def serializable_hash
return nil if @object.nil?
@node = attributes
include_associations! if _embed
@node
end
def include_associations!
_associations.each_key do |name|
include!(name) if include?(name)
end
end
def include?(name)
return false if @options.key?(:only) && !Array(@options[:only]).include?(name)
return false if @options.key?(:except) && Array(@options[:except]).include?(name)
send INCLUDE_METHODS[name]
end
def include!(name, options={})
hash = @options[:hash]
unique_values = @options[:unique_values] ||= {}
node = options[:node] ||= @node
value = options[:value]
if options[:include] == nil
if @options.key?(:include)
options[:include] = @options[:include].include?(name)
elsif @options.include?(:exclude)
options[:include] = !@options[:exclude].include?(name)
end
end
klass, klass_options = _associations[name]
association_class =
if klass
options = klass_options.merge options
klass
elsif value.respond_to?(:to_ary)
Association::HasMany
else
Association::HasOne
end
options = default_embed_options.merge!(options)
options[:value] ||= send(name)
association = association_class.new(name, options, self.options)
if association.embed_ids?
node[association.key] = association.serialize_ids
if association.embed_in_root? && hash.nil?
raise IncludeError.new(self.class, association.name)
elsif association.embed_in_root? && association.embeddable?
merge_association hash, association.root, association.serializables, unique_values
end
elsif association.embed_objects?
node[association.key] = association.serialize
end
end
# In some cases, an Array of associations is built by merging the associated
# content for all of the children. For instance, if a Post has_many comments,
# which has_many tags, the top-level :tags key will contain the merged list
# of all tags for all comments of the post.
#
# In order to make this efficient, we store a :unique_values hash containing
# a unique list of all of the objects that are already in the Array. This
# avoids the need to scan through the Array looking for entries every time
# we want to merge a new list of values.
def merge_association(hash, key, serializables, unique_values)
already_serialized = (unique_values[key] ||= {})
serializable_hashes = (hash[key] ||= [])
serializables.each do |serializable|
unless already_serialized.include? serializable.object
already_serialized[serializable.object] = true
serializable_hashes << serializable.serializable_hash
end
end
end
# Returns a hash representation of the serializable
# object attributes.
def attributes
_fast_attributes
rescue NameError
method = "def _fast_attributes\n"
filter(self.class._attributes.dup).each_with_object({}) do |name, hash|
hash[name] = send(name)
end
end
method << " h = {}\n"
_attributes.each do |name,key|
method << " h[:\"#{key}\"] = read_attribute_for_serialization(:\"#{name}\") if include?(:\"#{name}\")\n"
def associations
associations = self.class._associations
included_associations = filter(associations.keys)
associations.each_with_object({}) do |(name, association), hash|
if included_associations.include? name
if association.embed_ids?
hash[association.key] = serialize_ids association
elsif association.embed_objects?
hash[association.embedded_key] = serialize association
end
end
method << " h\nend"
self.class.class_eval method
_fast_attributes
end
end
# Returns options[:scope]
def scope
@options[:scope]
def filter(keys)
keys
end
alias :read_attribute_for_serialization :send
# Use ActiveSupport::Notifications to send events to external systems.
# The event name is: name.class_name.serializer
def instrument(name, payload = {}, &block)
event_name = INSTRUMENT[name]
ActiveSupport::Notifications.instrument(event_name, payload, &block)
def serializable_data
embedded_in_root_associations.merge!(super)
end
private
def default_embed_options
{
embed: _embed,
include: _root_embed
}
end
end
# DefaultSerializer
#
# Provides a constant interface for all items, particularly
# for ArraySerializer.
class DefaultSerializer
attr_reader :object, :options
def initialize(object, options={})
@object, @options = object, options
def embedded_in_root_associations
associations = self.class._associations
included_associations = filter(associations.keys)
associations.each_with_object({}) do |(name, association), hash|
if included_associations.include? name
if association.embed_in_root?
hash[association.embedded_key] = serialize association
end
end
end
end
def serializable_hash
@object.as_json(@options)
def serialize(association)
associated_data = send(association.name)
if associated_data.respond_to?(:to_ary)
associated_data.map { |elem| association.build_serializer(elem).serializable_hash }
else
result = association.build_serializer(associated_data).serializable_hash
association.is_a?(Association::HasMany) ? [result] : result
end
end
def serialize_ids(association)
associated_data = send(association.name)
if associated_data.respond_to?(:to_ary)
associated_data.map { |elem| elem.read_attribute_for_serialization(association.embed_key) }
else
associated_data.read_attribute_for_serialization(association.embed_key) if associated_data
end
end
def serializable_hash(options={})
return nil if object.nil?
hash = attributes
hash.merge! associations
end
alias serializable_object serializable_hash
end
end

View File

@ -1,183 +1,53 @@
require 'active_model/default_serializer'
require 'active_model/serializer'
module ActiveModel
class Serializer
class Association #:nodoc:
# name: The name of the association.
#
# options: A hash. These keys are accepted:
#
# value: The object we're associating with.
#
# serializer: The class used to serialize the association.
#
# embed: Define how associations should be embedded.
# - :objects # Embed associations as full objects.
# - :ids # Embed only the association ids.
# - :ids, include: true # Embed the association ids and include objects in the root.
#
# include: Used in conjunction with embed :ids. Includes the objects in the root.
#
# root: Used in conjunction with include: true. Defines the key used to embed the objects.
#
# key: Key name used to store the ids in.
#
# embed_key: Method used to fetch ids. Defaults to :id.
#
# polymorphic: Is the association is polymorphic?. Values: true or false.
def initialize(name, options={}, serializer_options={})
@name = name
@object = options[:value]
class Association
def initialize(name, options={})
@name = name.to_s
@options = options
embed = options[:embed]
@embed_ids = embed == :id || embed == :ids
@embed_objects = embed == :object || embed == :objects
self.embed = options[:embed] || SETTINGS[:embed] || :objects
@embed_in_root = @embed_ids && (options[:include] || SETTINGS[:include])
@embed_key = options[:embed_key] || :id
@embed_in_root = options[:include]
@key = options[:key]
@embedded_key = options[:root] || name
serializer = options[:serializer]
@serializer_class = serializer.is_a?(String) ? serializer.constantize : serializer
@options = options
@serializer_options = serializer_options
self.serializer_class = @options[:serializer]
end
attr_reader :object, :root, :name, :embed_ids, :embed_objects, :embed_in_root
alias embeddable? object
alias embed_objects? embed_objects
attr_reader :name, :embed_ids, :embed_objects, :serializer_class
attr_accessor :embed_in_root, :embed_key, :key, :embedded_key, :options
alias embed_ids? embed_ids
alias use_id_key? embed_ids?
alias embed_objects? embed_objects
alias embed_in_root? embed_in_root
def key
if key = options[:key]
key
elsif use_id_key?
id_key
else
name
end
def serializer_class=(serializer)
@serializer_class = serializer.is_a?(String) ? serializer.constantize : serializer
end
private
attr_reader :embed_key, :serializer_class, :options, :serializer_options
def find_serializable(object)
if serializer_class
serializer_class.new(object, serializer_options)
elsif object.respond_to?(:active_model_serializer) && (ams = object.active_model_serializer)
ams.new(object, serializer_options)
else
object
end
def embed=(embed)
@embed_ids = embed == :id || embed == :ids
@embed_objects = embed == :object || embed == :objects
end
class HasMany < Association #:nodoc:
def root
options[:root] || name
end
def id_key
"#{name.to_s.singularize}_ids".to_sym
end
def serializables
object.map do |item|
find_serializable(item)
end
end
def serialize
object.map do |item|
find_serializable(item).serializable_hash
end
end
def serialize_ids
object.map do |item|
serializer = find_serializable(item)
if serializer.respond_to?(embed_key)
serializer.send(embed_key)
else
item.read_attribute_for_serialization(embed_key)
end
end
end
def build_serializer(object)
@serializer_class ||= Serializer.serializer_for(object) || DefaultSerializer
@serializer_class.new(object, @options)
end
class HasOne < Association #:nodoc:
def initialize(name, options={}, serializer_options={})
class HasOne < Association
def initialize(*args)
super
@polymorphic = options[:polymorphic]
@key ||= "#{name}_id"
end
end
def root
if root = options[:root]
root
elsif polymorphic?
object.class.to_s.pluralize.demodulize.underscore.to_sym
else
name.to_s.pluralize.to_sym
end
end
def id_key
"#{name}_id".to_sym
end
def embeddable?
super || !polymorphic?
end
def serializables
value = object && find_serializable(object)
value ? [value] : []
end
def serialize
if object
if polymorphic?
{
:type => polymorphic_key,
polymorphic_key => find_serializable(object).serializable_hash
}
else
find_serializable(object).serializable_hash
end
end
end
def serialize_ids
if object
serializer = find_serializable(object)
id =
if serializer.respond_to?(embed_key)
serializer.send(embed_key)
else
object.read_attribute_for_serialization(embed_key)
end
if polymorphic?
{
type: polymorphic_key,
id: id
}
else
id
end
end
end
private
attr_reader :polymorphic
alias polymorphic? polymorphic
def use_id_key?
embed_ids? && !polymorphic?
end
def polymorphic_key
object.class.to_s.demodulize.underscore.to_sym
class HasMany < Association
def initialize(*args)
super
@key ||= "#{name.singularize}_ids"
end
end
end

View File

@ -1,37 +0,0 @@
module ActiveModel
class Serializer
module Caching
def to_json(*args)
if caching_enabled?
key = expand_cache_key([self.class.to_s.underscore, cache_key, 'to-json'])
cache.fetch key do
super
end
else
super
end
end
def serialize(*args)
if caching_enabled?
key = expand_cache_key([self.class.to_s.underscore, cache_key, 'serialize'])
cache.fetch key do
serialize_object
end
else
serialize_object
end
end
private
def caching_enabled?
perform_caching && cache && respond_to?(:cache_key)
end
def expand_cache_key(*args)
ActiveSupport::Cache.expand_cache_key(args)
end
end
end
end

View File

@ -0,0 +1,13 @@
require 'rails/generators'
require 'rails/generators/rails/resource/resource_generator'
module Rails
module Generators
class ResourceGenerator
def add_serializer
invoke 'serializer'
end
end
end
end

View File

@ -1,12 +1,12 @@
module Rails
module Generators
class SerializerGenerator < NamedBase
source_root File.expand_path("../templates", __FILE__)
check_class_collision suffix: "Serializer"
source_root File.expand_path('../templates', __FILE__)
check_class_collision suffix: 'Serializer'
argument :attributes, type: :array, default: [], banner: "field:type field:type"
argument :attributes, type: :array, default: [], banner: 'field:type field:type'
class_option :parent, type: :string, desc: "The parent class for the generated serializer"
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")
@ -25,10 +25,11 @@ module Rails
def parent_class_name
if options[:parent]
options[:parent]
elsif defined?(::ApplicationSerializer)
"ApplicationSerializer"
elsif (ns = Rails::Generators.namespace) && ns.const_defined?(:ApplicationSerializer) ||
defined?(::ApplicationSerializer)
'ApplicationSerializer'
else
"ActiveModel::Serializer"
'ActiveModel::Serializer'
end
end
end

View File

@ -0,0 +1,10 @@
module ActiveModel
class Railtie < Rails::Railtie
initializer 'generators' do |app|
require 'rails/generators'
require 'active_model/serializer/generators/serializer/serializer_generator'
Rails::Generators.configure!(app.config.generators)
require 'active_model/serializer/generators/resource_override'
end
end
end

View File

@ -0,0 +1,27 @@
module ActiveModel
class Serializer
class Settings
def initialize
@data = {}
end
def [](key)
@data[key.to_s]
end
def []=(key, value)
@data[key.to_s] = value
end
def each(&block)
@data.each(&block)
end
def clear
@data.clear
end
end
SETTINGS = Settings.new
end
end

View File

@ -1,5 +1,5 @@
module ActiveModel
class Serializer
VERSION = "0.8.1"
VERSION = "0.9.0.pre"
end
end

View File

@ -1,85 +1,7 @@
require "active_support"
require "active_support/core_ext/string/inflections"
require "active_support/notifications"
require "active_model"
require "active_model/array_serializer"
require "active_model/serializer"
require "active_model/serializer/associations"
require "set"
if defined?(Rails)
module ActiveModel
class Railtie < Rails::Railtie
generators do |app|
Rails::Generators.configure!(app.config.generators)
Rails::Generators.hidden_namespaces.uniq!
require_relative "generators/resource_override"
end
initializer "include_routes.active_model_serializer" do |app|
ActiveSupport.on_load(:active_model_serializers) do
include AbstractController::UrlFor
extend ::AbstractController::Railties::RoutesHelpers.with(app.routes)
include app.routes.mounted_helpers
end
end
initializer "caching.active_model_serializer" do |app|
ActiveModel::Serializer.perform_caching = app.config.action_controller.perform_caching
ActiveModel::ArraySerializer.perform_caching = app.config.action_controller.perform_caching
ActiveModel::Serializer.cache = Rails.cache
ActiveModel::ArraySerializer.cache = Rails.cache
end
end
end
end
module ActiveModel::SerializerSupport
extend ActiveSupport::Concern
module ClassMethods #:nodoc:
if "".respond_to?(:safe_constantize)
def active_model_serializer
"#{self.name}Serializer".safe_constantize
end
else
def active_model_serializer
begin
"#{self.name}Serializer".constantize
rescue NameError => e
raise unless e.message =~ /uninitialized constant/
end
end
end
end
# Returns a model serializer for this object considering its namespace.
def active_model_serializer
self.class.active_model_serializer
end
alias :read_attribute_for_serialization :send
end
module ActiveModel::ArraySerializerSupport
def active_model_serializer
ActiveModel::ArraySerializer
end
end
Array.send(:include, ActiveModel::ArraySerializerSupport)
Set.send(:include, ActiveModel::ArraySerializerSupport)
{
active_record: 'ActiveRecord::Relation',
mongoid: 'Mongoid::Criteria'
}.each do |orm, rel_class|
ActiveSupport.on_load(orm) do
include ActiveModel::SerializerSupport
rel_class.constantize.send(:include, ActiveModel::ArraySerializerSupport)
end
end
require 'active_model'
require 'active_model/serializer'
require 'active_model/serializer/version'
require 'active_model/serializer/railtie' if defined?(Rails)
begin
require 'action_controller'
@ -88,8 +10,6 @@ begin
ActiveSupport.on_load(:action_controller) do
include ::ActionController::Serialization
end
rescue LoadError => ex
# rails on installed, continuing
rescue LoadError
# rails not installed, continuing
end
ActiveSupport.run_load_hooks(:active_model_serializers, ActiveModel::Serializer)

View File

@ -1,16 +0,0 @@
# We do not recommend that you use AM::S in this way, but if you must, here
# is a mixin that overrides ActiveRecord::Base#to_json and #as_json.
module ActiveRecord
module SerializerOverride
def to_json options = {}
active_model_serializer.new(self).to_json options
end
def as_json options={}
active_model_serializer.new(self).as_json options
end
end
Base.send(:include, SerializerOverride)
end

View File

@ -1,13 +0,0 @@
require "rails/generators"
require "rails/generators/rails/resource/resource_generator"
module Rails
module Generators
ResourceGenerator.class_eval do
def add_serializer
invoke "serializer"
end
end
end
end

View File

@ -1,85 +0,0 @@
require "test_helper"
require "test_fakes"
class ArraySerializerTest < ActiveModel::TestCase
# serialize different typed objects
def test_array_serializer
model = Model.new
user = User.new
comments = Comment.new(title: "Comment1", id: 1)
array = [model, user, comments]
serializer = array.active_model_serializer.new(array, scope: { scope: true })
assert_equal([
{ model: "Model" },
{ last_name: "Valim", ok: true, first_name: "Jose", scope: true },
{ title: "Comment1" }
], serializer.as_json)
end
def test_array_serializer_with_root
comment1 = Comment.new(title: "Comment1", id: 1)
comment2 = Comment.new(title: "Comment2", id: 2)
array = [ comment1, comment2 ]
serializer = array.active_model_serializer.new(array, root: :comments)
assert_equal({ comments: [
{ title: "Comment1" },
{ title: "Comment2" }
]}, serializer.as_json)
end
def test_active_model_with_root
comment1 = ModelWithActiveModelSerializer.new(title: "Comment1")
comment2 = ModelWithActiveModelSerializer.new(title: "Comment2")
array = [ comment1, comment2 ]
serializer = array.active_model_serializer.new(array, root: :comments)
assert_equal({ comments: [
{ title: "Comment1" },
{ title: "Comment2" }
]}, serializer.as_json)
end
def test_array_serializer_with_hash
hash = { value: "something" }
array = [hash]
serializer = array.active_model_serializer.new(array, root: :items)
assert_equal({ items: [hash.as_json] }, serializer.as_json)
end
def test_array_serializer_with_specified_serializer
post1 = Post.new(title: "Post1", author: "Author1", id: 1)
post2 = Post.new(title: "Post2", author: "Author2", id: 2)
array = [ post1, post2 ]
serializer = array.active_model_serializer.new array, each_serializer: CustomPostSerializer
assert_equal([
{ title: "Post1" },
{ title: "Post2" }
], serializer.as_json)
end
def test_array_serializer_using_default_serializer
hash = { "value" => "something" }
class << hash
def active_model_serializer
nil
end
end
array = [hash]
serializer = array.active_model_serializer.new array
assert_equal([
{ "value" => "something" }
], serializer.as_json)
end
end

View File

@ -1,592 +0,0 @@
require "test_helper"
class AssociationTest < ActiveModel::TestCase
def def_serializer(&block)
Class.new(ActiveModel::Serializer, &block)
end
class Model
def initialize(hash={})
@attributes = hash
end
def read_attribute_for_serialization(name)
@attributes[name]
end
def as_json(*)
{ model: "Model" }
end
def method_missing(meth, *args)
if meth.to_s =~ /^(.*)=$/
@attributes[$1.to_sym] = args[0]
elsif @attributes.key?(meth)
@attributes[meth]
else
super
end
end
end
def setup
@hash = {}
@root_hash = {}
@post = Model.new(title: "New Post", body: "Body")
@comment = Model.new(id: 1, external_id: "COMM001", body: "ZOMG A COMMENT")
@post.comments = [ @comment ]
@post.comment = @comment
@comment_serializer_class = def_serializer do
attributes :id, :external_id, :body
end
@post_serializer_class = def_serializer do
attributes :title, :body
end
@post_serializer = @post_serializer_class.new(@post, hash: @root_hash)
end
def include!(key, options={})
@post_serializer.include! key, {
embed: :ids,
include: true,
node: @hash,
serializer: @comment_serializer_class
}.merge(options)
end
def include_bare!(key, options={})
@post_serializer.include! key, {
node: @hash,
serializer: @comment_serializer_class
}.merge(options)
end
class NoDefaults < AssociationTest
def test_include_bang_has_many_associations
include! :comments, value: @post.comments
assert_equal({
comment_ids: [ 1 ]
}, @hash)
assert_equal({
comments: [
{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }
]
}, @root_hash)
end
def test_include_bang_with_embed_false
include! :comments, value: @post.comments, embed: false
assert_equal({}, @hash)
assert_equal({}, @root_hash)
end
def test_include_bang_with_embed_ids_include_false
include! :comments, value: @post.comments, embed: :ids, include: false
assert_equal({
comment_ids: [ 1 ]
}, @hash)
assert_equal({}, @root_hash)
end
def test_include_bang_has_one_associations
include! :comment, value: @post.comment
assert_equal({
comment_id: 1
}, @hash)
assert_equal({
comments: [{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }]
}, @root_hash)
end
end
class DefaultsTest < AssociationTest
def test_with_default_has_many
@post_serializer_class.class_eval do
has_many :comments
end
include! :comments
assert_equal({
comment_ids: [ 1 ]
}, @hash)
assert_equal({
comments: [
{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }
]
}, @root_hash)
end
def test_with_default_has_one
@post_serializer_class.class_eval do
has_one :comment
end
include! :comment
assert_equal({
comment_id: 1
}, @hash)
assert_equal({
comments: [
{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }
]
}, @root_hash)
end
def test_with_default_has_many_with_custom_key
@post_serializer_class.class_eval do
has_many :comments, key: :custom_comments
end
include! :comments
assert_equal({
custom_comments: [ 1 ]
}, @hash)
assert_equal({
comments: [
{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }
]
}, @root_hash)
end
def test_with_default_has_one_with_custom_key
@post_serializer_class.class_eval do
has_one :comment, key: :custom_comment_id
end
include! :comment
assert_equal({
custom_comment_id: 1
}, @hash)
assert_equal({
comments: [
{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }
]
}, @root_hash)
end
def test_with_default_has_many_with_custom_embed_key
@post_serializer_class.class_eval do
has_many :comments, embed_key: :external_id
end
include! :comments
assert_equal({
comment_ids: [ "COMM001" ]
}, @hash)
assert_equal({
comments: [
{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }
]
}, @root_hash)
end
def test_with_default_has_one_with_custom_embed_key
@post_serializer_class.class_eval do
has_one :comment, embed_key: :external_id
end
include! :comment
assert_equal({
comment_id: "COMM001"
}, @hash)
assert_equal({
comments: [
{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }
]
}, @root_hash)
end
def test_with_default_has_many_with_custom_key_and_custom_embed_key
@post_serializer_class.class_eval do
has_many :comments, key: :custom_comments, embed_key: :external_id
end
include! :comments
assert_equal({
custom_comments: [ "COMM001" ]
}, @hash)
assert_equal({
comments: [
{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }
]
}, @root_hash)
end
def test_with_default_has_one_with_custom_key_and_custom_embed_key
@post_serializer_class.class_eval do
has_one :comment, key: :custom_comment, embed_key: :external_id
end
include! :comment
assert_equal({
custom_comment: "COMM001"
}, @hash)
assert_equal({
comments: [
{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }
]
}, @root_hash)
end
def test_embed_objects_for_has_many_associations
@post_serializer_class.class_eval do
has_many :comments, embed: :objects
end
include_bare! :comments
assert_equal({
comments: [
{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }
]
}, @hash)
assert_equal({}, @root_hash)
end
def test_embed_ids_for_has_many_associations
@post_serializer_class.class_eval do
has_many :comments, embed: :ids
end
include_bare! :comments
assert_equal({
comment_ids: [ 1 ]
}, @hash)
assert_equal({}, @root_hash)
end
def test_embed_false_for_has_many_associations
@post_serializer_class.class_eval do
has_many :comments, embed: false
end
include_bare! :comments
assert_equal({}, @hash)
assert_equal({}, @root_hash)
end
def test_embed_ids_include_true_for_has_many_associations
@post_serializer_class.class_eval do
has_many :comments, embed: :ids, include: true
end
include_bare! :comments
assert_equal({
comment_ids: [ 1 ]
}, @hash)
assert_equal({
comments: [
{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }
]
}, @root_hash)
end
def test_embed_ids_for_has_one_associations
@post_serializer_class.class_eval do
has_one :comment, embed: :ids
end
include_bare! :comment
assert_equal({
comment_id: 1
}, @hash)
assert_equal({}, @root_hash)
end
def test_embed_false_for_has_one_associations
@post_serializer_class.class_eval do
has_one :comment, embed: false
end
include_bare! :comment
assert_equal({}, @hash)
assert_equal({}, @root_hash)
end
def test_embed_ids_include_true_for_has_one_associations
@post_serializer_class.class_eval do
has_one :comment, embed: :ids, include: true
end
include_bare! :comment
assert_equal({
comment_id: 1
}, @hash)
assert_equal({
comments: [
{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }
]
}, @root_hash)
end
def test_embed_ids_include_true_does_not_serialize_multiple_times
@post.recent_comment = @comment
@post_serializer_class.class_eval do
has_one :comment, embed: :ids, include: true
has_one :recent_comment, embed: :ids, include: true, root: :comments
end
# Count how often the @comment record is serialized.
serialized_times = 0
@comment.class_eval do
define_method :read_attribute_for_serialization, lambda { |name|
serialized_times += 1 if name == :body
super(name)
}
end
include_bare! :comment
include_bare! :recent_comment
assert_equal 1, serialized_times
end
def test_include_with_read_association_id_for_serialization_hook
@post_serializer_class.class_eval do
has_one :comment, embed: :ids, include: true
end
association_name = nil
@post.class_eval do
define_method :read_attribute_for_serialization, lambda { |name|
association_name = name
send(name)
}
define_method :comment_id, lambda {
@attributes[:comment].id
}
end
include_bare! :comment
assert_equal({
comment_id: 1
}, @hash)
end
def test_include_with_read_association_ids_for_serialization_hook
@post_serializer_class.class_eval do
has_many :comments, embed: :ids, include: false
end
association_name = nil
@post.class_eval do
define_method :read_attribute_for_serialization, lambda { |name|
association_name = name
send(name)
}
define_method :comment_ids, lambda {
@attributes[:comments].map(&:id)
}
end
include_bare! :comments
assert_equal({
comment_ids: [1]
}, @hash)
end
end
class RecursiveTest < AssociationTest
class BarSerializer < ActiveModel::Serializer; end
class FooSerializer < ActiveModel::Serializer
root :foos
attributes :id
has_many :bars, serializer: BarSerializer, root: :bars, embed: :ids, include: true
end
class BarSerializer < ActiveModel::Serializer
root :bars
attributes :id
has_many :foos, serializer: FooSerializer, root: :foos, embed: :ids, include: true
end
class Foo < Model
def active_model_serializer; FooSerializer; end
end
class Bar < Model
def active_model_serializer; BarSerializer; end
end
def setup
super
foo = Foo.new(id: 1)
bar = Bar.new(id: 2)
foo.bars = [ bar ]
bar.foos = [ foo ]
collection = [ foo ]
@serializer = collection.active_model_serializer.new(collection, root: :foos)
end
def test_mutual_relation_result
assert_equal({
foos: [{
bar_ids: [ 2 ],
id: 1
}],
bars: [{
foo_ids: [ 1 ],
id: 2
}]
}, @serializer.as_json)
end
def test_mutual_relation_does_not_raise_error
assert_nothing_raised SystemStackError, 'stack level too deep' do
@serializer.as_json
end
end
end
class InclusionTest < AssociationTest
def setup
super
comment_serializer_class = @comment_serializer_class
@post_serializer_class.class_eval do
root :post
embed :ids, include: true
has_many :comments, serializer: comment_serializer_class
end
end
def test_when_it_is_included
post_serializer = @post_serializer_class.new(
@post, include: [:comments]
)
json = post_serializer.as_json
assert_equal({
post: {
title: "New Post",
body: "Body",
comment_ids: [ 1 ]
},
comments: [
{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }
]
}, json)
end
def test_when_it_is_not_included
post_serializer = @post_serializer_class.new(
@post, include: []
)
json = post_serializer.as_json
assert_equal({
post: {
title: "New Post",
body: "Body",
comment_ids: [ 1 ]
}
}, json)
end
def test_when_it_is_excluded
post_serializer = @post_serializer_class.new(
@post, exclude: [:comments]
)
json = post_serializer.as_json
assert_equal({
post: {
title: "New Post",
body: "Body",
comment_ids: [ 1 ]
}
}, json)
end
def test_when_it_is_not_excluded
post_serializer = @post_serializer_class.new(
@post, exclude: []
)
json = post_serializer.as_json
assert_equal({
post: {
title: "New Post",
body: "Body",
comment_ids: [ 1 ]
},
comments: [
{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }
]
}, json)
end
end
class StringSerializerOption < AssociationTest
class StringSerializer < ActiveModel::Serializer
attributes :id, :body
end
def test_specifying_serializer_class_as_string
@post_serializer_class.class_eval do
has_many :comments, embed: :objects
end
include_bare! :comments, serializer: "AssociationTest::StringSerializerOption::StringSerializer"
assert_equal({
comments: [
{ id: 1, body: "ZOMG A COMMENT" }
]
}, @hash)
assert_equal({}, @root_hash)
end
end
end

View File

@ -1,96 +0,0 @@
require "test_helper"
class CachingTest < ActiveModel::TestCase
class NullStore
def fetch(key)
return store[key] if store[key]
store[key] = yield
end
def clear
store.clear
end
def store
@store ||= {}
end
def read(key)
store[key]
end
end
class Programmer
def name
'Adam'
end
def skills
%w(ruby)
end
def read_attribute_for_serialization(name)
send name
end
end
def test_serializers_have_a_cache_store
ActiveModel::Serializer.cache = NullStore.new
assert_kind_of NullStore, ActiveModel::Serializer.cache
end
def test_serializers_can_enable_caching
serializer = Class.new(ActiveModel::Serializer) do
cached true
end
assert serializer.perform_caching
end
def test_serializers_use_cache
serializer = Class.new(ActiveModel::Serializer) do
cached true
attributes :name, :skills
def self.to_s
'serializer'
end
def cache_key
object.name
end
end
serializer.cache = NullStore.new
instance = serializer.new Programmer.new
instance.to_json
assert_equal(instance.serializable_hash, serializer.cache.read('serializer/Adam/serialize'))
assert_equal(instance.to_json, serializer.cache.read('serializer/Adam/to-json'))
end
def test_array_serializer_uses_cache
serializer = Class.new(ActiveModel::ArraySerializer) do
cached true
def self.to_s
'array_serializer'
end
def cache_key
'cache-key'
end
end
serializer.cache = NullStore.new
instance = serializer.new [Programmer.new]
instance.to_json
assert_equal instance.serializable_array, serializer.cache.read('array_serializer/cache-key/serialize')
assert_equal instance.to_json, serializer.cache.read('array_serializer/cache-key/to-json')
end
end

12
test/coverage_setup.rb Normal file
View File

@ -0,0 +1,12 @@
require 'simplecov'
require 'coveralls'
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
SimpleCov::Formatter::HTMLFormatter,
Coveralls::SimpleCov::Formatter
]
SimpleCov.start do
add_group "lib", "lib"
add_group "test", "test"
end

92
test/fixtures/active_record.rb vendored Normal file
View File

@ -0,0 +1,92 @@
require 'active_record'
ActiveRecord::Base.establish_connection(
:adapter => 'sqlite3',
:database => ':memory:'
)
ActiveRecord::Schema.define do
create_table :ar_posts, force: true do |t|
t.string :title
t.text :body
t.belongs_to :ar_section, index: true
t.timestamps
end
create_table :ar_comments, force: true do |t|
t.text :body
t.belongs_to :ar_post, index: true
t.timestamps
end
create_table :ar_tags, force: true do |t|
t.string :name
end
create_table :ar_sections, force: true do |t|
t.string :name
end
create_table :ar_posts_tags, force: true do |t|
t.references :ar_post, :ar_tag, index: true
end
create_table :ar_comments_tags, force: true do |t|
t.references :ar_comment, :ar_tag, index: true
end
end
class ARPost < ActiveRecord::Base
has_many :ar_comments, class_name: 'ARComment'
has_and_belongs_to_many :ar_tags, class_name: 'ARTag', join_table: :ar_posts_tags
belongs_to :ar_section, class_name: 'ARSection'
end
class ARComment < ActiveRecord::Base
belongs_to :ar_post, class_name: 'ARPost'
has_and_belongs_to_many :ar_tags, class_name: 'ARTag', join_table: :ar_comments_tags
end
class ARTag < ActiveRecord::Base
end
class ARSection < ActiveRecord::Base
end
class ARPostSerializer < ActiveModel::Serializer
attributes :title, :body
has_many :ar_comments, :ar_tags
has_one :ar_section
end
class ARCommentSerializer < ActiveModel::Serializer
attributes :body
has_many :ar_tags
end
class ARTagSerializer < ActiveModel::Serializer
attributes :name
end
class ARSectionSerializer < ActiveModel::Serializer
attributes :name
end
ARPost.create(title: 'New post',
body: 'A body!!!',
ar_section: ARSection.create(name: 'ruby')).tap do |post|
short_tag = post.ar_tags.create(name: 'short')
whiny_tag = post.ar_tags.create(name: 'whiny')
happy_tag = post.ar_tags.create(name: 'happy')
post.ar_comments.create(body: 'what a dumb post').tap do |comment|
comment.ar_tags.concat short_tag, whiny_tag
end
post.ar_comments.create(body: 'i liked it').tap do |comment|
comment.ar_tags.concat short_tag, happy_tag
end
end

64
test/fixtures/poro.rb vendored Normal file
View File

@ -0,0 +1,64 @@
class Model
def initialize(hash={})
@attributes = hash
end
def read_attribute_for_serialization(name)
if name == :id || name == 'id'
object_id
else
@attributes[name]
end
end
end
###
## Models
###
class User < Model
def profile
@profile ||= Profile.new(name: 'N1', description: 'D1')
end
end
class Profile < Model
end
class Post < Model
def comments
@comments ||= [Comment.new(content: 'C1'),
Comment.new(content: 'C2')]
end
end
class Comment < Model
end
###
## Serializers
###
class UserSerializer < ActiveModel::Serializer
attributes :name, :email
has_one :profile
end
class ProfileSerializer < ActiveModel::Serializer
def description
description = object.read_attribute_for_serialization(:description)
scope ? "#{description} - #{scope}" : description
end
attributes :name, :description
end
class PostSerializer < ActiveModel::Serializer
attributes :title, :body
has_many :comments
end
class CommentSerializer < ActiveModel::Serializer
attributes :content
end

View File

@ -1,73 +0,0 @@
require 'test_helper'
class Foo < Rails::Application
if Rails.version.to_s.start_with? '4'
config.eager_load = false
config.secret_key_base = 'abc123'
end
end
Rails.application.load_generators
require 'generators/serializer/serializer_generator'
class SerializerGeneratorTest < Rails::Generators::TestCase
destination File.expand_path("../tmp", __FILE__)
setup :prepare_destination
tests Rails::Generators::SerializerGenerator
arguments %w(account name:string description:text business:references)
def test_generates_a_serializer
run_generator
assert_file "app/serializers/account_serializer.rb", /class AccountSerializer < ActiveModel::Serializer/
end
def test_generates_a_namespaced_serializer
run_generator ["admin/account"]
assert_file "app/serializers/admin/account_serializer.rb", /class Admin::AccountSerializer < ActiveModel::Serializer/
end
def test_uses_application_serializer_if_one_exists
Object.const_set(:ApplicationSerializer, Class.new)
run_generator
assert_file "app/serializers/account_serializer.rb", /class AccountSerializer < ApplicationSerializer/
ensure
Object.send :remove_const, :ApplicationSerializer
end
# def test_uses_namespace_application_serializer_if_one_exists
# Object.const_set(:SerializerNamespace, Module.new)
# SerializerNamespace.const_set(:ApplicationSerializer, Class.new)
# Rails::Generators.namespace = SerializerNamespace
# run_generator
# assert_file "app/serializers/serializer_namespace/account_serializer.rb",
# /module SerializerNamespace\n class AccountSerializer < ApplicationSerializer/
# ensure
# Object.send :remove_const, :SerializerNamespace
# Rails::Generators.namespace = nil
# end
def test_uses_given_parent
Object.const_set(:ApplicationSerializer, Class.new)
run_generator ["Account", "--parent=MySerializer"]
assert_file "app/serializers/account_serializer.rb", /class AccountSerializer < MySerializer/
ensure
Object.send :remove_const, :ApplicationSerializer
end
def test_generates_attributes_and_associations
run_generator
assert_file "app/serializers/account_serializer.rb" do |serializer|
assert_match(/^ attributes :id, :name, :description$/, serializer)
assert_match(/^ has_one :business$/, serializer)
end
end
def test_with_no_attributes_does_not_add_extra_space
run_generator ["account"]
assert_file "app/serializers/account_serializer.rb" do |content|
assert_no_match /\n\nend/, content
end
end
end

View File

@ -0,0 +1,181 @@
require 'test_helper'
module ActionController
module Serialization
class ImplicitSerializerTest < ActionController::TestCase
class MyController < ActionController::Base
def render_using_implicit_serializer
render json: Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })
end
end
tests MyController
def test_render_using_implicit_serializer
get :render_using_implicit_serializer
assert_equal 'application/json', @response.content_type
assert_equal '{"profile":{"name":"Name 1","description":"Description 1"}}', @response.body
end
end
class ImplicitSerializerScopeTest < ActionController::TestCase
class MyController < ActionController::Base
def render_using_implicit_serializer_and_scope
render json: Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })
end
private
def current_user
'current_user'
end
end
tests MyController
def test_render_using_implicit_serializer_and_scope
get :render_using_implicit_serializer_and_scope
assert_equal 'application/json', @response.content_type
assert_equal '{"profile":{"name":"Name 1","description":"Description 1 - current_user"}}', @response.body
end
end
class DefaultOptionsForSerializerScopeTest < ActionController::TestCase
class MyController < ActionController::Base
def default_serializer_options
{ scope: current_admin }
end
def render_using_scope_set_in_default_serializer_options
render json: Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })
end
private
def current_user
'current_user'
end
def current_admin
'current_admin'
end
end
tests MyController
def test_render_using_scope_set_in_default_serializer_options
get :render_using_scope_set_in_default_serializer_options
assert_equal 'application/json', @response.content_type
assert_equal '{"profile":{"name":"Name 1","description":"Description 1 - current_admin"}}', @response.body
end
end
class ExplicitSerializerScopeTest < ActionController::TestCase
class MyController < ActionController::Base
def render_using_implicit_serializer_and_explicit_scope
render json: Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }), scope: current_admin
end
private
def current_user
'current_user'
end
def current_admin
'current_admin'
end
end
tests MyController
def test_render_using_implicit_serializer_and_explicit_scope
get :render_using_implicit_serializer_and_explicit_scope
assert_equal 'application/json', @response.content_type
assert_equal '{"profile":{"name":"Name 1","description":"Description 1 - current_admin"}}', @response.body
end
end
class OverridingSerializationScopeTest < ActionController::TestCase
class MyController < ActionController::Base
def render_overriding_serialization_scope
render json: Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })
end
private
def current_user
'current_user'
end
def serialization_scope
'current_admin'
end
end
tests MyController
def test_render_overriding_serialization_scope
get :render_overriding_serialization_scope
assert_equal 'application/json', @response.content_type
assert_equal '{"profile":{"name":"Name 1","description":"Description 1 - current_admin"}}', @response.body
end
end
class CallingSerializationScopeTest < ActionController::TestCase
class MyController < ActionController::Base
def render_calling_serialization_scope
render json: Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })
end
private
def current_user
'current_user'
end
serialization_scope :current_user
end
tests MyController
def test_render_calling_serialization_scope
get :render_calling_serialization_scope
assert_equal 'application/json', @response.content_type
assert_equal '{"profile":{"name":"Name 1","description":"Description 1 - current_user"}}', @response.body
end
end
class RailsSerializerTest < ActionController::TestCase
class MyController < ActionController::Base
def render_using_rails_behavior
render json: JSON.dump(hello: 'world')
end
end
tests MyController
def test_render_using_rails_behavior
get :render_using_rails_behavior
assert_equal 'application/json', @response.content_type
assert_equal '{"hello":"world"}', @response.body
end
end
class ArraySerializerTest < ActionController::TestCase
class MyController < ActionController::Base
def render_array
render json: [Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })]
end
end
tests MyController
def test_render_array
get :render_array
assert_equal 'application/json', @response.content_type
assert_equal '{"my":[{"name":"Name 1","description":"Description 1"}]}', @response.body
end
end
end
end

View File

@ -0,0 +1,75 @@
require 'test_helper'
require 'fixtures/active_record'
module ActiveModel
class Serializer
class ActiveRecordTest < ActiveModel::TestCase
def setup
@post = ARPost.first
end
def test_serialization_embedding_objects
post_serializer = ARPostSerializer.new(@post)
assert_equal({
'ar_post' => {
title: 'New post', body: 'A body!!!',
ar_comments: [{ body: 'what a dumb post', ar_tags: [{ name: 'short' }, { name: 'whiny' }] },
{ body: 'i liked it', ar_tags: [{:name=>"short"}, {:name=>"happy"}] }],
ar_tags: [{ name: 'short' }, { name: 'whiny' }, { name: 'happy' }],
ar_section: { name: 'ruby' }
}
}, post_serializer.as_json)
end
def test_serialization_embedding_ids
post_serializer = ARPostSerializer.new(@post)
embed(ARPostSerializer, embed: :ids) do
assert_equal({
'ar_post' => {
title: 'New post', body: 'A body!!!',
'ar_comment_ids' => [1, 2],
'ar_tag_ids' => [1, 2, 3],
'ar_section_id' => 1
}
}, post_serializer.as_json)
end
end
def test_serialization_embedding_ids_including_in_root
post_serializer = ARPostSerializer.new(@post)
embed(ARPostSerializer, embed: :ids, include: true) do
assert_equal({
'ar_post' => {
title: 'New post', body: 'A body!!!',
'ar_comment_ids' => [1, 2],
'ar_tag_ids' => [1, 2, 3],
'ar_section_id' => 1
},
ar_comments: [{ body: 'what a dumb post', ar_tags: [{ name: 'short' }, { name: 'whiny' }] },
{ body: 'i liked it', ar_tags: [{:name=>"short"}, {:name=>"happy"}] }],
ar_tags: [{ name: 'short' }, { name: 'whiny' }, { name: 'happy' }],
ar_section: { name: 'ruby' }
}, post_serializer.as_json)
end
end
private
def embed(klass, options = {})
old_assocs = Hash[ARPostSerializer._associations.to_a.map { |(name, association)| [name, association.dup] }]
ARPostSerializer._associations.each_value do |association|
association.embed = options[:embed]
association.embed_in_root = options[:include]
end
yield
ensure
ARPostSerializer._associations = old_assocs
end
end
end
end

View File

@ -0,0 +1,27 @@
require 'test_helper'
require 'rails'
require 'test_app'
require 'rails/generators/rails/resource/resource_generator'
require 'active_model/serializer/generators/resource_override'
class ResourceGeneratorTest < Rails::Generators::TestCase
destination File.expand_path('../../../tmp', __FILE__)
setup :prepare_destination, :copy_routes
tests Rails::Generators::ResourceGenerator
arguments %w(account)
def test_serializer_file_is_generated
run_generator
assert_file 'app/serializers/account_serializer.rb', /class AccountSerializer < ActiveModel::Serializer/
end
private
def copy_routes
config_dir = File.join(destination_root, 'config')
FileUtils.mkdir_p(config_dir)
File.write(File.join(config_dir, 'routes.rb'), 'Rails.application.routes.draw { }')
end
end

View File

@ -0,0 +1,41 @@
require 'test_helper'
require 'rails'
require 'test_app'
require 'active_model/serializer/generators/serializer/serializer_generator'
class SerializerGeneratorTest < Rails::Generators::TestCase
destination File.expand_path('../../../tmp', __FILE__)
setup :prepare_destination
tests Rails::Generators::SerializerGenerator
arguments %w(account name:string description:text business:references)
def test_generates_a_serializer_with_attributes_and_associations
run_generator
assert_file 'app/serializers/account_serializer.rb', /class AccountSerializer < ActiveModel::Serializer/ do |serializer|
assert_match(/attributes :id, :name, :description/, serializer)
assert_match(/has_one :business/, serializer)
end
end
def test_generates_a_namespaced_serializer
run_generator ['admin/account']
assert_file 'app/serializers/admin/account_serializer.rb', /class Admin::AccountSerializer < ActiveModel::Serializer/
end
def test_uses_application_serializer_if_one_exists
Object.const_set(:ApplicationSerializer, Class.new)
run_generator
assert_file 'app/serializers/account_serializer.rb', /class AccountSerializer < ApplicationSerializer/
ensure
Object.send :remove_const, :ApplicationSerializer
end
def test_uses_given_parent
Object.const_set(:ApplicationSerializer, Class.new)
run_generator ['Account', '--parent=MySerializer']
assert_file 'app/serializers/account_serializer.rb', /class AccountSerializer < MySerializer/
ensure
Object.send :remove_const, :ApplicationSerializer
end
end

View File

@ -1,34 +0,0 @@
require "test_helper"
class NoSerializationScopeTest < ActionController::TestCase
class ScopeSerializer
def initialize(object, options)
@object, @options = object, options
end
def as_json(*)
{ scope: @options[:scope].as_json }
end
end
class ScopeSerializable
def active_model_serializer
ScopeSerializer
end
end
class NoSerializationScopeController < ActionController::Base
serialization_scope nil
def index
render json: ScopeSerializable.new
end
end
tests NoSerializationScopeController
def test_disabled_serialization_scope
get :index, format: :json
assert_equal '{"scope":null}', @response.body
end
end

View File

@ -1,99 +0,0 @@
require 'test_helper'
require 'pathname'
class DefaultScopeNameTest < ActionController::TestCase
TestUser = Struct.new(:name, :admin)
class UserSerializer < ActiveModel::Serializer
attributes :admin?
def admin?
current_user.admin
end
end
class UserTestController < ActionController::Base
protect_from_forgery
before_filter { request.format = :json }
def current_user
TestUser.new('Pete', false)
end
def render_new_user
render json: TestUser.new('pete', false), serializer: UserSerializer
end
end
tests UserTestController
def test_default_scope_name
get :render_new_user
assert_equal '{"user":{"admin":false}}', @response.body
end
end
class SerializationScopeNameTest < ActionController::TestCase
TestUser = Struct.new(:name, :admin)
class AdminUserSerializer < ActiveModel::Serializer
attributes :admin?
def admin?
current_admin.admin
end
end
class AdminUserTestController < ActionController::Base
protect_from_forgery
serialization_scope :current_admin
before_filter { request.format = :json }
def current_admin
TestUser.new('Bob', true)
end
def render_new_user
render json: TestUser.new('pete', false), serializer: AdminUserSerializer
end
end
tests AdminUserTestController
def test_override_scope_name_with_controller
get :render_new_user
assert_equal '{"admin_user":{"admin":true}}', @response.body
end
end
class SerializationActionScopeOverrideTest < ActionController::TestCase
TestUser = Struct.new(:name, :admin)
class AdminUserSerializer < ActiveModel::Serializer
attributes :admin?
def admin?
current_admin.admin
end
end
class AdminUserTestController < ActionController::Base
protect_from_forgery
before_filter { request.format = :json }
def current_admin
TestUser.new('Bob', true)
end
def render_new_user
render json: TestUser.new('pete', false), serializer: AdminUserSerializer, scope: current_admin, scope_name: :current_admin
end
end
tests AdminUserTestController
def test_override_scope_name_with_controller
get :render_new_user
assert_equal '{"admin_user":{"admin":true}}', @response.body
end
end

View File

@ -1,394 +0,0 @@
require 'test_helper'
require 'pathname'
class RenderJsonTest < ActionController::TestCase
class JsonRenderable
def as_json(options={})
hash = { a: :b, c: :d, e: :f }
hash.except!(*options[:except]) if options[:except]
hash
end
def to_json(options = {})
super except: [:c, :e]
end
end
class JsonSerializer
def initialize(object, options={})
@object, @options = object, options
end
def as_json(*)
hash = { object: serializable_hash, scope: @options[:scope].as_json }
hash.merge!(options: true) if @options[:options]
hash.merge!(check_defaults: true) if @options[:check_defaults]
hash
end
def serializable_hash
@object.as_json
end
end
class JsonSerializable
def initialize(skip=false)
@skip = skip
end
def active_model_serializer
JsonSerializer unless @skip
end
def as_json(*)
{ serializable_object: true }
end
end
class CustomSerializer
def initialize(*)
end
def as_json(*)
{ hello: true }
end
end
class AnotherCustomSerializer
def initialize(*)
end
def as_json(*)
{ rails: 'rocks' }
end
end
class DummyCustomSerializer < ActiveModel::Serializer
attributes :id
end
class HypermediaSerializable
def active_model_serializer
HypermediaSerializer
end
end
class HypermediaSerializer < ActiveModel::Serializer
def as_json(*)
{ link: hypermedia_url }
end
end
class CustomArraySerializer < ActiveModel::ArraySerializer
self.root = "items"
end
class TestController < ActionController::Base
protect_from_forgery
serialization_scope :current_user
attr_reader :current_user
def self.controller_path
'test'
end
def render_json_nil
render json: nil
end
def render_json_render_to_string
render text: render_to_string(json: '[]')
end
def render_json_hello_world
render json: ActiveSupport::JSON.encode(hello: 'world')
end
def render_json_hello_world_with_status
render json: ActiveSupport::JSON.encode(hello: 'world'), status: 401
end
def render_json_hello_world_with_callback
render json: ActiveSupport::JSON.encode(hello: 'world'), callback: 'alert'
end
def render_json_with_custom_content_type
render json: ActiveSupport::JSON.encode(hello: 'world'), content_type: 'text/javascript'
end
def render_symbol_json
render json: ActiveSupport::JSON.encode(hello: 'world')
end
def render_json_nil_with_custom_serializer
render json: nil, serializer: DummyCustomSerializer
end
def render_json_with_extra_options
render json: JsonRenderable.new, except: [:c, :e]
end
def render_json_without_options
render json: JsonRenderable.new
end
def render_json_with_serializer
@current_user = Struct.new(:as_json).new(current_user: true)
render json: JsonSerializable.new
end
def render_json_with_serializer_and_implicit_root
@current_user = Struct.new(:as_json).new(current_user: true)
render json: [JsonSerializable.new]
end
def render_json_with_serializer_and_options
@current_user = Struct.new(:as_json).new(current_user: true)
render json: JsonSerializable.new, options: true
end
def render_json_with_serializer_and_scope_option
@current_user = Struct.new(:as_json).new(current_user: true)
scope = Struct.new(:as_json).new(current_user: false)
render json: JsonSerializable.new, scope: scope
end
def render_json_with_serializer_api_but_without_serializer
@current_user = Struct.new(:as_json).new(current_user: true)
render json: JsonSerializable.new(true)
end
# To specify a custom serializer for an object, use :serializer.
def render_json_with_custom_serializer
render json: Object.new, serializer: CustomSerializer
end
# To specify a custom serializer for each item in the Array, use :each_serializer.
def render_json_array_with_custom_serializer
render json: [Object.new], each_serializer: CustomSerializer
end
def render_json_array_with_wrong_option
render json: [Object.new], serializer: CustomSerializer
end
def render_json_with_links
render json: HypermediaSerializable.new
end
def render_json_array_with_no_root
render json: [], root: false
end
def render_json_empty_array
render json: []
end
def render_json_array_with_custom_array_serializer
render json: [], serializer: CustomArraySerializer
end
private
def default_serializer_options
defaults = {}
defaults.merge!(check_defaults: true) if params[:check_defaults]
defaults.merge!(root: :awesome) if params[:check_default_root]
defaults.merge!(scope: :current_admin) if params[:check_default_scope]
defaults.merge!(serializer: AnotherCustomSerializer) if params[:check_default_serializer]
defaults.merge!(each_serializer: AnotherCustomSerializer) if params[:check_default_each_serializer]
defaults
end
end
tests TestController
def setup
# enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
# a more accurate simulation of what happens in "real life".
super
@controller.logger = Logger.new(nil)
@request.host = "www.nextangle.com"
end
def test_render_json_nil
get :render_json_nil
assert_equal 'null', @response.body
assert_equal 'application/json', @response.content_type
end
def test_render_json_render_to_string
get :render_json_render_to_string
assert_equal '[]', @response.body
end
def test_render_json_nil_with_custom_serializer
get :render_json_nil_with_custom_serializer
assert_equal "{\"dummy_custom\":null}", @response.body
end
def test_render_json
get :render_json_hello_world
assert_equal '{"hello":"world"}', @response.body
assert_equal 'application/json', @response.content_type
end
def test_render_json_with_status
get :render_json_hello_world_with_status
assert_equal '{"hello":"world"}', @response.body
assert_equal 401, @response.status
end
def test_render_json_with_callback
get :render_json_hello_world_with_callback
assert_equal 'alert({"hello":"world"})', @response.body
# For JSONP, Rails 3 uses application/json, but Rails 4 uses text/javascript
assert_match %r(application/json|text/javascript), @response.content_type.to_s
end
def test_render_json_with_custom_content_type
get :render_json_with_custom_content_type
assert_equal '{"hello":"world"}', @response.body
assert_equal 'text/javascript', @response.content_type
end
def test_render_symbol_json
get :render_symbol_json
assert_equal '{"hello":"world"}', @response.body
assert_equal 'application/json', @response.content_type
end
def test_render_json_forwards_extra_options
get :render_json_with_extra_options
assert_equal '{"a":"b"}', @response.body
assert_equal 'application/json', @response.content_type
end
def test_render_json_calls_to_json_from_object
get :render_json_without_options
assert_equal '{"a":"b"}', @response.body
end
def test_render_json_with_serializer
get :render_json_with_serializer
assert_match '"scope":{"current_user":true}', @response.body
assert_match '"object":{"serializable_object":true}', @response.body
end
def test_render_json_with_serializer_checking_defaults
get :render_json_with_serializer, check_defaults: true
assert_match '"scope":{"current_user":true}', @response.body
assert_match '"object":{"serializable_object":true}', @response.body
assert_match '"check_defaults":true', @response.body
end
def test_render_json_with_serializer_checking_default_serailizer
get :render_json_with_serializer, check_default_serializer: true
assert_match '{"rails":"rocks"}', @response.body
end
def test_render_json_with_serializer_checking_default_scope
get :render_json_with_serializer, check_default_scope: true
assert_match '"scope":"current_admin"', @response.body
end
def test_render_json_with_serializer_and_implicit_root
get :render_json_with_serializer_and_implicit_root
assert_match '"test":[{"serializable_object":true}]', @response.body
end
def test_render_json_with_serializer_and_implicit_root_checking_default_each_serailizer
get :render_json_with_serializer_and_implicit_root, check_default_each_serializer: true
assert_match '"test":[{"rails":"rocks"}]', @response.body
end
def test_render_json_with_serializer_and_options
get :render_json_with_serializer_and_options
assert_match '"scope":{"current_user":true}', @response.body
assert_match '"object":{"serializable_object":true}', @response.body
assert_match '"options":true', @response.body
end
def test_render_json_with_serializer_and_scope_option
get :render_json_with_serializer_and_scope_option
assert_match '"scope":{"current_user":false}', @response.body
end
def test_render_json_with_serializer_and_scope_option_checking_default_scope
get :render_json_with_serializer_and_scope_option, check_default_scope: true
assert_match '"scope":{"current_user":false}', @response.body
end
def test_render_json_with_serializer_api_but_without_serializer
get :render_json_with_serializer_api_but_without_serializer
assert_match '{"serializable_object":true}', @response.body
end
def test_render_json_with_custom_serializer
get :render_json_with_custom_serializer
assert_match '{"hello":true}', @response.body
end
def test_render_json_with_custom_serializer_checking_default_serailizer
get :render_json_with_custom_serializer, check_default_serializer: true
assert_match '{"hello":true}', @response.body
end
def test_render_json_array_with_custom_serializer
get :render_json_array_with_custom_serializer
assert_match '{"test":[{"hello":true}]}', @response.body
end
def test_render_json_array_with_wrong_option
assert_raise ArgumentError do
get :render_json_array_with_wrong_option
end
end
def test_render_json_array_with_custom_serializer_checking_default_each_serailizer
get :render_json_array_with_custom_serializer, check_default_each_serializer: true
assert_match '{"test":[{"hello":true}]}', @response.body
end
def test_render_json_with_links
get :render_json_with_links
assert_match '{"link":"http://www.nextangle.com/hypermedia"}', @response.body
end
def test_render_json_array_with_no_root
get :render_json_array_with_no_root
assert_equal '[]', @response.body
end
def test_render_json_array_with_no_root_checking_default_root
get :render_json_array_with_no_root, check_default_root: true
assert_equal '[]', @response.body
end
def test_render_json_empty_array
get :render_json_empty_array
assert_equal '{"test":[]}', @response.body
end
def test_render_json_empty_array_checking_default_root
get :render_json_empty_array, check_default_root: true
assert_equal '{"awesome":[]}', @response.body
end
def test_render_json_empty_array_with_array_serializer_root_false
ActiveModel::ArraySerializer.root = false
get :render_json_empty_array
assert_equal '[]', @response.body
ensure # teardown
ActiveModel::ArraySerializer.root = nil
end
def test_render_json_array_with_custom_array_serializer
get :render_json_array_with_custom_array_serializer
assert_equal '{"items":[]}', @response.body
end
end

View File

@ -1,51 +0,0 @@
require "test_helper"
class RandomModel
include ActiveModel::SerializerSupport
end
class OtherRandomModel
include ActiveModel::SerializerSupport
end
class OtherRandomModelSerializer
end
class RandomModelCollection
include ActiveModel::ArraySerializerSupport
end
module ActiveRecord
class Relation
end
end
module Mongoid
class Criteria
end
end
class SerializerSupportTest < ActiveModel::TestCase
test "it returns nil if no serializer exists" do
assert_equal nil, RandomModel.new.active_model_serializer
end
test "it returns a deducted serializer if it exists exists" do
assert_equal OtherRandomModelSerializer, OtherRandomModel.new.active_model_serializer
end
test "it returns ArraySerializer for a collection" do
assert_equal ActiveModel::ArraySerializer, RandomModelCollection.new.active_model_serializer
end
test "it automatically includes array_serializer in active_record/relation" do
ActiveSupport.run_load_hooks(:active_record)
assert_equal ActiveModel::ArraySerializer, ActiveRecord::Relation.new.active_model_serializer
end
test "it automatically includes array_serializer in mongoid/criteria" do
ActiveSupport.run_load_hooks(:mongoid)
assert_equal ActiveModel::ArraySerializer, Mongoid::Criteria.new.active_model_serializer
end
end

File diff suppressed because it is too large Load Diff

8
test/test_app.rb Normal file
View File

@ -0,0 +1,8 @@
class TestApp < Rails::Application
if Rails.version.to_s.first >= '4'
config.eager_load = false
config.secret_key_base = 'abc123'
end
end
TestApp.load_generators

View File

@ -1,202 +0,0 @@
class Model
def initialize(hash={})
@attributes = hash
end
def read_attribute_for_serialization(name)
@attributes[name]
end
def as_json(*)
{ model: "Model" }
end
end
class ModelWithActiveModelSerializer < Model
include ActiveModel::Serializers::JSON
attr_accessor :attributes
def read_attribute_for_serialization(name)
@attributes[name]
end
end
class User
include ActiveModel::SerializerSupport
attr_accessor :superuser
def initialize(hash={})
@attributes = hash.merge(first_name: "Jose", last_name: "Valim", password: "oh noes yugive my password")
end
def read_attribute_for_serialization(name)
@attributes[name]
end
def super_user?
@superuser
end
end
class Post < Model
def initialize(attributes)
super(attributes)
self.comments ||= []
self.comments_disabled = false
self.author = nil
end
attr_accessor :comments, :comments_disabled, :author
def active_model_serializer; PostSerializer; end
end
class Comment < Model
def active_model_serializer; CommentSerializer; end
end
class UserSerializer < ActiveModel::Serializer
attributes :first_name, :last_name
def serializable_hash
attributes.merge(ok: true).merge(options[:scope])
end
end
class UserAttributesWithKeySerializer < ActiveModel::Serializer
attributes first_name: :f_name, last_name: :l_name
def serializable_hash
attributes.merge(ok: true).merge(options[:scope])
end
end
class UserAttributesWithSomeKeySerializer < ActiveModel::Serializer
attributes :first_name, last_name: :l_name
def serializable_hash
attributes.merge(ok: true).merge(options[:scope])
end
end
class UserAttributesWithUnsymbolizableKeySerializer < ActiveModel::Serializer
attributes :first_name, last_name: :"last-name"
def serializable_hash
attributes.merge(ok: true).merge(options[:scope])
end
end
class DefaultUserSerializer < ActiveModel::Serializer
attributes :first_name, :last_name
end
class MyUserSerializer < ActiveModel::Serializer
attributes :first_name, :last_name
def serializable_hash
hash = attributes
hash = hash.merge(super_user: true) if object.super_user?
hash
end
end
class CommentSerializer
def initialize(comment, options={})
@object = comment
end
attr_reader :object
def serializable_hash
{ title: @object.read_attribute_for_serialization(:title) }
end
def as_json(options=nil)
options ||= {}
if options[:root] == false
serializable_hash
else
{ comment: serializable_hash }
end
end
end
class PostSerializer < ActiveModel::Serializer
attributes :title, :body
has_many :comments, serializer: CommentSerializer
end
class PostWithConditionalCommentsSerializer < ActiveModel::Serializer
root :post
attributes :title, :body
has_many :comments, serializer: CommentSerializer
def include_associations!
include! :comments unless object.comments_disabled
end
end
class PostWithMultipleConditionalsSerializer < ActiveModel::Serializer
root :post
attributes :title, :body, :author
has_many :comments, serializer: CommentSerializer
def include_comments?
!object.comments_disabled
end
def include_author?
scope.super_user?
end
end
class Blog < Model
attr_accessor :author
end
class AuthorSerializer < ActiveModel::Serializer
attributes :first_name, :last_name
end
class BlogSerializer < ActiveModel::Serializer
has_one :author, serializer: AuthorSerializer
end
class BlogWithRootSerializer < BlogSerializer
root true
end
class CustomPostSerializer < ActiveModel::Serializer
attributes :title
end
class CustomBlog < Blog
attr_accessor :public_posts, :public_user
end
class CustomBlogSerializer < ActiveModel::Serializer
has_many :public_posts, key: :posts, serializer: PostSerializer
has_one :public_user, key: :user, serializer: UserSerializer
end
class SomeSerializer < ActiveModel::Serializer
attributes :some
end
class SomeObject < Struct.new(:some)
end
# Set up some classes for polymorphic testing
class Attachment < Model
def attachable
@attributes[:attachable]
end
def readable
@attributes[:readable]
end
def edible
@attributes[:edible]
end
end

View File

@ -1,41 +1,21 @@
require "rubygems"
require "bundler/setup"
require 'simplecov'
SimpleCov.start do
add_group "lib", "lib"
add_group "spec", "spec"
end
require 'coveralls'
Coveralls.wear!
require "pry"
require "active_model_serializers"
require "active_support/json"
require "minitest/autorun"
require 'rails'
require 'bundler/setup'
require 'coverage_setup'
require 'minitest/autorun'
require 'active_model_serializers'
require 'fixtures/poro'
module TestHelper
Routes = ActionDispatch::Routing::RouteSet.new
Routes.draw do
resource :hypermedia
get ':controller(/:action(/:id))'
get ':controller(/:action)'
end
ActionController::Base.send :include, Routes.url_helpers
ActiveModel::Serializer.send :include, Routes.url_helpers
end
ActiveSupport::TestCase.class_eval do
setup do
@routes = ::TestHelper::Routes
ActionController::TestCase.class_eval do
def setup
@routes = TestHelper::Routes
end
end
class Object
undef_method :id if respond_to?(:id)
end

View File

@ -0,0 +1,53 @@
require 'test_helper'
require 'active_model/serializer'
module ActiveModel
class ArraySerializer
class MetaTest < ActiveModel::TestCase
def setup
@profile1 = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })
@profile2 = Profile.new({ name: 'Name 2', description: 'Description 2', comments: 'Comments 2' })
@serializer = ArraySerializer.new([@profile1, @profile2], root: 'profiles')
end
def test_meta
@serializer.meta = { total: 10 }
assert_equal({
'profiles' => [
{
name: 'Name 1',
description: 'Description 1'
}, {
name: 'Name 2',
description: 'Description 2'
}
],
meta: {
total: 10
}
}, @serializer.as_json)
end
def test_meta_using_meta_key
@serializer.meta_key = :my_meta
@serializer.meta = { total: 10 }
assert_equal({
'profiles' => [
{
name: 'Name 1',
description: 'Description 1'
}, {
name: 'Name 2',
description: 'Description 2'
}
],
my_meta: {
total: 10
}
}, @serializer.as_json)
end
end
end
end

View File

@ -0,0 +1,102 @@
require 'test_helper'
module ActiveModel
class ArraySerializer
class RootAsOptionTest < ActiveModel::TestCase
def setup
@old_root = ArraySerializer._root
@profile1 = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })
@profile2 = Profile.new({ name: 'Name 2', description: 'Description 2', comments: 'Comments 2' })
@serializer = ArraySerializer.new([@profile1, @profile2], root: :initialize)
end
def teardown
ArraySerializer._root = @old_root
end
def test_root_is_not_displayed_using_serializable_array
assert_equal([
{ name: 'Name 1', description: 'Description 1' },
{ name: 'Name 2', description: 'Description 2' }
], @serializer.serializable_array)
end
def test_root_using_as_json
assert_equal({
initialize: [
{ name: 'Name 1', description: 'Description 1' },
{ name: 'Name 2', description: 'Description 2' }
]
}, @serializer.as_json)
end
def test_root_as_argument_takes_precedence
assert_equal({
argument: [
{ name: 'Name 1', description: 'Description 1' },
{ name: 'Name 2', description: 'Description 2' }
]
}, @serializer.as_json(root: :argument))
end
def test_using_false_root_in_initialize_takes_precedence
ArraySerializer._root = 'root'
@serializer = ArraySerializer.new([@profile1, @profile2], root: false)
assert_equal([
{ name: 'Name 1', description: 'Description 1' },
{ name: 'Name 2', description: 'Description 2' }
], @serializer.as_json)
end
end
class RootInSerializerTest < ActiveModel::TestCase
def setup
@old_root = ArraySerializer._root
ArraySerializer._root = :in_serializer
@profile1 = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })
@profile2 = Profile.new({ name: 'Name 2', description: 'Description 2', comments: 'Comments 2' })
@serializer = ArraySerializer.new([@profile1, @profile2])
@rooted_serializer = ArraySerializer.new([@profile1, @profile2], root: :initialize)
end
def teardown
ArraySerializer._root = @old_root
end
def test_root_is_not_displayed_using_serializable_hash
assert_equal([
{ name: 'Name 1', description: 'Description 1' },
{ name: 'Name 2', description: 'Description 2' }
], @serializer.serializable_array)
end
def test_root_using_as_json
assert_equal({
in_serializer: [
{ name: 'Name 1', description: 'Description 1' },
{ name: 'Name 2', description: 'Description 2' }
]
}, @serializer.as_json)
end
def test_root_in_initializer_takes_precedence
assert_equal({
initialize: [
{ name: 'Name 1', description: 'Description 1' },
{ name: 'Name 2', description: 'Description 2' }
]
}, @rooted_serializer.as_json)
end
def test_root_as_argument_takes_precedence
assert_equal({
argument: [
{ name: 'Name 1', description: 'Description 1' },
{ name: 'Name 2', description: 'Description 2' }
]
}, @rooted_serializer.as_json(root: :argument))
end
end
end
end

View File

@ -0,0 +1,24 @@
require 'test_helper'
module ActiveModel
class ArraySerializer
class ScopeTest < ActiveModel::TestCase
def test_array_serializer_pass_options_to_items_serializers
array = [Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }),
Profile.new({ name: 'Name 2', description: 'Description 2', comments: 'Comments 2' })]
serializer = ArraySerializer.new(array, scope: current_user)
expected = [{ name: 'Name 1', description: 'Description 1 - user' },
{ name: 'Name 2', description: 'Description 2 - user' }]
assert_equal expected, serializer.serializable_array
end
private
def current_user
'user'
end
end
end
end

View File

@ -0,0 +1,45 @@
require 'test_helper'
module ActiveModel
class ArraySerializer
class SerializeTest < ActiveModel::TestCase
def setup
array = [1, 2, 3]
@serializer = ActiveModel::Serializer.serializer_for(array).new(array)
end
def test_serializer_for_array_returns_appropriate_type
assert_kind_of ArraySerializer, @serializer
end
def test_array_serializer_serializes_simple_objects
assert_equal [1, 2, 3], @serializer.serializable_array
assert_equal [1, 2, 3], @serializer.as_json
end
def test_array_serializer_serializes_models
array = [Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }),
Profile.new({ name: 'Name 2', description: 'Description 2', comments: 'Comments 2' })]
serializer = ArraySerializer.new(array)
expected = [{ name: 'Name 1', description: 'Description 1' },
{ name: 'Name 2', description: 'Description 2' }]
assert_equal expected, serializer.serializable_array
assert_equal expected, serializer.as_json
end
def test_array_serializers_each_serializer
array = [::Model.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }),
::Model.new({ name: 'Name 2', description: 'Description 2', comments: 'Comments 2' })]
serializer = ArraySerializer.new(array, each_serializer: ProfileSerializer)
expected = [{ name: 'Name 1', description: 'Description 1' },
{ name: 'Name 2', description: 'Description 2' }]
assert_equal expected, serializer.serializable_array
assert_equal expected, serializer.as_json
end
end
end
end

View File

@ -0,0 +1,15 @@
require 'test_helper'
module ActiveModel
class DefaultSerializer
class Test < ActiveModel::TestCase
def test_serialize_objects
assert_equal(nil, DefaultSerializer.new(nil).serializable_hash)
assert_equal(1, DefaultSerializer.new(1).serializable_hash)
assert_equal('hi', DefaultSerializer.new('hi').serializable_hash)
obj = Struct.new(:a, :b).new(1, 2)
assert_equal({ a: 1, b: 2 }, DefaultSerializer.new(obj).serializable_hash)
end
end
end
end

View File

@ -0,0 +1,29 @@
require 'test_helper'
module ActiveModel
class Serializer
class AttributesTest < ActiveModel::TestCase
def setup
@profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })
@profile_serializer = ProfileSerializer.new(@profile)
end
def test_attributes_definition
assert_equal([:name, :description],
@profile_serializer.class._attributes)
end
def test_attributes_serialization_using_serializable_hash
assert_equal({
name: 'Name 1', description: 'Description 1'
}, @profile_serializer.serializable_hash)
end
def test_attributes_serialization_using_as_json
assert_equal({
'profile' => { name: 'Name 1', description: 'Description 1' }
}, @profile_serializer.as_json)
end
end
end
end

View File

@ -0,0 +1,49 @@
require 'test_helper'
module ActiveModel
class Serializer
class FilterAttributesTest < ActiveModel::TestCase
def setup
@profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })
@profile_serializer = ProfileSerializer.new(@profile)
@profile_serializer.instance_eval do
def filter(keys)
keys - [:description]
end
end
end
def test_filtered_attributes_serialization
assert_equal({
'profile' => { name: 'Name 1' }
}, @profile_serializer.as_json)
end
end
class FilterAssociationsTest < ActiveModel::TestCase
def setup
@association = PostSerializer._associations[:comments]
@old_association = @association.dup
@association.embed = :ids
@association.embed_in_root = true
@post = Post.new({ title: 'Title 1', body: 'Body 1', date: '1/1/2000' })
@post_serializer = PostSerializer.new(@post)
@post_serializer.instance_eval do
def filter(keys)
keys - [:body, :comments]
end
end
end
def teardown
PostSerializer._associations[:comments] = @old_association
end
def test_filtered_associations_serialization
assert_equal({
'post' => { title: 'Title 1' }
}, @post_serializer.as_json)
end
end
end
end

View File

@ -0,0 +1,117 @@
require 'test_helper'
module ActiveModel
class Serializer
class HasManyTest < ActiveModel::TestCase
def setup
@association = PostSerializer._associations[:comments]
@old_association = @association.dup
@association.embed = :ids
@post = Post.new({ title: 'Title 1', body: 'Body 1', date: '1/1/2000' })
@post_serializer = PostSerializer.new(@post)
end
def teardown
PostSerializer._associations[:comments] = @old_association
end
def test_associations_definition
assert_equal 1, PostSerializer._associations.length
assert_kind_of Association::HasMany, @association
assert_equal 'comments', @association.name
end
def test_associations_embedding_ids_serialization_using_serializable_hash
assert_equal({
title: 'Title 1', body: 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id }
}, @post_serializer.serializable_hash)
end
def test_associations_embedding_ids_serialization_using_as_json
assert_equal({
'post' => { title: 'Title 1', body: 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id } }
}, @post_serializer.as_json)
end
def test_associations_embedding_ids_serialization_using_serializable_hash_and_key_from_options
@association.key = 'key'
assert_equal({
title: 'Title 1', body: 'Body 1', 'key' => @post.comments.map { |c| c.object_id }
}, @post_serializer.serializable_hash)
end
def test_associations_embedding_objects_serialization_using_serializable_hash
@association.embed = :objects
assert_equal({
title: 'Title 1', body: 'Body 1', comments: [{ content: 'C1' }, { content: 'C2' }]
}, @post_serializer.serializable_hash)
end
def test_associations_embedding_objects_serialization_using_as_json
@association.embed = :objects
assert_equal({
'post' => { title: 'Title 1', body: 'Body 1', comments: [{ content: 'C1' }, { content: 'C2' }] }
}, @post_serializer.as_json)
end
def test_associations_embedding_nil_objects_serialization_using_as_json
@association.embed = :objects
@post.instance_eval do
def comments
[nil]
end
end
assert_equal({
'post' => { title: 'Title 1', body: 'Body 1', comments: [nil] }
}, @post_serializer.as_json)
end
def test_associations_embedding_objects_serialization_using_serializable_hash_and_root_from_options
@association.embed = :objects
@association.embedded_key = 'root'
assert_equal({
title: 'Title 1', body: 'Body 1', 'root' => [{ content: 'C1' }, { content: 'C2' }]
}, @post_serializer.serializable_hash)
end
def test_associations_embedding_ids_including_objects_serialization_using_serializable_hash
@association.embed_in_root = true
@post_serializer.root = nil
assert_equal({
title: 'Title 1', body: 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id }
}, @post_serializer.serializable_hash)
end
def test_associations_embedding_ids_including_objects_serialization_using_as_json
PostSerializer.embed :ids, include: true
PostSerializer._associations[:comments].send :initialize, @association.name, @association.options
@post_serializer.root = nil
assert_equal({
'post' => { title: 'Title 1', body: 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id } },
'comments' => [{ content: 'C1' }, { content: 'C2' }]
}, @post_serializer.as_json)
ensure
SETTINGS.clear
end
def test_associations_using_a_given_serializer
@association.embed_in_root = true
@post_serializer.root = nil
@association.serializer_class = Class.new(ActiveModel::Serializer) do
def content
'fake'
end
attributes :content
end
assert_equal({
'post' => { title: 'Title 1', body: 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id } },
comments: [{ content: 'fake' }, { content: 'fake' }]
}, @post_serializer.as_json)
end
end
end
end

View File

@ -0,0 +1,125 @@
require 'test_helper'
module ActiveModel
class Serializer
class HasOneTest < ActiveModel::TestCase
def setup
@association = UserSerializer._associations[:profile]
@old_association = @association.dup
@association.embed = :ids
@user = User.new({ name: 'Name 1', email: 'mail@server.com', gender: 'M' })
@user_serializer = UserSerializer.new(@user)
end
def teardown
UserSerializer._associations[:profile] = @old_association
end
def test_associations_definition
assert_equal 1, UserSerializer._associations.length
assert_kind_of Association::HasOne, @association
assert_equal 'profile', @association.name
end
def test_associations_embedding_ids_serialization_using_serializable_hash
assert_equal({
name: 'Name 1', email: 'mail@server.com', 'profile_id' => @user.profile.object_id
}, @user_serializer.serializable_hash)
end
def test_associations_embedding_ids_serialization_using_as_json
assert_equal({
'user' => { name: 'Name 1', email: 'mail@server.com', 'profile_id' => @user.profile.object_id }
}, @user_serializer.as_json)
end
def test_associations_embedding_ids_serialization_using_serializable_hash_and_key_from_options
@association.key = 'key'
assert_equal({
name: 'Name 1', email: 'mail@server.com', 'key' => @user.profile.object_id
}, @user_serializer.serializable_hash)
end
def test_associations_embedding_objects_serialization_using_serializable_hash
@association.embed = :objects
assert_equal({
name: 'Name 1', email: 'mail@server.com', profile: { name: 'N1', description: 'D1' }
}, @user_serializer.serializable_hash)
end
def test_associations_embedding_objects_serialization_using_as_json
@association.embed = :objects
assert_equal({
'user' => { name: 'Name 1', email: 'mail@server.com', profile: { name: 'N1', description: 'D1' } }
}, @user_serializer.as_json)
end
def test_associations_embedding_nil_ids_serialization_using_as_json
@user.instance_eval do
def profile
nil
end
end
assert_equal({
'user' => { name: 'Name 1', email: 'mail@server.com', 'profile_id' => nil }
}, @user_serializer.as_json)
end
def test_associations_embedding_nil_objects_serialization_using_as_json
@association.embed = :objects
@user.instance_eval do
def profile
nil
end
end
assert_equal({
'user' => { name: 'Name 1', email: 'mail@server.com', profile: nil }
}, @user_serializer.as_json)
end
def test_associations_embedding_objects_serialization_using_serializable_hash_and_root_from_options
@association.embed = :objects
@association.embedded_key = 'root'
assert_equal({
name: 'Name 1', email: 'mail@server.com', 'root' => { name: 'N1', description: 'D1' }
}, @user_serializer.serializable_hash)
end
def test_associations_embedding_ids_including_objects_serialization_using_serializable_hash
@association.embed_in_root = true
@user_serializer.root = nil
assert_equal({
name: 'Name 1', email: 'mail@server.com', 'profile_id' => @user.profile.object_id
}, @user_serializer.serializable_hash)
end
def test_associations_embedding_ids_including_objects_serialization_using_as_json
@association.embed_in_root = true
@user_serializer.root = nil
assert_equal({
'user' => { name: 'Name 1', email: 'mail@server.com', 'profile_id' => @user.profile.object_id },
profile: { name: 'N1', description: 'D1' }
}, @user_serializer.as_json)
end
def test_associations_using_a_given_serializer
@association.embed_in_root = true
@user_serializer.root = nil
@association.serializer_class = Class.new(ActiveModel::Serializer) do
def name
'fake'
end
attributes :name
end
assert_equal({
'user' => { name: 'Name 1', email: 'mail@server.com', 'profile_id' => @user.profile.object_id },
profile: { name: 'fake' }
}, @user_serializer.as_json)
end
end
end
end

View File

@ -0,0 +1,39 @@
require 'test_helper'
module ActiveModel
class Serializer
class MetaTest < ActiveModel::TestCase
def setup
@profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })
end
def test_meta
profile_serializer = ProfileSerializer.new(@profile, root: 'profile', meta: { total: 10 })
assert_equal({
'profile' => {
name: 'Name 1',
description: 'Description 1'
},
meta: {
total: 10
}
}, profile_serializer.as_json)
end
def test_meta_using_meta_key
profile_serializer = ProfileSerializer.new(@profile, root: 'profile', meta_key: :my_meta, my_meta: { total: 10 })
assert_equal({
'profile' => {
name: 'Name 1',
description: 'Description 1'
},
my_meta: {
total: 10
}
}, profile_serializer.as_json)
end
end
end
end

View File

@ -0,0 +1,103 @@
require 'test_helper'
module ActiveModel
class Serializer
class RootAsOptionTest < ActiveModel::TestCase
def setup
@old_root = ProfileSerializer._root
@profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })
@serializer = ProfileSerializer.new(@profile, root: :initialize)
ProfileSerializer._root = true
end
def teardown
ProfileSerializer._root = @old_root
end
def test_root_is_not_displayed_using_serializable_hash
assert_equal({
name: 'Name 1', description: 'Description 1'
}, @serializer.serializable_hash)
end
def test_root_using_as_json
assert_equal({
initialize: {
name: 'Name 1', description: 'Description 1'
}
}, @serializer.as_json)
end
def test_root_from_serializer_name
@serializer = ProfileSerializer.new(@profile)
assert_equal({
'profile' => {
name: 'Name 1', description: 'Description 1'
}
}, @serializer.as_json)
end
def test_root_as_argument_takes_precedence
assert_equal({
argument: {
name: 'Name 1', description: 'Description 1'
}
}, @serializer.as_json(root: :argument))
end
def test_using_false_root_in_initializer_takes_precedence
ProfileSerializer._root = 'root'
@serializer = ProfileSerializer.new(@profile, root: false)
assert_equal({
name: 'Name 1', description: 'Description 1'
}, @serializer.as_json)
end
end
class RootInSerializerTest < ActiveModel::TestCase
def setup
@old_root = ProfileSerializer._root
ProfileSerializer._root = :in_serializer
profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })
@serializer = ProfileSerializer.new(profile)
@rooted_serializer = ProfileSerializer.new(profile, root: :initialize)
end
def teardown
ProfileSerializer._root = @old_root
end
def test_root_is_not_displayed_using_serializable_hash
assert_equal({
name: 'Name 1', description: 'Description 1'
}, @serializer.serializable_hash)
end
def test_root_using_as_json
assert_equal({
in_serializer: {
name: 'Name 1', description: 'Description 1'
}
}, @serializer.as_json)
end
def test_root_in_initializer_takes_precedence
assert_equal({
initialize: {
name: 'Name 1', description: 'Description 1'
}
}, @rooted_serializer.as_json)
end
def test_root_as_argument_takes_precedence
assert_equal({
argument: {
name: 'Name 1', description: 'Description 1'
}
}, @rooted_serializer.as_json(root: :argument))
end
end
end
end

View File

@ -0,0 +1,21 @@
require 'test_helper'
module ActiveModel
class Serializer
class ScopeTest < ActiveModel::TestCase
def setup
@serializer = ProfileSerializer.new(nil, scope: current_user)
end
def test_scope
assert_equal('user', @serializer.scope)
end
private
def current_user
'user'
end
end
end
end

View File

@ -0,0 +1,49 @@
require 'test_helper'
module ActiveModel
class Serializer
class Settings
class Test < ActiveModel::TestCase
def test_settings_const_is_an_instance_of_settings
assert_kind_of Settings, SETTINGS
end
def test_settings_instance
settings = Settings.new
settings[:setting1] = 'value1'
assert_equal 'value1', settings[:setting1]
end
def test_each_settings
settings = Settings.new
settings['setting1'] = 'value1'
settings['setting2'] = 'value2'
actual = {}
settings.each do |k, v|
actual[k] = v
end
assert_equal({ 'setting1' => 'value1', 'setting2' => 'value2' }, actual)
end
end
class SetupTest < ActiveModel::TestCase
def test_setup
ActiveModel::Serializer.setup do |settings|
settings[:a] = 'v1'
settings[:b] = 'v2'
end
assert_equal 'v1', SETTINGS[:a]
assert_equal 'v2', SETTINGS[:b]
ensure
SETTINGS.clear
end
end
end
end
end