lib/jsonapi/resource_serializer.rb in jsonapi-resources-0.3.3 vs lib/jsonapi/resource_serializer.rb in jsonapi-resources-0.4.0

- old
+ new

@@ -16,25 +16,25 @@ @primary_resource_klass = primary_resource_klass @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, '') 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) @included_objects = {} + @include_directives ||= JSONAPI::IncludeDirectives.new(@include) - requested_associations = parse_includes(@include) + process_primary(source, @include_directives.include_directives) - process_primary(source, requested_associations) - included_objects = [] primary_objects = [] @included_objects.each_value do |objects| objects.each_value do |object| if object[:primary] @@ -45,15 +45,11 @@ end end primary_hash = {data: is_resource_collection ? primary_objects : primary_objects[0]} - if included_objects.size > 0 - primary_hash[:included] = included_objects - else - primary_hash - end + 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) @@ -70,64 +66,53 @@ data: data } end private - # Convert an array of associated objects to include along with the primary document in the form of - # ['comments','author','comments.tags','author.posts'] into a structure that tells what we need to include - # from each association. - def parse_includes(includes) - requested_associations = {} - includes.each do |include| - include = include.to_s.underscore - - pos = include.index('.') - if pos - association_name = include[0, pos].to_sym - requested_associations[association_name] ||= {} - requested_associations[association_name].store(:include_children, true) - requested_associations[association_name].store(:include_related, parse_includes([include[pos+1, include.length]])) - else - association_name = include.to_sym - requested_associations[association_name] ||= {} - requested_associations[association_name].store(:include, true) - end - end if includes.is_a?(Array) - return requested_associations - end - # Process the primary source object(s). This will then serialize associated object recursively based on the # requested includes. Fields are controlled fields option for each resource type, such # as fields: { people: [:id, :email, :comments], posts: [:id, :title, :author], comments: [:id, :body, :post]} # The fields options controls both fields and included links references. - def process_primary(source, requested_associations) + 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) set_primary(@primary_class_name, id) end - add_included_object(@primary_class_name, id, object_hash(resource, requested_associations), true) + add_included_object(@primary_class_name, 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, requested_associations), true) + add_included_object(@primary_class_name, id, object_hash(source, include_directives), true) end end # Returns a serialized hash for the source model - def object_hash(source, requested_associations) - obj_hash = attribute_hash(source) - links = links_hash(source, requested_associations) + def object_hash(source, include_directives) + obj_hash = {} + 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) - obj_hash['id'] ||= format_value(source.id, :id, source) - obj_hash.merge!({links: links}) unless links.empty? + + links = relationship_links(source) + obj_hash['links'] = links unless links.empty? + + attributes = attribute_hash(source) + obj_hash['attributes'] = attributes unless attributes.empty? + + relationships = relationship_data(source, include_directives) + obj_hash['relationships'] = relationships unless relationships.nil? || relationships.empty? + return obj_hash end def requested_fields(model) @fields[model] if @fields @@ -140,24 +125,17 @@ fields = requested & fields end fields.each_with_object({}) do |name, hash| format = source.class._attribute_options(name)[:format] - if format == :default && name == :id - format = 'id' + unless name == :id + hash[format_key(name)] = format_value(source.send(name), format) end - hash[format_key(name)] = format_value( - source.send(name), - format, - source - ) end end - # Returns a hash of links for the requested associations for a resource, filtered by the resource - # class's fetchable method - def links_hash(source, requested_associations) + def relationship_data(source, include_directives) associations = source.class._associations requested = requested_fields(source.class._type) fields = associations.keys unless requested.nil? fields = requested & fields @@ -165,19 +143,18 @@ field_set = Set.new(fields) included_associations = source.fetchable_fields & associations.keys - links = {} - links[:self] = self_href(source) + data = {} - associations.each_with_object(links) do |(name, association), hash| + associations.each_with_object(data) do |(name, association), hash| if included_associations.include? name - ia = requested_associations.is_a?(Hash) ? requested_associations[name] : nil + ia = include_directives[:include_related][name] include_linkage = ia && ia[:include] - include_linked_children = ia && ia[:include_children] + include_linked_children = ia && !ia[:include_related].empty? if field_set.include?(name) hash[format_key(name)] = link_object(source, association, include_linkage) end @@ -191,32 +168,39 @@ resource = source.send(name) if resource id = resource.id associations_only = already_serialized?(type, id) if include_linkage && !associations_only - add_included_object(type, id, object_hash(resource, ia[:include_related])) + add_included_object(type, id, object_hash(resource, ia)) elsif include_linked_children || associations_only - links_hash(resource, ia[:include_related]) + relationship_data(resource, ia) end end elsif association.is_a?(JSONAPI::Association::HasMany) resources = source.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[:include_related])) + add_included_object(type, id, object_hash(resource, ia)) elsif include_linked_children || associations_only - links_hash(resource, ia[:include_related]) + relationship_data(resource, ia) end end end end end end end + def relationship_links(source) + links = {} + links[:self] = self_href(source) + + links + end + def formatted_module_path(source) source.class.name =~ /::[^:]+\Z/ ? (@route_formatter.format($`).freeze.gsub('::', '/') + '/').downcase : '' end def self_href(source) @@ -231,11 +215,11 @@ def format_route(route) @route_formatter.format(route.to_s) end def self_link(source, association) - "#{self_href(source)}/links/#{format_route(association.name)}" + "#{self_href(source)}/relationships/#{format_route(association.name)}" end def related_link(source, association) "#{self_href(source)}/#{format_route(association.name)}" end @@ -261,21 +245,23 @@ linkage end def link_object_has_one(source, association) link_object_hash = {} - link_object_hash[:self] = self_link(source, association) - link_object_hash[:related] = related_link(source, association) - link_object_hash[:linkage] = has_one_linkage(source, association) + 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 end def link_object_has_many(source, association, include_linkage) link_object_hash = {} - link_object_hash[:self] = self_link(source, association) - link_object_hash[:related] = related_link(source, association) - link_object_hash[:linkage] = has_many_linkage(source, association) if include_linkage + 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 end def link_object(source, association, include_linkage = false) if association.is_a?(JSONAPI::Association::HasOne) @@ -289,13 +275,13 @@ def foreign_key_value(source, association) foreign_key = association.foreign_key value = source.send(foreign_key) if association.is_a?(JSONAPI::Association::HasMany) - value.map { |value| IdValueFormatter.format(value, {}) } + value.map { |value| IdValueFormatter.format(value) } elsif association.is_a?(JSONAPI::Association::HasOne) - IdValueFormatter.format(value, {}) + IdValueFormatter.format(value) end end # Sets that an object should be included in the primary document of the response. def set_primary(type, id) @@ -322,11 +308,11 @@ def format_key(key) @key_formatter.format(key) end - def format_value(value, format, source) + def format_value(value, format) value_formatter = JSONAPI::ValueFormatter.value_formatter_for(format) - value_formatter.format(value, source) + value_formatter.format(value) end end end