class JsonApiServer::RelationshipsBuilder

Description

Part of jsonapi.org/format/#fetching-includes: β€œAn endpoint may support an include request parameter to allow the client to customize which related resources should be returned.β€β€œ

ie., GET /articles/1?include=comments,comment.author,tags HTTP/1.1

Use this class to build relationships and included sections in serializers.

Examples

Relate, relate conditionally, or relate a collection. In this example, Publisher is added only if a condition is met (current user is an admin).

JsonApiServer::RelationshipsBuilder.new
  .relate('author', AuthorSerializer.new(@object.author))
  .relate_each('comments', @object.comments) {|c| CommentSerializer.new(c)}
  .relationships

# produces something like this if current user is an admin:
# {
#   "author"=>{
#      {data: {type: "authors", id: 6, attributes: {first_name: "john", last_name: "Doe"}}}
#   },
#   "publisher"=>{
#     {data: {type: "publishers", id: 1, attributes: {name: "abc"}}}
#   },
#   "comments"=>[
#     {data: {type: "comments", id: 1, attributes: {title: "a", comment: "b"}}},
#     {data: {type: "comments", id: 2, attributes: {title: "c", comment: "d"}}}
#   ]
# }

relationships can include all relationship data or it can reference data in included. To include and relate in one go, include with the :relate option which takes a BaseSerializer#as_json_options hash.

builder = JsonApiServer::RelationshipsBuilder.new
  .include('author', AuthorSerializer.new(@object.author),
      relate: { include: [:relationship_data] })
  .include_each('comments', @object.comments) {|c| CommentSerializer.new(c) }

builder.relationships
# produces something like this if current user is an admin:
# {
#  "author" => {
#    {data: {id: 6, type: "authors"}}
#   },
#  "publisher" => {
#    {links: {self: 'http://.../publishers/1'}}
#   }
# }

builder.included
# produces something like this:
# [
#   {type: "author", id: 6, attributes: {first_name: "john", last_name: "Doe"}},
#   {type: "publisher", id: 1, attributes: {name: "abc"}},
#   {type: "comments", id: 1, attributes: {title: "a", comment: "b"}},
#   {type: "comments", id: 2, attributes: {title: "c", comment: "d"}}
# ]

Public Class Methods

new() click to toggle source
# File lib/json_api_server/relationships_builder.rb, line 67
def initialize
  @relationships = {}
  @included = []
end

Public Instance Methods

include(type, serializer, **options) click to toggle source

Add to included with this method.

Arguments:

  • type - (String) Relationship type/name.

  • serializer - (instance of serializer or something that responds to :as_json) - relationship content.

  • options - (Hash) -

i.e.,

# include and relate
JsonApiServer::RelationshipsBuilder.new(['comment.author'])
  .include('author', author_serializer,
     relate: {include: [:relationship_data]})

# or just include
JsonApiServer::RelationshipsBuilder.new(['author'])
   .include('author', author_serializer)
# File lib/json_api_server/relationships_builder.rb, line 163
def include(type, serializer, **options)
  merge_included(serializer)
  if options[:relate]
    serializer.as_json_options = options[:relate]
    relate(type, serializer)
  end
  self
end
include_each(type, collection, **options) click to toggle source

Add a collection to included with this method.

Arguments:

  • type - (String) Relationship type/name.

  • collection - Collection of objects to pass to block.

  • options - (Hash) -

  • block - Block that returns a serializer or something that repsonds_to as_json.

i.e.,

JsonApiServer::RelationshipsBuilder.new(['comments'])
   .include_each('comments', @comments) {|c| CommentSerializer.new(c)}
# File lib/json_api_server/relationships_builder.rb, line 187
def include_each(type, collection, **options)
  collection.each { |item| include(type, yield(item), options) }
  self
end
included() click to toggle source

Returns included object. Includes added with include, include_if, include_each.

# File lib/json_api_server/relationships_builder.rb, line 86
def included
  @included.uniq! if @included.respond_to?(:uniq!)
  @included
end
relate(type, serializer) click to toggle source

Add relationships with this method.

Arguments:

  • type - (String) Relationship type/name.

  • serializer - (instance of serializer or something that responds to :as_json) Content.

i.e.,

JsonApiServer::RelationshipsBuilder.new(['comment.author'])
  .relate('author', author_serializer)

# outputs something like...
# { 'author' => {
#  :data => {
#     :type => "people",
#    :id => 6,
#     :attributes => {:first_name=>"John", :last_name=>"Steinbeck"}
#     }
#   }
# }

JsonApiServer::RelationshipsBuilder.new(['comment.author'])
   .relate('author', author_serializer)

# outputs something like...
# { 'author' => {
#  :data => {
#     :type => "people",
#    :id => 6,
#     :attributes => {:first_name=>"John", :last_name=>"Steinbeck"}
#     }
#   }
# }
# File lib/json_api_server/relationships_builder.rb, line 124
def relate(type, serializer)
  merge_relationship(type, serializer)
  self
end
relate_each(type, collection) { |item| ... } click to toggle source

Add a collection to relationships with this method.

Arguments:

  • type - (String) Relationship type/name.

  • collection - Collection of objects to pass to serializer.

  • block - Block that returns a serializer or something that repsonds_to as_json.

i.e.,

JsonApiServer::RelationshipsBuilder.new(['comments'])
  .relate_each('comments', @comments) { |c| CommentSerializer.new(c) }
# File lib/json_api_server/relationships_builder.rb, line 140
def relate_each(type, collection)
  collection.each { |item| relate(type, yield(item)) }
  self
end
relationships() click to toggle source

Returns relationships object. Relationships added with relate, relate_if, relate_each and include (with :relate option).

# File lib/json_api_server/relationships_builder.rb, line 74
def relationships
  @relationships.each do |k, v|
    if v.respond_to?(:uniq!)
      v.uniq!
      @relationships[k] = v.first if v.length == 1
    end
  end
  @relationships
end

Protected Instance Methods

merge_included(value) click to toggle source
# File lib/json_api_server/relationships_builder.rb, line 208
def merge_included(value)
  if value.respond_to?(:as_json)
    content = value.respond_to?(:as_json_options) ? value.as_json(include: [:data]) : value.as_json
    @included.push(content[:data]) if content.present?
  end
end
merge_relationship(type, value) click to toggle source
# File lib/json_api_server/relationships_builder.rb, line 194
def merge_relationship(type, value)
  content = value.as_json if value.respond_to?(:as_json)
  return if content.blank?

  if @relationships.key?(type)
    unless @relationships[type].is_a?(Array)
      @relationships[type] = [@relationships[type]]
    end
    @relationships[type].push(content)
  else
    @relationships.merge!(type => content)
  end
end