Add support for resource-level JSON API links.

This commit is contained in:
Lucas Hosseini 2015-10-06 19:35:09 +02:00
parent 827a623d16
commit 3804dcc238
4 changed files with 108 additions and 5 deletions

View File

@ -50,6 +50,9 @@ module ActiveModel
self._attributes ||= []
class_attribute :_attributes_keys # @api private : maps attribute value to explict key name, @see Serializer#attribute
self._attributes_keys ||= {}
class_attribute :_links # @api private : links definitions, @see Serializer#link
self._links ||= {}
serializer.class_attribute :_cache # @api private : the cache object
serializer.class_attribute :_fragmented # @api private : @see ::fragmented
serializer.class_attribute :_cache_key # @api private : when present, is first item in cache_key
@ -72,6 +75,7 @@ module ActiveModel
caller_line = caller.first
base._attributes = _attributes.dup
base._attributes_keys = _attributes_keys.dup
base._links = _links.dup
base._cache_digest = digest_caller_file(caller_line)
super
end
@ -83,6 +87,10 @@ module ActiveModel
self._type = type
end
def self.link(name, value = nil, &block)
_links[name] = block || value
end
# @example
# class AdminAuthorSerializer < ActiveModel::Serializer
# attributes :id, :name, :recent_edits
@ -249,6 +257,12 @@ module ActiveModel
end
end
# @api private
# Used by JsonApi adapter to build resource links.
def links
self.class._links
end
protected
attr_accessor :instance_options

View File

@ -5,6 +5,7 @@ module ActiveModel
extend ActiveSupport::Autoload
autoload :PaginationLinks
autoload :FragmentCache
autoload :Link
# TODO: if we like this abstraction and other API objects to it,
# then extract to its own file and require it.
@ -94,7 +95,7 @@ module ActiveModel
if serializer.paginated?
hash[:links] ||= {}
hash[:links].update(links_for(serializer, options))
hash[:links].update(pagination_links_for(serializer, options))
end
hash
@ -136,12 +137,21 @@ module ActiveModel
{ id: id.to_s, type: type }
end
def attributes_for(serializer, fields)
serializer.attributes(fields).except(:id)
end
def resource_object_for(serializer)
cache_check(serializer) do
resource_object = resource_identifier_for(serializer)
requested_fields = fieldset && fieldset.fields_for(resource_object[:type])
attributes = serializer.attributes(requested_fields).except(:id)
attributes = attributes_for(serializer, requested_fields)
resource_object[:attributes] = attributes if attributes.any?
links = links_for(serializer)
resource_object[:links] = links if links.any?
resource_object
end
end
@ -204,7 +214,21 @@ module ActiveModel
end
end
def links_for(serializer, options)
def links_for(serializer)
serializer.links.each_with_object({}) do |(name, value), hash|
hash[name] =
if value.respond_to?(:call)
link = Link.new(serializer)
link.instance_eval(&value)
link.to_hash
else
value
end
end
end
def pagination_links_for(serializer, options)
JsonApi::PaginationLinks.new(serializer.object, options[:context]).serializable_hash(options)
end
end

View File

@ -0,0 +1,34 @@
module ActiveModel
class Serializer
module Adapter
class JsonApi
class Link
def initialize(serializer)
@object = serializer.object
@scope = serializer.scope
end
def href(value)
self._href = value
end
def meta(value)
self._meta = value
end
def to_hash
hash = { href: _href }
hash.merge!(meta: _meta) if _meta
hash
end
protected
attr_accessor :_href, :_meta
attr_reader :object, :scope
end
end
end
end
end

View File

@ -5,8 +5,19 @@ module ActiveModel
module Adapter
class JsonApi
class LinksTest < Minitest::Test
LinkAuthor = Class.new(::Model)
class LinkAuthorSerializer < ActiveModel::Serializer
link :self do
href "//example.com/link_author/#{object.id}"
meta stuff: 'value'
end
link :other, '//example.com/resource'
end
def setup
@post = Post.new(id: 1337, comments: [], author: nil)
@author = LinkAuthor.new(id: 1337)
end
def test_toplevel_links
@ -15,16 +26,36 @@ module ActiveModel
adapter: :json_api,
links: {
self: {
href: '//posts'
href: '//example.com/posts',
meta: {
stuff: 'value'
}
}
}).serializable_hash
expected = {
self: {
href: '//posts'
href: '//example.com/posts',
meta: {
stuff: 'value'
}
}
}
assert_equal(expected, hash[:links])
end
def test_resource_links
hash = serializable(@author, adapter: :json_api).serializable_hash
expected = {
self: {
href: '//example.com/link_author/1337',
meta: {
stuff: 'value'
}
},
other: '//example.com/resource'
}
assert_equal(expected, hash[:data][:links])
end
end
end
end