lib/jsonapi/resource_serializer.rb in jsonapi-resources-0.4.4 vs lib/jsonapi/resource_serializer.rb in jsonapi-resources-0.5.0
- old
+ new
@@ -5,25 +5,28 @@
# 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
- # association ids in the links section for a resource. Fields are global for a resource type.
+ # 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 class to override the default configuration
# base_url: a string to prepend to generated resource links
- def initialize(primary_resource_klass, options = {})
- @primary_resource_klass = primary_resource_klass
- @primary_class_name = @primary_resource_klass._type
+ attr_reader :url_generator
- @fields = options.fetch(:fields, {})
- @include = options.fetch(:include, [])
+ def initialize(primary_resource_klass, options = {})
+ @primary_class_name = primary_resource_klass._type
+ @fields = options.fetch(:fields, {})
+ @include = options.fetch(:include, [])
@include_directives = options[:include_directives]
- @key_formatter = options.fetch(:key_formatter, JSONAPI.configuration.key_formatter)
- @route_formatter = options.fetch(:route_formatter, JSONAPI.configuration.route_formatter)
- @base_url = options.fetch(:base_url, '')
+ @key_formatter = options.fetch(:key_formatter, JSONAPI.configuration.key_formatter)
+ @url_generator = 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)
end
# Converts a single resource, or an array of resources to a hash, conforming to the JSONAPI structure
def serialize_to_hash(source)
is_resource_collection = source.respond_to?(:to_ary)
@@ -49,28 +52,28 @@
primary_hash[:included] = included_objects if included_objects.size > 0
primary_hash
end
- def serialize_to_links_hash(source, requested_association)
- if requested_association.is_a?(JSONAPI::Association::HasOne)
- data = has_one_linkage(source, requested_association)
+ def serialize_to_links_hash(source, requested_relationship)
+ if requested_relationship.is_a?(JSONAPI::Relationship::ToOne)
+ data = to_one_linkage(source, requested_relationship)
else
- data = has_many_linkage(source, requested_association)
+ data = to_many_linkage(source, requested_relationship)
end
{
links: {
- self: self_link(source, requested_association),
- related: related_link(source, requested_association)
+ self: self_link(source, requested_relationship),
+ related: related_link(source, requested_relationship)
},
data: data
}
end
def find_link(query_params)
- "#{@base_url}/#{formatted_module_path_from_klass(@primary_resource_klass)}#{@route_formatter.format(@primary_resource_klass._type.to_s)}?#{query_params.to_query}"
+ url_generator.query_link(query_params)
end
private
# Process the primary source object(s). This will then serialize associated object recursively based on the
@@ -79,22 +82,22 @@
# The fields options controls both fields and included links references.
def process_primary(source, include_directives)
if source.respond_to?(:to_ary)
source.each do |resource|
id = resource.id
- if already_serialized?(@primary_class_name, id)
+ if already_serialized?(resource.class._type, id)
set_primary(@primary_class_name, id)
end
- add_included_object(@primary_class_name, id, object_hash(resource, include_directives), true)
+ add_included_object(id, object_hash(resource, include_directives), true)
end
else
return {} if source.nil?
resource = source
id = resource.id
- add_included_object(@primary_class_name, id, object_hash(source, include_directives), true)
+ add_included_object(id, object_hash(source, include_directives), true)
end
end
# Returns a serialized hash for the source model
def object_hash(source, include_directives)
@@ -129,64 +132,64 @@
fields = requested & fields unless requested.nil?
fields.each_with_object({}) do |name, hash|
format = source.class._attribute_options(name)[:format]
unless name == :id
- hash[format_key(name)] = format_value(source.send(name), format)
+ hash[format_key(name)] = format_value(source.public_send(name), format)
end
end
end
def relationship_data(source, include_directives)
- associations = source.class._associations
+ relationships = source.class._relationships
requested = requested_fields(source.class._type)
- fields = associations.keys
+ fields = relationships.keys
fields = requested & fields unless requested.nil?
field_set = Set.new(fields)
- included_associations = source.fetchable_fields & associations.keys
+ included_relationships = source.fetchable_fields & relationships.keys
data = {}
- associations.each_with_object(data) do |(name, association), hash|
- if included_associations.include? name
+ relationships.each_with_object(data) do |(name, relationship), hash|
+ if included_relationships.include? name
ia = include_directives[:include_related][name]
include_linkage = ia && ia[:include]
include_linked_children = ia && !ia[:include_related].empty?
if field_set.include?(name)
- hash[format_key(name)] = link_object(source, association, include_linkage)
+ hash[format_key(name)] = link_object(source, relationship, include_linkage)
end
- type = association.type
+ type = relationship.type
# If the object has been serialized once it will be in the related objects list,
# but it's possible all children won't have been captured. So we must still go
- # through the associations.
+ # through the relationships.
if include_linkage || include_linked_children
- if association.is_a?(JSONAPI::Association::HasOne)
- resource = source.send(name)
+ if relationship.is_a?(JSONAPI::Relationship::ToOne)
+ resource = source.public_send(name)
if resource
id = resource.id
- type = association.type_for_source(source)
- associations_only = already_serialized?(type, id)
- if include_linkage && !associations_only
- add_included_object(type, id, object_hash(resource, ia))
- elsif include_linked_children || associations_only
+ type = relationship.type_for_source(source)
+ relationships_only = already_serialized?(type, id)
+ if include_linkage && !relationships_only
+ add_included_object(id, object_hash(resource, ia))
+ elsif include_linked_children || relationships_only
relationship_data(resource, ia)
end
end
- elsif association.is_a?(JSONAPI::Association::HasMany)
- resources = source.send(name)
+ elsif relationship.is_a?(JSONAPI::Relationship::ToMany)
+ resources = source.public_send(name)
resources.each do |resource|
id = resource.id
- associations_only = already_serialized?(type, id)
- if include_linkage && !associations_only
- add_included_object(type, id, object_hash(resource, ia))
- elsif include_linked_children || associations_only
+ relationships_only = already_serialized?(type, id)
+ if include_linkage && !relationships_only
+ add_included_object(id, object_hash(resource, ia))
+ elsif include_linked_children || relationships_only
relationship_data(resource, ia)
end
end
end
end
@@ -194,109 +197,94 @@
end
end
def relationship_links(source)
links = {}
- links[:self] = self_href(source)
+ links[:self] = url_generator.self_link(source)
links
end
- def formatted_module_path(source)
- formatted_module_path_from_klass(source.class)
- end
-
- def formatted_module_path_from_klass(klass)
- klass.name =~ /::[^:]+\Z/ ? (@route_formatter.format($`).freeze.gsub('::', '/') + '/').downcase : ''
- end
-
- def self_href(source)
- "#{@base_url}/#{formatted_module_path(source)}#{@route_formatter.format(source.class._type.to_s)}/#{source.id}"
- end
-
def already_serialized?(type, id)
type = format_key(type)
@included_objects.key?(type) && @included_objects[type].key?(id)
end
- def format_route(route)
- @route_formatter.format(route.to_s)
+ def self_link(source, relationship)
+ url_generator.relationships_self_link(source, relationship)
end
- def self_link(source, association)
- "#{self_href(source)}/relationships/#{format_route(association.name)}"
+ def related_link(source, relationship)
+ url_generator.relationships_related_link(source, relationship)
end
- def related_link(source, association)
- "#{self_href(source)}/#{format_route(association.name)}"
- end
-
- def has_one_linkage(source, association)
+ def to_one_linkage(source, relationship)
linkage = {}
- linkage_id = foreign_key_value(source, association)
+ linkage_id = foreign_key_value(source, relationship)
if linkage_id
- linkage[:type] = format_key(association.type_for_source(source))
+ linkage[:type] = format_key(relationship.type_for_source(source))
linkage[:id] = linkage_id
else
linkage = nil
end
linkage
end
- def has_many_linkage(source, association)
+ def to_many_linkage(source, relationship)
linkage = []
- linkage_types_and_values = foreign_key_types_and_values(source, association)
+ linkage_types_and_values = foreign_key_types_and_values(source, relationship)
linkage_types_and_values.each do |type, value|
linkage.append({type: format_key(type), id: value})
end
linkage
end
- def link_object_has_one(source, association)
+ def link_object_to_one(source, relationship, include_linkage)
+ include_linkage = include_linkage | @always_include_to_one_linkage_data | relationship.always_include_linkage_data
link_object_hash = {}
link_object_hash[:links] = {}
- link_object_hash[:links][:self] = self_link(source, association)
- link_object_hash[:links][:related] = related_link(source, association)
- link_object_hash[:data] = has_one_linkage(source, association)
+ link_object_hash[:links][:self] = self_link(source, relationship)
+ link_object_hash[:links][:related] = related_link(source, relationship)
+ link_object_hash[:data] = to_one_linkage(source, relationship) if include_linkage
link_object_hash
end
- def link_object_has_many(source, association, include_linkage)
+ def link_object_to_many(source, relationship, include_linkage)
link_object_hash = {}
link_object_hash[:links] = {}
- link_object_hash[:links][:self] = self_link(source, association)
- link_object_hash[:links][:related] = related_link(source, association)
- link_object_hash[:data] = has_many_linkage(source, association) if include_linkage
+ link_object_hash[:links][:self] = self_link(source, relationship)
+ link_object_hash[:links][:related] = related_link(source, relationship)
+ link_object_hash[:data] = to_many_linkage(source, relationship) if include_linkage
link_object_hash
end
- def link_object(source, association, include_linkage = false)
- if association.is_a?(JSONAPI::Association::HasOne)
- link_object_has_one(source, association)
- elsif association.is_a?(JSONAPI::Association::HasMany)
- link_object_has_many(source, association, include_linkage)
+ def link_object(source, relationship, include_linkage = false)
+ if relationship.is_a?(JSONAPI::Relationship::ToOne)
+ link_object_to_one(source, relationship, include_linkage)
+ elsif relationship.is_a?(JSONAPI::Relationship::ToMany)
+ link_object_to_many(source, relationship, include_linkage)
end
end
- # Extracts the foreign key value for a has_one association.
- def foreign_key_value(source, association)
- foreign_key = association.foreign_key
- value = source.send(foreign_key)
+ # Extracts the foreign key value for a to_one relationship.
+ def foreign_key_value(source, relationship)
+ foreign_key = relationship.foreign_key
+ value = source.public_send(foreign_key)
IdValueFormatter.format(value)
end
- def foreign_key_types_and_values(source, association)
- if association.is_a?(JSONAPI::Association::HasMany)
- if association.polymorphic?
- source.model.send(association.name).pluck(:type, :id).map do |type, id|
+ def foreign_key_types_and_values(source, relationship)
+ if relationship.is_a?(JSONAPI::Relationship::ToMany)
+ if relationship.polymorphic?
+ source.model.public_send(relationship.name).pluck(:type, :id).map do |type, id|
[type.pluralize, IdValueFormatter.format(id)]
end
else
- source.send(association.foreign_key).map do |value|
- [association.type, IdValueFormatter.format(value)]
+ source.public_send(relationship.foreign_key).map do |value|
+ [relationship.type, IdValueFormatter.format(value)]
end
end
end
end
@@ -305,16 +293,17 @@
type = format_key(type)
@included_objects[type][id][:primary] = true
end
# Collects the hashes for all objects processed by the serializer
- def add_included_object(type, id, object_hash, primary = false)
- type = format_key(type)
+ def add_included_object(id, object_hash, primary = false)
+ type = object_hash['type']
@included_objects[type] = {} unless @included_objects.key?(type)
if already_serialized?(type, id)
+ @included_objects[type][id][:object_hash].merge!(object_hash)
set_primary(type, id) if primary
else
@included_objects[type].store(id, primary: primary, object_hash: object_hash)
end
end
@@ -324,8 +313,16 @@
end
def format_value(value, format)
value_formatter = JSONAPI::ValueFormatter.value_formatter_for(format)
value_formatter.format(value)
+ end
+
+ def generate_link_builder(primary_resource_klass, options)
+ LinkBuilder.new(
+ base_url: options.fetch(:base_url, ''),
+ route_formatter: options.fetch(:route_formatter, JSONAPI.configuration.route_formatter),
+ primary_resource_klass: primary_resource_klass,
+ )
end
end
end