Skip to content

Commit

Permalink
Add support for resource-level JSON API links.
Browse files Browse the repository at this point in the history
  • Loading branch information
beauby committed Oct 26, 2015
1 parent 73cb9f7 commit cf246bc
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 6 deletions.
7 changes: 7 additions & 0 deletions lib/active_model/serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ def self.digest_caller_file(caller_line)
self._attributes ||= []
class_attribute :_attributes_keys
self._attributes_keys ||= {}
class_attribute :links, instance_reader: true
self.links ||= {}
serializer.class_attribute :_cache
serializer.class_attribute :_fragmented
serializer.class_attribute :_cache_key
Expand All @@ -60,6 +62,7 @@ def self.inherited(base)
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
Expand All @@ -68,6 +71,10 @@ def self.type(type)
self._type = type
end

def self.link(name, value = nil, &block)
links[name] = block_given? ? block : value
end

def self.attributes(*attrs)
attrs = attrs.first if attrs.first.class == Array

Expand Down
34 changes: 30 additions & 4 deletions lib/active_model/serializer/adapter/json_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class JsonApi < Base
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.
Expand Down Expand Up @@ -100,7 +101,7 @@ def serializable_hash_for_collection(options)

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

hash
Expand Down Expand Up @@ -142,13 +143,24 @@ def resource_identifier_for(serializer)
{ id: id.to_s, type: type }
end

def attributes_for(serializer, fields)
attributes = serializer.attributes.except(:id)
attributes.slice!(*fields) if fields

attributes
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.except(:id)
attributes.slice!(*requested_fields) if requested_fields
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
Expand Down Expand Up @@ -208,7 +220,21 @@ def add_included_resources_for(serializer, include_tree, primary_data, included)
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
Expand Down
34 changes: 34 additions & 0 deletions lib/active_model/serializer/adapter/json_api/link.rb
Original file line number Diff line number Diff line change
@@ -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
35 changes: 33 additions & 2 deletions test/adapter/json_api/links_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,19 @@ class Serializer
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
Expand All @@ -15,16 +26,36 @@ def test_toplevel_links
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
Expand Down

0 comments on commit cf246bc

Please sign in to comment.