Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restrict serializable_hash to accepted options #1647

Merged
merged 7 commits into from
Apr 11, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Breaking changes:
- [#1574](https://github.com/rails-api/active_model_serializers/pull/1574) Default key case for the JsonApi adapter changed to dashed. (@remear)

Features:
- [#1647](https://github.com/rails-api/active_model_serializers/pull/1647) Restrict usage of `serializable_hash` options
to the ActiveModel::Serialization and ActiveModel::Serializers::JSON interface. (@bf4)
- [#1645](https://github.com/rails-api/active_model_serializers/pull/1645) Transform keys referenced in values. (@remear)
- [#1650](https://github.com/rails-api/active_model_serializers/pull/1650) Fix serialization scope options `scope`, `scope_name`
take precedence over `serialization_scope` in the controller.
Expand Down
2 changes: 1 addition & 1 deletion lib/active_model_serializers/adapter/json.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class Json < Base
def serializable_hash(options = nil)
options ||= {}
serialized_hash = { root => Attributes.new(serializer, instance_options).serializable_hash(options) }
self.class.transform_key_casing!(serialized_hash, options)
self.class.transform_key_casing!(serialized_hash, instance_options)
end
end
end
Expand Down
59 changes: 29 additions & 30 deletions lib/active_model_serializers/adapter/json_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,13 @@ def self.default_key_transform

# {http://jsonapi.org/format/#crud Requests are transactional, i.e. success or failure}
# {http://jsonapi.org/format/#document-top-level data and errors MUST NOT coexist in the same document.}
def serializable_hash(options = nil)
options ||= {}
def serializable_hash(*)
document = if serializer.success?
success_document(options)
success_document
else
failure_document(options)
failure_document
end
self.class.transform_key_casing!(document, options)
self.class.transform_key_casing!(document, instance_options)
end

# {http://jsonapi.org/format/#document-top-level Primary data}
Expand All @@ -68,10 +67,10 @@ def serializable_hash(options = nil)
# links: toplevel_links,
# jsonapi: toplevel_jsonapi
# }.reject! {|_,v| v.nil? }
def success_document(options)
def success_document
is_collection = serializer.respond_to?(:each)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like how you've gotten rid of all the option arguments :-)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might need to re-add them later, but it's not later :)

serializers = is_collection ? serializer : [serializer]
primary_data, included = resource_objects_for(serializers, options)
primary_data, included = resource_objects_for(serializers)

hash = {}
# toplevel_data
Expand Down Expand Up @@ -128,7 +127,7 @@ def success_document(options)

if is_collection && serializer.paginated?
hash[:links] ||= {}
hash[:links].update(pagination_links_for(serializer, options))
hash[:links].update(pagination_links_for(serializer))
end

hash
Expand All @@ -148,7 +147,7 @@ def success_document(options)
# }.reject! {|_,v| v.nil? }
# prs:
# https://github.com/rails-api/active_model_serializers/pull/1004
def failure_document(options)
def failure_document
hash = {}
# PR Please :)
# Jsonapi.add!(hash)
Expand All @@ -163,10 +162,10 @@ def failure_document(options)
# ]
if serializer.respond_to?(:each)
hash[:errors] = serializer.flat_map do |error_serializer|
Error.resource_errors(error_serializer, options)
Error.resource_errors(error_serializer, instance_options)
end
else
hash[:errors] = Error.resource_errors(serializer, options)
hash[:errors] = Error.resource_errors(serializer, instance_options)
end
hash
end
Expand Down Expand Up @@ -224,21 +223,21 @@ def fragment_cache(cached_hash, non_cached_hash)
# [x] url helpers https://github.com/rails-api/active_model_serializers/issues/1269
# meta
# [x] https://github.com/rails-api/active_model_serializers/pull/1340
def resource_objects_for(serializers, options)
def resource_objects_for(serializers)
@primary = []
@included = []
@resource_identifiers = Set.new
serializers.each { |serializer| process_resource(serializer, true, options) }
serializers.each { |serializer| process_relationships(serializer, @include_tree, options) }
serializers.each { |serializer| process_resource(serializer, true) }
serializers.each { |serializer| process_relationships(serializer, @include_tree) }

[@primary, @included]
end

def process_resource(serializer, primary, options)
resource_identifier = ResourceIdentifier.new(serializer, options).as_json
def process_resource(serializer, primary)
resource_identifier = ResourceIdentifier.new(serializer, instance_options).as_json
return false unless @resource_identifiers.add?(resource_identifier)

resource_object = resource_object_for(serializer, options)
resource_object = resource_object_for(serializer)
if primary
@primary << resource_object
else
Expand All @@ -248,21 +247,21 @@ def process_resource(serializer, primary, options)
true
end

def process_relationships(serializer, include_tree, options)
def process_relationships(serializer, include_tree)
serializer.associations(include_tree).each do |association|
process_relationship(association.serializer, include_tree[association.key], options)
process_relationship(association.serializer, include_tree[association.key])
end
end

def process_relationship(serializer, include_tree, options)
def process_relationship(serializer, include_tree)
if serializer.respond_to?(:each)
serializer.each { |s| process_relationship(s, include_tree, options) }
serializer.each { |s| process_relationship(s, include_tree) }
return
end
return unless serializer && serializer.object
return unless process_resource(serializer, false, options)
return unless process_resource(serializer, false)

process_relationships(serializer, include_tree, options)
process_relationships(serializer, include_tree)
end

# {http://jsonapi.org/format/#document-resource-object-attributes Document Resource Object Attributes}
Expand All @@ -286,9 +285,9 @@ def attributes_for(serializer, fields)
end

# {http://jsonapi.org/format/#document-resource-objects Document Resource Objects}
def resource_object_for(serializer, options)
def resource_object_for(serializer)
resource_object = cache_check(serializer) do
resource_object = ResourceIdentifier.new(serializer, options).as_json
resource_object = ResourceIdentifier.new(serializer, instance_options).as_json

requested_fields = fieldset && fieldset.fields_for(resource_object[:type])
attributes = attributes_for(serializer, requested_fields)
Expand All @@ -297,7 +296,7 @@ def resource_object_for(serializer, options)
end

requested_associations = fieldset.fields_for(resource_object[:type]) || '*'
relationships = relationships_for(serializer, requested_associations, options)
relationships = relationships_for(serializer, requested_associations)
resource_object[:relationships] = relationships if relationships.any?

links = links_for(serializer)
Expand Down Expand Up @@ -425,13 +424,13 @@ def resource_object_for(serializer, options)
# id: 'required-id',
# meta: meta
# }.reject! {|_,v| v.nil? }
def relationships_for(serializer, requested_associations, options)
def relationships_for(serializer, requested_associations)
include_tree = ActiveModel::Serializer::IncludeTree.from_include_args(requested_associations)
serializer.associations(include_tree).each_with_object({}) do |association, hash|
hash[association.key] = Relationship.new(
serializer,
association.serializer,
options,
instance_options,
options: association.options,
links: association.links,
meta: association.meta
Expand Down Expand Up @@ -499,8 +498,8 @@ def links_for(serializer)
# end
# prs:
# https://github.com/rails-api/active_model_serializers/pull/1041
def pagination_links_for(serializer, options)
PaginationLinks.new(serializer.object, options[:serialization_context]).serializable_hash(options)
def pagination_links_for(serializer)
PaginationLinks.new(serializer.object, instance_options).as_json
end

# {http://jsonapi.org/format/#document-meta Docment Meta}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,25 @@ class PaginationLinks

attr_reader :collection, :context

def initialize(collection, context)
def initialize(collection, adapter_options)
@collection = collection
@context = context
@adapter_options = adapter_options
@context = adapter_options.fetch(:serialization_context)
end

def serializable_hash(options = {})
def as_json
per_page = collection.try(:per_page) || collection.try(:limit_value) || collection.size
pages_from.each_with_object({}) do |(key, value), hash|
params = query_parameters.merge(page: { number: value, size: per_page }).to_query

hash[key] = "#{url(options)}?#{params}"
hash[key] = "#{url(adapter_options)}?#{params}"
end
end

protected

attr_reader :adapter_options

private

def pages_from
Expand Down
3 changes: 1 addition & 2 deletions lib/active_model_serializers/adapter/null.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
module ActiveModelSerializers
module Adapter
class Null < Base
# Since options param is not being used, underscored naming of the param
def serializable_hash(_options = nil)
def serializable_hash(*)
{}
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/active_model_serializers/serializable_resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

module ActiveModelSerializers
class SerializableResource
ADAPTER_OPTION_KEYS = Set.new([:include, :fields, :adapter, :meta, :meta_key, :links])
ADAPTER_OPTION_KEYS = Set.new([:include, :fields, :adapter, :meta, :meta_key, :links, :serialization_context, :key_transform])
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@groyoh thanks for catching :key_transform, d'oh

include ActiveModelSerializers::Logging

delegate :serializable_hash, :as_json, :to_json, to: :adapter
Expand Down
28 changes: 14 additions & 14 deletions test/adapter/json/transform_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,34 @@ def mock_request(key_transform = nil)
context = Minitest::Mock.new
context.expect(:request_url, URI)
context.expect(:query_parameters, {})
@options = {}
@options[:key_transform] = key_transform if key_transform
@options[:serialization_context] = context
options = {}
options[:key_transform] = key_transform if key_transform
options[:serialization_context] = context
serializer = CustomBlogSerializer.new(@blog)
@adapter = ActiveModelSerializers::Adapter::Json.new(serializer, options)
end

Post = Class.new(::Model)
class PostSerializer < ActiveModel::Serializer
attributes :id, :title, :body, :publish_at
end

def setup
setup do
ActionController::Base.cache_store.clear
@blog = Blog.new(id: 1, name: 'My Blog!!', special_attribute: 'neat')
serializer = CustomBlogSerializer.new(@blog)
@adapter = ActiveModelSerializers::Adapter::Json.new(serializer)
end

def test_transform_default
mock_request
assert_equal({
blog: { id: 1, special_attribute: 'neat', articles: nil }
}, @adapter.serializable_hash(@options))
}, @adapter.serializable_hash)
end

def test_transform_global_config
mock_request
result = with_config(key_transform: :camel_lower) do
@adapter.serializable_hash(@options)
@adapter.serializable_hash
end
assert_equal({
blog: { id: 1, specialAttribute: 'neat', articles: nil }
Expand All @@ -45,7 +45,7 @@ def test_transform_global_config
def test_transform_serialization_ctx_overrides_global_config
mock_request(:camel)
result = with_config(key_transform: :camel_lower) do
@adapter.serializable_hash(@options)
@adapter.serializable_hash
end
assert_equal({
Blog: { Id: 1, SpecialAttribute: 'neat', Articles: nil }
Expand All @@ -56,36 +56,36 @@ def test_transform_undefined
mock_request(:blam)
result = nil
assert_raises NoMethodError do
result = @adapter.serializable_hash(@options)
result = @adapter.serializable_hash
end
end

def test_transform_dash
mock_request(:dash)
assert_equal({
blog: { id: 1, :"special-attribute" => 'neat', articles: nil }
}, @adapter.serializable_hash(@options))
}, @adapter.serializable_hash)
end

def test_transform_unaltered
mock_request(:unaltered)
assert_equal({
blog: { id: 1, special_attribute: 'neat', articles: nil }
}, @adapter.serializable_hash(@options))
}, @adapter.serializable_hash)
end

def test_transform_camel
mock_request(:camel)
assert_equal({
Blog: { Id: 1, SpecialAttribute: 'neat', Articles: nil }
}, @adapter.serializable_hash(@options))
}, @adapter.serializable_hash)
end

def test_transform_camel_lower
mock_request(:camel_lower)
assert_equal({
blog: { id: 1, specialAttribute: 'neat', articles: nil }
}, @adapter.serializable_hash(@options))
}, @adapter.serializable_hash)
end
end
end
Expand Down
Loading