class JSONAPI::ResourceSerializer

Attributes

always_include_to_many_linkage_data[R]
always_include_to_one_linkage_data[R]
fields[R]
include_directives[R]
key_formatter[R]
serialization_options[R]

Public Class Methods

new(primary_resource_klass, options = {}) click to toggle source

initialize Options can include include:

Purpose: determines which objects will be side loaded with the source objects in a linked section
Example: ['comments','author','comments.tags','author.posts']

fields:

Purpose: determines which fields are serialized for a resource type. This encompasses both attributes and
         relationship ids in the links section for a resource. Fields are global for a resource type.
Example: { people: [:id, :email, :comments], posts: [:id, :title, :author], comments: [:id, :body, :post]}

key_formatter: KeyFormatter instance to override the default configuration serialization_options: additional options that will be passed to resource meta and links lambdas

# File lib/jsonapi/resource_serializer.rb, line 20
def initialize(primary_resource_klass, options = {})
  @primary_resource_klass = primary_resource_klass
  @fields                 = options.fetch(:fields, {})
  @include                = options.fetch(:include, [])
  @include_directives     = options[:include_directives]
  @include_directives     ||= JSONAPI::IncludeDirectives.new(@primary_resource_klass, @include)
  @key_formatter          = options.fetch(:key_formatter, JSONAPI.configuration.key_formatter)
  @id_formatter           = ValueFormatter.value_formatter_for(:id)
  @link_builder           = generate_link_builder(primary_resource_klass, options)
  @always_include_to_one_linkage_data = options.fetch(:always_include_to_one_linkage_data,
                                                      JSONAPI.configuration.always_include_to_one_linkage_data)
  @always_include_to_many_linkage_data = options.fetch(:always_include_to_many_linkage_data,
                                                       JSONAPI.configuration.always_include_to_many_linkage_data)
  @serialization_options = options.fetch(:serialization_options, {})

  # Warning: This makes ResourceSerializer non-thread-safe. That's not a problem with the
  # request-specific way it's currently used, though.
  @value_formatter_type_cache = NaiveCache.new{|arg| ValueFormatter.value_formatter_for(arg) }

  @_config_keys = {}
  @_supplying_attribute_fields = {}
  @_supplying_relationship_fields = {}
end

Public Instance Methods

config_description(resource_klass) click to toggle source
# File lib/jsonapi/resource_serializer.rb, line 131
def config_description(resource_klass)
  {
    class_name: self.class.name,
    serialization_options: serialization_options.sort.map(&:as_json),
    supplying_attribute_fields: supplying_attribute_fields(resource_klass).sort,
    supplying_relationship_fields: supplying_relationship_fields(resource_klass).sort,
    link_builder_base_url: link_builder.base_url,
    key_formatter_class: key_formatter.uncached.class.name,
    always_include_to_one_linkage_data: always_include_to_one_linkage_data,
    always_include_to_many_linkage_data: always_include_to_many_linkage_data
  }
end
config_key(resource_klass) click to toggle source
# File lib/jsonapi/resource_serializer.rb, line 123
def config_key(resource_klass)
  @_config_keys.fetch resource_klass do
    desc = self.config_description(resource_klass).map(&:inspect).join(",")
    key = JSONAPI.configuration.resource_cache_digest_function.call(desc)
    @_config_keys[resource_klass] = "SRLZ-#{key}"
  end
end
format_key(key) click to toggle source
# File lib/jsonapi/resource_serializer.rb, line 111
def format_key(key)
  @key_formatter.format(key)
end
format_value(value, format) click to toggle source
# File lib/jsonapi/resource_serializer.rb, line 119
def format_value(value, format)
  @value_formatter_type_cache.get(format).format(value)
end
object_hash(source, relationship_data) click to toggle source
# File lib/jsonapi/resource_serializer.rb, line 144
def object_hash(source, relationship_data)
  obj_hash = {}

  return obj_hash if source.nil?

  fetchable_fields = Set.new(source.fetchable_fields)

  if source.is_a?(JSONAPI::CachedResponseFragment)
    id_format = source.resource_klass._attribute_options(:id)[:format]

    id_format = 'id' if id_format == :default
    obj_hash['id'] = format_value(source.id, id_format)
    obj_hash['type'] = source.type

    obj_hash['links'] = source.links_json if source.links_json
    obj_hash['attributes'] = source.attributes_json if source.attributes_json

    relationships = cached_relationships_hash(source, fetchable_fields, relationship_data)
    obj_hash['relationships'] = relationships unless relationships.blank?

    obj_hash['meta'] = source.meta_json if source.meta_json
  else
    # TODO Should this maybe be using @id_formatter instead, for consistency?
    id_format = source.class._attribute_options(:id)[:format]
    # protect against ids that were declared as an attribute, but did not have a format set.
    id_format = 'id' if id_format == :default
    obj_hash['id'] = format_value(source.id, id_format)

    obj_hash['type'] = format_key(source.class._type.to_s)

    links = links_hash(source)
    obj_hash['links'] = links unless links.empty?

    attributes = attributes_hash(source, fetchable_fields)
    obj_hash['attributes'] = attributes unless attributes.empty?

    relationships = relationships_hash(source, fetchable_fields, relationship_data)
    obj_hash['relationships'] = relationships unless relationships.blank?

    meta = meta_hash(source)
    obj_hash['meta'] = meta unless meta.empty?
  end

  obj_hash
end
serialize_resource_set_to_hash_plural(resource_set) click to toggle source
# File lib/jsonapi/resource_serializer.rb, line 69
def serialize_resource_set_to_hash_plural(resource_set)

  primary_objects = []
  included_objects = []

  resource_set.resource_klasses.each_value do |resource_klass|
    resource_klass.each_value do |resource|
      serialized_resource = object_hash(resource[:resource], resource[:relationships])

      if resource[:primary]
        primary_objects.push(serialized_resource)
      else
        included_objects.push(serialized_resource)
      end
    end
  end

  primary_hash = { 'data' => primary_objects }

  primary_hash['included'] = included_objects if included_objects.size > 0
  primary_hash
end
serialize_resource_set_to_hash_single(resource_set) click to toggle source

Converts a resource_set to a hash, conforming to the JSONAPI structure

# File lib/jsonapi/resource_serializer.rb, line 45
def serialize_resource_set_to_hash_single(resource_set)

  primary_objects = []
  included_objects = []

  resource_set.resource_klasses.each_value do |resource_klass|
    resource_klass.each_value do |resource|
      serialized_resource = object_hash(resource[:resource], resource[:relationships])

      if resource[:primary]
        primary_objects.push(serialized_resource)
      else
        included_objects.push(serialized_resource)
      end
    end
  end

  fail "Too many primary objects for show" if (primary_objects.count > 1)
  primary_hash = { 'data' => primary_objects[0] }

  primary_hash['included'] = included_objects if included_objects.size > 0
  primary_hash
end
serialize_to_relationship_hash(source, requested_relationship, resource_ids) click to toggle source
# File lib/jsonapi/resource_serializer.rb, line 96
def serialize_to_relationship_hash(source, requested_relationship, resource_ids)
  if requested_relationship.is_a?(JSONAPI::Relationship::ToOne)
    data = to_one_linkage(resource_ids[0])
  else
    data = to_many_linkage(resource_ids)
  end

  rel_hash = { 'data': data }

  links = default_relationship_links(source, requested_relationship)
  rel_hash['links'] = links unless links.blank?

  rel_hash
end
unformat_key(key) click to toggle source
# File lib/jsonapi/resource_serializer.rb, line 115
def unformat_key(key)
  @key_formatter.unformat(key)
end

Private Instance Methods

attributes_hash(source, fetchable_fields) click to toggle source
# File lib/jsonapi/resource_serializer.rb, line 222
def attributes_hash(source, fetchable_fields)
  fields = fetchable_fields & supplying_attribute_fields(source.class)
  fields.each_with_object({}) do |name, hash|
    unless name == :id
      format = source.class._attribute_options(name)[:format]
      hash[format_key(name)] = format_value(source.public_send(name), format)
    end
  end
end
cached_relationships_hash(source, fetchable_fields, relationship_data) click to toggle source
# File lib/jsonapi/resource_serializer.rb, line 279
def cached_relationships_hash(source, fetchable_fields, relationship_data)
  relationships = {}

  source.relationships.try(:each_pair) do |k,v|
    if fetchable_fields.include?(unformat_key(k).to_sym)
      relationships[k.to_sym] = v
    end
  end

  field_set = supplying_relationship_fields(source.resource_klass).collect {|k| format_key(k).to_sym } & relationships.keys

  relationships.each_with_object({}) do |(name, relationship), hash|
    if field_set.include?(name)

      relationship_name = unformat_key(name).to_sym
      relationship_klass = source.resource_klass._relationships[relationship_name]

      if relationship_klass.is_a?(JSONAPI::Relationship::ToOne)
        # include_linkage = @always_include_to_one_linkage_data | relationship_klass.always_include_linkage_data
        if relationship_data[relationship_name]
          rids = relationship_data[relationship_name].first
          relationship['data'] = to_one_linkage(rids)
        end
      else
        # include_linkage = relationship_klass.always_include_linkage_data
        if relationship_data[relationship_name]
          rids = relationship_data[relationship_name]
          relationship['data'] = to_many_linkage(rids)
        end
      end

      hash[format_key(name)] = relationship
    end
  end
end
custom_generation_options() click to toggle source
# File lib/jsonapi/resource_serializer.rb, line 232
def custom_generation_options
  @_custom_generation_options ||= {
    serializer: self,
    serialization_options: @serialization_options
  }
end
meta_hash(source) click to toggle source
# File lib/jsonapi/resource_serializer.rb, line 239
def meta_hash(source)
  meta = source.meta(custom_generation_options)
  (meta.is_a?(Hash) && meta) || {}
end
relationship_object(source, relationship, rid, include_data) click to toggle source
# File lib/jsonapi/resource_serializer.rb, line 372
def relationship_object(source, relationship, rid, include_data)
  if relationship.is_a?(JSONAPI::Relationship::ToOne)
    relationship_object_to_one(source, relationship, rid, include_data)
  elsif relationship.is_a?(JSONAPI::Relationship::ToMany)
    relationship_object_to_many(source, relationship, rid, include_data)
  end
end
relationship_object_to_many(source, relationship, rids, include_data) click to toggle source
# File lib/jsonapi/resource_serializer.rb, line 363
def relationship_object_to_many(source, relationship, rids, include_data)
  link_object_hash = {}

  links = default_relationship_links(source, relationship)
  link_object_hash['links'] = links unless links.blank?
  link_object_hash['data'] = to_many_linkage(rids) if include_data
  link_object_hash
end
relationship_object_to_one(source, relationship, rid, include_data) click to toggle source
# File lib/jsonapi/resource_serializer.rb, line 353
def relationship_object_to_one(source, relationship, rid, include_data)
  link_object_hash = {}

  links = default_relationship_links(source, relationship)

  link_object_hash['links'] = links unless links.blank?
  link_object_hash['data'] = to_one_linkage(rid) if include_data
  link_object_hash
end
relationships_hash(source, fetchable_fields, relationship_data) click to toggle source
# File lib/jsonapi/resource_serializer.rb, line 257
def relationships_hash(source, fetchable_fields, relationship_data)
  relationships = source.class._relationships.select{|k,_v| fetchable_fields.include?(k) }
  field_set = supplying_relationship_fields(source.class) & relationships.keys

  relationships.each_with_object({}) do |(name, relationship), hash|
    include_data = false
    if field_set.include?(name)
      if relationship_data[name]
        include_data = true
        if relationship.is_a?(JSONAPI::Relationship::ToOne)
          rids = relationship_data[name].first
        else
          rids = relationship_data[name]
        end
      end

      ro = relationship_object(source, relationship, rids, include_data)
      hash[format_key(name)] = ro unless ro.blank?
    end
  end
end
supplying_attribute_fields(resource_klass) click to toggle source
# File lib/jsonapi/resource_serializer.rb, line 192
def supplying_attribute_fields(resource_klass)
  @_supplying_attribute_fields.fetch resource_klass do
    attrs = Set.new(resource_klass._attributes.keys.map(&:to_sym))
    cur = resource_klass
    while !cur.root? # do not traverse beyond the first root resource
      if @fields.has_key?(cur._type)
        attrs &= @fields[cur._type]
        break
      end
      cur = cur.superclass
    end
    @_supplying_attribute_fields[resource_klass] = attrs
  end
end
supplying_relationship_fields(resource_klass) click to toggle source
# File lib/jsonapi/resource_serializer.rb, line 207
def supplying_relationship_fields(resource_klass)
  @_supplying_relationship_fields.fetch resource_klass do
    relationships = Set.new(resource_klass._relationships.keys.map(&:to_sym))
    cur = resource_klass
    while !cur.root? # do not traverse beyond the first root resource
      if @fields.has_key?(cur._type)
        relationships &= @fields[cur._type]
        break
      end
      cur = cur.superclass
    end
    @_supplying_relationship_fields[resource_klass] = relationships
  end
end
to_many_linkage(rids) click to toggle source
# File lib/jsonapi/resource_serializer.rb, line 330
def to_many_linkage(rids)
  linkage = []

  rids && rids.each do |details|
    id = details.id
    type = details.resource_klass.try(:_type)
    if type && id
      linkage.append({'type' => format_key(type), 'id' => @id_formatter.format(id)})
    end
  end

  linkage
end
to_one_linkage(rid) click to toggle source
# File lib/jsonapi/resource_serializer.rb, line 344
def to_one_linkage(rid)
  return unless rid

  {
      'type' => format_key(rid.resource_klass._type),
      'id' => @id_formatter.format(rid.id),
  }
end