Merge pull request #1678 from bf4/serialization_interface

Restrict serializable_hash to accepted options
This commit is contained in:
Benjamin Fleischer 2016-04-13 00:59:10 -05:00
commit e2ded594d3
7 changed files with 57 additions and 3 deletions

View File

@ -18,6 +18,8 @@ require 'active_model/serializer/type'
# reified when subclassed to decorate a resource. # reified when subclassed to decorate a resource.
module ActiveModel module ActiveModel
class Serializer class Serializer
# @see #serializable_hash for more details on these valid keys.
SERIALIZABLE_HASH_VALID_KEYS = [:only, :except, :methods, :include, :root].freeze
extend ActiveSupport::Autoload extend ActiveSupport::Autoload
autoload :Adapter autoload :Adapter
autoload :Null autoload :Null

View File

@ -8,7 +8,7 @@ module ActiveModelSerializers
end end
def serializable_hash(options = nil) def serializable_hash(options = nil)
options ||= {} options = serialization_options(options)
if serializer.respond_to?(:each) if serializer.respond_to?(:each)
serializable_hash_for_collection(options) serializable_hash_for_collection(options)

View File

@ -19,6 +19,8 @@ module ActiveModelSerializers
@cached_name ||= self.class.name.demodulize.underscore @cached_name ||= self.class.name.demodulize.underscore
end end
# Subclasses that implement this method must first call
# options = serialization_options(options)
def serializable_hash(_options = nil) def serializable_hash(_options = nil)
fail NotImplementedError, 'This is an abstract method. Should be implemented at the concrete adapter.' fail NotImplementedError, 'This is an abstract method. Should be implemented at the concrete adapter.'
end end
@ -41,6 +43,12 @@ module ActiveModelSerializers
private private
# see https://github.com/rails-api/active_model_serializers/pull/965
# When <tt>options</tt> is +nil+, sets it to +{}+
def serialization_options(options)
options ||= {} # rubocop:disable Lint/UselessAssignment
end
def meta def meta
instance_options.fetch(:meta, nil) instance_options.fetch(:meta, nil)
end end

View File

@ -2,7 +2,7 @@ module ActiveModelSerializers
module Adapter module Adapter
class Json < Base class Json < Base
def serializable_hash(options = nil) def serializable_hash(options = nil)
options ||= {} options = serialization_options(options)
serialized_hash = { root => Attributes.new(serializer, instance_options).serializable_hash(options) } serialized_hash = { root => Attributes.new(serializer, instance_options).serializable_hash(options) }
self.class.transform_key_casing!(serialized_hash, instance_options) self.class.transform_key_casing!(serialized_hash, instance_options)
end end

View File

@ -60,10 +60,11 @@ module ActionController
end end
def render_resource_with_transform_with_global_config def render_resource_with_transform_with_global_config
setup_post
old_transform = ActiveModelSerializers.config.key_transform old_transform = ActiveModelSerializers.config.key_transform
setup_post
ActiveModelSerializers.config.key_transform = :camel_lower ActiveModelSerializers.config.key_transform = :camel_lower
render json: @post, serializer: PostSerializer, adapter: :json_api render json: @post, serializer: PostSerializer, adapter: :json_api
ensure
ActiveModelSerializers.config.key_transform = old_transform ActiveModelSerializers.config.key_transform = old_transform
end end
end end

View File

@ -14,6 +14,33 @@ module ActiveModelSerializers
end end
end end
def test_serialization_options_ensures_option_is_a_hash
adapter = Class.new(ActiveModelSerializers::Adapter::Base) do
def serializable_hash(options = nil)
serialization_options(options)
end
end.new(@serializer)
assert_equal({}, adapter.serializable_hash(nil))
assert_equal({}, adapter.serializable_hash({}))
ensure
ActiveModelSerializers::Adapter.adapter_map.delete_if { |k, _| k =~ /class/ }
end
def test_serialization_options_ensures_option_is_one_of_valid_options
adapter = Class.new(ActiveModelSerializers::Adapter::Base) do
def serializable_hash(options = nil)
serialization_options(options)
end
end.new(@serializer)
filtered_options = { now: :see_me, then: :not }
valid_options = ActiveModel::Serializer::SERIALIZABLE_HASH_VALID_KEYS.each_with_object({}) do |option, result|
result[option] = option
end
assert_equal(valid_options, adapter.serializable_hash(filtered_options.merge(valid_options)))
ensure
ActiveModelSerializers::Adapter.adapter_map.delete_if { |k, _| k =~ /class/ }
end
def test_serializer def test_serializer
assert_equal @serializer, @adapter.serializer assert_equal @serializer, @adapter.serializer
end end

View File

@ -15,6 +15,22 @@ require 'action_controller'
require 'action_controller/test_case' require 'action_controller/test_case'
require 'action_controller/railtie' require 'action_controller/railtie'
require 'active_model_serializers' require 'active_model_serializers'
# For now, we only restrict the options to serializable_hash/as_json/to_json
# in tests, to ensure developers don't add any unsupported options.
# There's no known benefit, at this time, to having the filtering run in
# production when the excluded options would simply not be used.
#
# However, for documentation purposes, the constant
# ActiveModel::Serializer::SERIALIZABLE_HASH_VALID_KEYS is defined
# in the Serializer.
ActiveModelSerializers::Adapter::Base.class_eval do
alias_method :original_serialization_options, :serialization_options
def serialization_options(options)
original_serialization_options(options)
.slice(*ActiveModel::Serializer::SERIALIZABLE_HASH_VALID_KEYS)
end
end
require 'fileutils' require 'fileutils'
FileUtils.mkdir_p(File.expand_path('../../tmp/cache', __FILE__)) FileUtils.mkdir_p(File.expand_path('../../tmp/cache', __FILE__))