Merge branch 'beauby-association-blocks'

Has Rubocop failures to be fixed in next commit.
This commit is contained in:
Benjamin Fleischer 2016-02-08 17:37:31 -06:00
commit e51597480a
7 changed files with 175 additions and 50 deletions

View File

@ -6,6 +6,8 @@ module ActiveModel
autoload :PaginationLinks autoload :PaginationLinks
autoload :FragmentCache autoload :FragmentCache
autoload :Link autoload :Link
autoload :Association
autoload :ResourceIdentifier
autoload :Deserialization autoload :Deserialization
# TODO: if we like this abstraction and other API objects to it, # TODO: if we like this abstraction and other API objects to it,
@ -97,7 +99,7 @@ module ActiveModel
end end
def process_resource(serializer, primary) def process_resource(serializer, primary)
resource_identifier = resource_identifier_for(serializer) resource_identifier = JsonApi::ResourceIdentifier.new(serializer).as_json
return false unless @resource_identifiers.add?(resource_identifier) return false unless @resource_identifiers.add?(resource_identifier)
resource_object = resource_object_for(serializer) resource_object = resource_object_for(serializer)
@ -127,37 +129,13 @@ module ActiveModel
process_relationships(serializer, include_tree) process_relationships(serializer, include_tree)
end end
def resource_identifier_type_for(serializer)
return serializer._type if serializer._type
if ActiveModelSerializers.config.jsonapi_resource_type == :singular
serializer.object.class.model_name.singular
else
serializer.object.class.model_name.plural
end
end
def resource_identifier_id_for(serializer)
if serializer.respond_to?(:id)
serializer.id
else
serializer.object.id
end
end
def resource_identifier_for(serializer)
type = resource_identifier_type_for(serializer)
id = resource_identifier_id_for(serializer)
{ id: id.to_s, type: type }
end
def attributes_for(serializer, fields) def attributes_for(serializer, fields)
serializer.attributes(fields).except(:id) serializer.attributes(fields).except(:id)
end end
def resource_object_for(serializer) def resource_object_for(serializer)
resource_object = cache_check(serializer) do resource_object = cache_check(serializer) do
resource_object = resource_identifier_for(serializer) resource_object = JsonApi::ResourceIdentifier.new(serializer).as_json
requested_fields = fieldset && fieldset.fields_for(resource_object[:type]) requested_fields = fieldset && fieldset.fields_for(resource_object[:type])
attributes = attributes_for(serializer, requested_fields) attributes = attributes_for(serializer, requested_fields)
@ -165,7 +143,8 @@ module ActiveModel
resource_object resource_object
end end
relationships = relationships_for(serializer) requested_associations = fieldset.fields_for(resource_object[:type]) || '*'
relationships = relationships_for(serializer, requested_associations)
resource_object[:relationships] = relationships if relationships.any? resource_object[:relationships] = relationships if relationships.any?
links = links_for(serializer) links = links_for(serializer)
@ -174,24 +153,15 @@ module ActiveModel
resource_object resource_object
end end
def relationship_value_for(serializer, options = {}) def relationships_for(serializer, requested_associations)
if serializer.respond_to?(:each)
serializer.map { |s| resource_identifier_for(s) }
else
if options[:virtual_value]
options[:virtual_value]
elsif serializer && serializer.object
resource_identifier_for(serializer)
end
end
end
def relationships_for(serializer)
resource_type = resource_identifier_type_for(serializer)
requested_associations = fieldset.fields_for(resource_type) || '*'
include_tree = IncludeTree.from_include_args(requested_associations) include_tree = IncludeTree.from_include_args(requested_associations)
serializer.associations(include_tree).each_with_object({}) do |association, hash| serializer.associations(include_tree).each_with_object({}) do |association, hash|
hash[association.key] = { data: relationship_value_for(association.serializer, association.options) } hash[association.key] = JsonApi::Association.new(serializer,
association.serializer,
association.options,
association.links,
association.meta)
.as_json
end end
end end

View File

@ -0,0 +1,48 @@
module ActiveModel
class Serializer
module Adapter
class JsonApi
class Association
def initialize(parent_serializer, serializer, options, links, meta)
@object = parent_serializer.object
@scope = parent_serializer.scope
@options = options
@data = data_for(serializer, options)
@links = links
.map { |key, value| { key => Link.new(parent_serializer, value).as_json } }
.reduce({}, :merge)
@meta = meta.respond_to?(:call) ? parent_serializer.instance_eval(&meta) : meta
end
def as_json
hash = {}
hash[:data] = @data if @options[:include_data]
hash[:links] = @links if @links.any?
hash[:meta] = @meta if @meta
hash
end
protected
attr_reader :object, :scope
private
def data_for(serializer, options)
if serializer.respond_to?(:each)
serializer.map { |s| ResourceIdentifier.new(s).as_json }
else
if options[:virtual_value]
options[:virtual_value]
elsif serializer && serializer.object
ResourceIdentifier.new(serializer).as_json
end
end
end
end
end
end
end
end

View File

@ -0,0 +1,41 @@
module ActiveModel
class Serializer
module Adapter
class JsonApi
class ResourceIdentifier
def initialize(serializer)
@id = id_for(serializer)
@type = type_for(serializer)
end
def as_json
{ id: @id.to_s, type: @type }
end
protected
attr_reader :object, :scope
private
def type_for(serializer)
return serializer._type if serializer._type
if ActiveModelSerializers.config.jsonapi_resource_type == :singular
serializer.object.class.model_name.singular
else
serializer.object.class.model_name.plural
end
end
def id_for(serializer)
if serializer.respond_to?(:id)
serializer.id
else
serializer.object.id
end
end
end
end
end
end
end

View File

@ -9,7 +9,7 @@ module ActiveModel
# @example # @example
# Association.new(:comments, CommentSummarySerializer) # Association.new(:comments, CommentSummarySerializer)
# #
Association = Struct.new(:name, :serializer, :options) do Association = Struct.new(:name, :serializer, :options, :links, :meta) do
# @return [Symbol] # @return [Symbol]
# #
def key def key

View File

@ -34,6 +34,38 @@ module ActiveModel
# So you can inspect reflections in your Adapters. # So you can inspect reflections in your Adapters.
# #
class Reflection < Field class Reflection < Field
def initialize(*)
super
@_links = {}
@_include_data = true
end
def link(name, value = nil, &block)
@_links[name] = block || value
nil
end
def meta(value = nil, &block)
@_meta = block || value
nil
end
def include_data(value = true)
@_include_data = value
nil
end
def value(serializer)
@object = serializer.object
@scope = serializer.scope
if block
instance_eval(&block)
else
serializer.read_attribute_for_serialization(name)
end
end
# Build association. This method is used internally to # Build association. This method is used internally to
# build serializer's association by its reflection. # build serializer's association by its reflection.
# #
@ -59,6 +91,7 @@ module ActiveModel
association_value = value(subject) association_value = value(subject)
reflection_options = options.dup reflection_options = options.dup
serializer_class = subject.class.serializer_for(association_value, reflection_options) serializer_class = subject.class.serializer_for(association_value, reflection_options)
reflection_options[:include_data] = @_include_data
if serializer_class if serializer_class
begin begin
@ -73,9 +106,13 @@ module ActiveModel
reflection_options[:virtual_value] = association_value reflection_options[:virtual_value] = association_value
end end
Association.new(name, serializer, reflection_options) Association.new(name, serializer, reflection_options, @_links, @_meta)
end end
protected
attr_accessor :object, :scope
private private
def serializer_options(subject, parent_serializer_options, reflection_options) def serializer_options(subject, parent_serializer_options, reflection_options)

View File

@ -17,11 +17,23 @@ module ActiveModel
link :yet_another do link :yet_another do
"//example.com/resource/#{object.id}" "//example.com/resource/#{object.id}"
end end
has_many :posts do
link :self do
href '//example.com/link_author/relationships/posts'
meta stuff: 'value'
end
link :related do
href '//example.com/link_author/posts'
meta count: object.posts.count
end
include_data false
end
end end
def setup def setup
@post = Post.new(id: 1337, comments: [], author: nil) @post = Post.new(id: 1337, comments: [], author: nil)
@author = LinkAuthor.new(id: 1337) @author = LinkAuthor.new(id: 1337, posts: [@post])
end end
def test_toplevel_links def test_toplevel_links
@ -61,6 +73,23 @@ module ActiveModel
} }
assert_equal(expected, hash[:data][:links]) assert_equal(expected, hash[:data][:links])
end end
def test_relationship_links
hash = serializable(@author, adapter: :json_api).serializable_hash
expected = {
links: {
self: {
href: '//example.com/link_author/relationships/posts',
meta: { stuff: 'value' }
},
related: {
href: '//example.com/link_author/posts',
meta: { count: 1 }
}
}
}
assert_equal(expected, hash[:data][:relationships][:posts])
end
end end
end end
end end

View File

@ -32,13 +32,13 @@ module ActiveModel
case key case key
when :posts when :posts
assert_equal({}, options) assert_equal({ include_data: true }, options)
assert_kind_of(ActiveModelSerializers.config.collection_serializer, serializer) assert_kind_of(ActiveModelSerializers.config.collection_serializer, serializer)
when :bio when :bio
assert_equal({}, options) assert_equal({ include_data: true }, options)
assert_nil serializer assert_nil serializer
when :roles when :roles
assert_equal({}, options) assert_equal({ include_data: true }, options)
assert_kind_of(ActiveModelSerializers.config.collection_serializer, serializer) assert_kind_of(ActiveModelSerializers.config.collection_serializer, serializer)
else else
flunk "Unknown association: #{key}" flunk "Unknown association: #{key}"
@ -80,7 +80,7 @@ module ActiveModel
flunk "Unknown association: #{key}" flunk "Unknown association: #{key}"
end end
assert_equal({}, association.options) assert_equal({ include_data: true }, association.options)
end end
end end