Skip to content

Commit

Permalink
tests added
Browse files Browse the repository at this point in the history
all tests tpass

remove binding.pry require
  • Loading branch information
NullVoxPopuli committed Sep 1, 2015
1 parent f05cf96 commit db5d174
Show file tree
Hide file tree
Showing 2 changed files with 314 additions and 21 deletions.
132 changes: 111 additions & 21 deletions lib/active_model/serializer/adapter/json.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ class Adapter
class Json < Adapter
def serializable_hash(options = nil)
options ||= {}

if serializer.respond_to?(:each)
@result = serialize_array_without_root(serializer, options)
else
@hash = {}

@core = resource_object_for(serializer, options)
@result = resource_object_for(serializer)
@result = add_resource_relationships(
@result,
serializer,
serializer.options[:include])

add_resource_relationships(serializer)

@result = @core.merge @hash
@result
end

{ root => @result }
Expand All @@ -28,33 +28,84 @@ def fragment_cache(cached_hash, non_cached_hash)

private

def serialize_association(association)
serialized_or_virtual_of(association.serializer, association.options)
end

# iterate through the associations on the serializer,
# adding them to @hash as needed (as singular or plural)
def add_resource_relationships(serializer)
serializer.associations.each do |association|
# adding them to the parent as needed (as singular or plural)
#
# restrict_to is a list of symbols that governs what
# associations on the passed in seralizer to include
def add_resource_relationships(parent, serializer, restrict_to = [])
# have the include array normalized
restrict_to = include_array_to_hash(restrict_to)

included_associations = if restrict_to.present?
serializer.associations.select{ |association|
# restrict_to is a hash of:
# key => nested association to include
restrict_to.keys.include?(association.name)
}
else
serializer.associations
end

included_associations.each do |association|
serializer = association.serializer
opts = association.options
key = association.key

# sanity check if the association has nesting data
has_nesting = restrict_to[key].present?
if has_nesting
include_options_from_parent = { include: restrict_to[key] }
opts = opts.merge(include_options_from_parent)
end

if serializer.respond_to?(:each)
add_has_many_relationship(association.key, serializer, opts)
parent[key] = add_relationships(serializer, opts)
else
add_singular_relationship(association.key, serializer, opts)
parent[key] = add_relationship(serializer, opts)
end
end

@hash
parent
end

# add a singular relationship
def add_singular_relationship(key, serializer, options)
@hash[key] = serialized_or_virtual_of(serializer, options)
# the options should always belong to the serializer
def add_relationship(serializer, options)
serialized_relationship = serialized_or_virtual_of(serializer, options)

nested_relationships = options[:include]
if nested_relationships.present?
serialized_relationship = add_resource_relationships(
serialized_relationship,
serializer,
nested_relationships)
end

serialized_relationship
end

# add a many relationship
def add_has_many_relationship(key, serializer, options)
@hash[key] = serialize_array(serializer, options)
def add_relationships(serializer, options)
serialize_array(serializer, options) do |serialized_item, serializer|
nested_relationships = options[:include]

if nested_relationships.present?
seralized_item = add_resource_relationships(
serialized_item,
serializer,
nested_relationships)
end

serialized_item
end
end


def serialize_array_without_root(serializer, options)
serializer.map { |s| FlattenJson.new(s).serializable_hash(options) }
end
Expand All @@ -63,24 +114,63 @@ def serialize_array_without_root(serializer, options)
# such as a ruby array, or any other raw value
def serialized_or_virtual_of(serializer, options)
if serializer && serializer.object
resource_object_for(serializer, options)
resource_object_for(serializer)
elsif options[:virtual_value]
options[:virtual_value]
end
end

def serialize_array(serializer, options)
serializer.map do |item|
resource_object_for(item, options)
serialized_item = resource_object_for(item)
serialized_item = yield(serialized_item, item) if block_given?
serialized_item
end
end

def resource_object_for(item, options)
cache_check(item) do
item.attributes(options)
def resource_object_for(serializer)
cache_check(serializer) do
serializer.attributes(serializer.options)
end
end

# converts the include hash to a standard format
# for constrained serialization recursion
#
# converts
# [:author, :comments => [:author]] to
# {:author => [], :comments => [:author]}
#
# and
# [:author, :comments => {:author => :bio}, :posts => [:comments]] to
# {:author => [], :comments => {:author => :bio}, :posts => [:comments]}
#
# The data passed in to this method should be an array where the last
# parameter is a hash
#
# the point of this method is to normalize the include
# options for the child relationships.
# if a sub inclusion is still an array after this method,
# it will get converted during the next iteration
def include_array_to_hash(include_array)
# still don't trust input
# but this also allows
# include: :author syntax
include_array = Array[*include_array].compact

result = {}

hashes = include_array.select{|a| a.is_a?(Hash)}
non_hashes = include_array - hashes

hashes += non_hashes.map{ |association_name| { association_name => [] } }

# now merge all the hashes
hashes.each{|hash| result.merge!(hash) }

result
end

end
end
end
Expand Down
203 changes: 203 additions & 0 deletions test/adapter/json/nested_relationships_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
require 'test_helper'

NestedPostSerializer = Class.new(ActiveModel::Serializer) do
attributes :id, :title

has_many :comments, include: :author
end

NestedCommentBelongsToSerializer = Class.new(ActiveModel::Serializer) do
attributes :id, :body

belongs_to :author, include: [:posts]
end

NestedAuthorSerializer = Class.new(ActiveModel::Serializer) do
attributes :id, :name

has_many :posts, include: [:comments]
end

ComplexNestedAuthorSerializer = Class.new(ActiveModel::Serializer) do
attributes :id, :name

# it would normally be silly to have this in production code, cause a post's
# author in this case is always going to be your root object
has_many :posts, include: [:author, comments: [:author]]
end

module ActiveModel
class Serializer
class Adapter
class Json
class NestedRelationShipsTestTest < Minitest::Test
def setup
ActionController::Base.cache_store.clear
@author = Author.new(id: 1, name: 'Steve K.')

@post = Post.new(id: 42, title: 'New Post', body: 'Body')
@first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT')
@second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT')

@post.comments = [@first_comment, @second_comment]
@post.author = @author

@first_comment.post = @post
@second_comment.post = @post

@blog = Blog.new(id: 1, name: "My Blog!!")
@post.blog = @blog
@author.posts = [@post]
end

def test_complex_nested_has_many
@first_comment.author = @author
@second_comment.author = @author

serializer = ComplexNestedAuthorSerializer.new(@author)
adapter = ActiveModel::Serializer::Adapter::Json.new(serializer)

expected = {
author: {
id: 1,
name: 'Steve K.',
posts: [
{
id: 42, title: 'New Post', body: 'Body',
author: {
id: 1,
name: 'Steve K.'
},
comments: [
{
id: 1, body: 'ZOMG A COMMENT',
author: {
id: 1,
name: 'Steve K.'
}
},
{
id: 2, body: 'ZOMG ANOTHER COMMENT',
author: {
id: 1,
name: 'Steve K.'
}
}
]
}
]
}
}

actual = adapter.serializable_hash

assert_equal(expected, actual)
end

def test_nested_has_many
serializer = NestedAuthorSerializer.new(@author)
adapter = ActiveModel::Serializer::Adapter::Json.new(serializer)

expected = {
author: {
id: 1,
name: 'Steve K.',
posts: [
{
id: 42, title: 'New Post', body: 'Body',
comments: [
{
id: 1, body: 'ZOMG A COMMENT'
},
{
id: 2, body: 'ZOMG ANOTHER COMMENT'
}
]
}
]
}
}

actual = adapter.serializable_hash

assert_equal(expected, actual)
end

def test_belongs_to_on_a_has_many
@first_comment.author = @author
@second_comment.author = @author

serializer = NestedPostSerializer.new(@post)
adapter = ActiveModel::Serializer::Adapter::Json.new(serializer)

expected = {
post: {
id: 42, title: 'New Post',
comments: [
{
id: 1, body: 'ZOMG A COMMENT',
author: {
id: 1,
name: 'Steve K.'
}
},
{
id: 2, body: 'ZOMG ANOTHER COMMENT',
author: {
id: 1,
name: 'Steve K.'
}
}
]
},
}

actual = adapter.serializable_hash

assert_equal(expected, actual)
end

def test_belongs_to_with_a_has_many
@author.roles = []
@author.bio = {}
@first_comment.author = @author
@second_comment.author = @author

serializer = NestedCommentBelongsToSerializer.new(@first_comment)
adapter = ActiveModel::Serializer::Adapter::Json.new(serializer)

expected = {
comment: {
id: 1, body: 'ZOMG A COMMENT',
author: {
id: 1,
name: 'Steve K.',
posts: [
{
id: 42, title: 'New Post', body: 'Body'
}
]
}
}
}

actual = adapter.serializable_hash

assert_equal(expected, actual)
end

def test_include_array_to_hash
serializer = NestedCommentBelongsToSerializer.new(@first_comment)
adapter = ActiveModel::Serializer::Adapter::Json.new(serializer)

expected = {author: [], comments: {author: :bio}, posts: [:comments]}
input = [:author, comments: {author: :bio}, posts: [:comments]]
actual = adapter.send(:include_array_to_hash, input)

assert_equal(expected, actual)
end
end
end
end
end
end

0 comments on commit db5d174

Please sign in to comment.