Add a Responder to handle respond_with(resource)

This commit is contained in:
Michael Rykov 2013-03-13 16:45:23 -07:00
parent ad886495a1
commit 37b0690fb8
8 changed files with 463 additions and 21 deletions

View File

@ -30,6 +30,12 @@ module ActionController
included do included do
class_attribute :_serialization_scope class_attribute :_serialization_scope
self._serialization_scope = :current_user self._serialization_scope = :current_user
self.responder = ActiveModel::Serializer::Responder
self.respond_to :json
unless ActiveModel::Serializer.use_default_render_json
self.send(:include, RenderJsonOverride)
end
end end
def serialization_scope def serialization_scope
@ -39,6 +45,7 @@ module ActionController
def default_serializer_options def default_serializer_options
end end
module RenderJsonOverride
def _render_option_json(json, options) def _render_option_json(json, options)
options = default_serializer_options.merge(options) if default_serializer_options options = default_serializer_options.merge(options) if default_serializer_options
@ -65,6 +72,7 @@ module ActionController
end end
super super
end end
end
module ClassMethods module ClassMethods
def serialization_scope(scope) def serialization_scope(scope)

View File

@ -66,6 +66,9 @@ module ActiveModel
self._embed = :objects self._embed = :objects
class_attribute :_root_embed class_attribute :_root_embed
class_attribute :use_default_render_json
self.use_default_render_json = false
class << self class << self
# Define attributes to be used in the serialization. # Define attributes to be used in the serialization.
def attributes(*attrs) def attributes(*attrs)

View File

@ -0,0 +1,43 @@
module ActiveModel
class Serializer
class Responder < ::ActionController::Responder #:nodoc:
attr_reader :serializer
protected
def display(resource, given_options = {})
if format != :json
super
else
default_options = controller.send(:default_serializer_options)
options = self.options.reverse_merge(default_options || {})
serializer = options[:serializer] ||
(resource.respond_to?(:active_model_serializer) &&
resource.active_model_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
# default root element for arrays is serializer's root or the controller name
# the serializer for an Array is ActiveModel::ArraySerializer
options[:root] ||= serializer.root || controller.send(:controller_name)
end
end
if serializer
serialization_scope = controller.send(:serialization_scope)
options[:scope] = serialization_scope unless options.has_key?(:scope)
options[:url_options] = controller.send(:url_options)
render(given_options.merge(:json => serializer.new(resource, options)))
else
super
end
end
end
end
end
end

View File

@ -76,6 +76,7 @@ begin
require 'action_controller/serialization' require 'action_controller/serialization'
ActiveSupport.on_load(:action_controller) do ActiveSupport.on_load(:action_controller) do
require 'active_model/serializer/responder'
include ::ActionController::Serialization include ::ActionController::Serialization
end end
rescue LoadError => ex rescue LoadError => ex

View File

@ -21,14 +21,14 @@ class NoSerializationScopeTest < ActionController::TestCase
serialization_scope nil serialization_scope nil
def index def index
render :json => ScopeSerializable.new respond_with(ScopeSerializable.new)
end end
end end
tests NoSerializationScopeController tests NoSerializationScopeController
def test_disabled_serialization_scope def test_disabled_serialization_scope
get :index get :index, :format => :json
assert_equal '{"scope":null}', @response.body assert_equal '{"scope":null}', @response.body
end end
end end

383
test/responder_test.rb Normal file
View File

@ -0,0 +1,383 @@
require 'test_helper'
require 'pathname'
class ResponderTest < 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 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
before_filter do
request.format = :json
end
def self.controller_path
'test'
end
def render_json_nil
respond_with(nil)
end
def render_json_render_to_string
respond_with render_to_string(:json => '[]')
end
def render_json_hello_world
respond_with ActiveSupport::JSON.encode(:hello => 'world')
end
def render_json_hello_world_with_status
respond_with ActiveSupport::JSON.encode(:hello => 'world'), :status => 401
end
def render_json_hello_world_with_callback
respond_with ActiveSupport::JSON.encode(:hello => 'world'), :callback => 'alert'
end
def render_json_with_custom_content_type
respond_with ActiveSupport::JSON.encode(:hello => 'world'), :content_type => 'text/javascript'
end
def render_symbol_json
respond_with ActiveSupport::JSON.encode(:hello => 'world')
end
def render_json_with_extra_options
respond_with JsonRenderable.new, :except => [:c, :e]
end
def render_json_without_options
respond_with JsonRenderable.new
end
def render_json_with_serializer
@current_user = Struct.new(:as_json).new(:current_user => true)
respond_with JsonSerializable.new
end
def render_json_with_serializer_and_implicit_root
@current_user = Struct.new(:as_json).new(:current_user => true)
respond_with [JsonSerializable.new]
end
def render_json_with_serializer_and_options
@current_user = Struct.new(:as_json).new(:current_user => true)
respond_with 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)
respond_with JsonSerializable.new, :scope => scope
end
def render_json_with_serializer_api_but_without_serializer
@current_user = Struct.new(:as_json).new(:current_user => true)
respond_with JsonSerializable.new(true)
end
# To specify a custom serializer for an object, use :serializer.
def render_json_with_custom_serializer
respond_with 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
respond_with [Object.new], :each_serializer => CustomSerializer
end
def render_json_array_with_wrong_option
respond_with [Object.new], :serializer => CustomSerializer
end
def render_json_with_links
respond_with HypermediaSerializable.new
end
def render_json_array_with_no_root
respond_with [], :root => false
end
def render_json_empty_array
respond_with []
end
def render_json_array_with_custom_array_serializer
respond_with [], :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
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_arry_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

@ -84,6 +84,7 @@ class RenderJsonTest < ActionController::TestCase
end end
class TestController < ActionController::Base class TestController < ActionController::Base
include ::ActionController::Serialization::RenderJsonOverride
protect_from_forgery protect_from_forgery
serialization_scope :current_user serialization_scope :current_user

View File

@ -15,6 +15,9 @@ require "active_model_serializers"
require "active_support/json" require "active_support/json"
require "test/unit" require "test/unit"
# Manually include RenderJsonOverride where needed
ActiveModel::Serializer.use_default_render_json = true
require 'rails' require 'rails'
module TestHelper module TestHelper