lib/jsonapi/resource_serializer.rb in jsonapi-resources-0.4.2 vs lib/jsonapi/resource_serializer.rb in jsonapi-resources-0.4.3

- old
+ new

@@ -20,10 +20,11 @@ @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, '') + @scope_id = options[:scope_id] 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) @@ -43,11 +44,11 @@ included_objects.push(object[:object_hash]) end end end - primary_hash = {data: is_resource_collection ? primary_objects : primary_objects[0]} + primary_hash = { data: is_resource_collection ? primary_objects : primary_objects[0] } primary_hash[:included] = included_objects if included_objects.size > 0 primary_hash end @@ -70,10 +71,11 @@ 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}" end private + # 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, include_directives) @@ -98,11 +100,11 @@ # Returns a serialized hash for the source model 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. + # 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) @@ -113,23 +115,21 @@ 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 + obj_hash end def requested_fields(model) @fields[model] if @fields end def attribute_hash(source) requested = requested_fields(source.class._type) fields = source.fetchable_fields & source.class._attributes.keys.to_a - unless requested.nil? - fields = requested & fields - end + 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) @@ -139,13 +139,11 @@ 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 - end + fields = requested & fields unless requested.nil? field_set = Set.new(fields) included_associations = source.fetchable_fields & associations.keys @@ -170,10 +168,11 @@ if include_linkage || include_linked_children if association.is_a?(JSONAPI::Association::HasOne) resource = source.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 relationship_data(resource, ia) @@ -206,20 +205,25 @@ 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 : '' + if klass.name =~ /::[^:]+\Z/ + path = (@route_formatter.format($`).freeze.gsub('::', '/') + '/').downcase + @scope_id ? "#{path}#{@scope_id}/" : path + else + '' + end 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) - return @included_objects.key?(type) && @included_objects[type].key?(id) + @included_objects.key?(type) && @included_objects[type].key?(id) end def format_route(route) @route_formatter.format(route.to_s) end @@ -233,24 +237,26 @@ end def has_one_linkage(source, association) linkage = {} linkage_id = foreign_key_value(source, association) + if linkage_id - linkage[:type] = format_key(association.type) + linkage[:type] = format_key(association.type_for_source(source)) linkage[:id] = linkage_id else linkage = nil end linkage end def has_many_linkage(source, association) linkage = [] - linkage_ids = foreign_key_value(source, association) - linkage_ids.each do |linkage_id| - linkage.append({type: format_key(association.type), id: linkage_id}) + linkage_types_and_values = foreign_key_types_and_values(source, association) + + 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) @@ -277,19 +283,28 @@ elsif association.is_a?(JSONAPI::Association::HasMany) link_object_has_many(source, association, include_linkage) end end - # Extracts the foreign key value for an association. + # 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) + IdValueFormatter.format(value) + end + def foreign_key_types_and_values(source, association) if association.is_a?(JSONAPI::Association::HasMany) - value.map { |value| IdValueFormatter.format(value) } - elsif association.is_a?(JSONAPI::Association::HasOne) - IdValueFormatter.format(value) + if association.polymorphic? + source.model.send(association.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)] + end + end end end # Sets that an object should be included in the primary document of the response. def set_primary(type, id) @@ -299,19 +314,15 @@ # Collects the hashes for all objects processed by the serializer def add_included_object(type, id, object_hash, primary = false) type = format_key(type) - unless @included_objects.key?(type) - @included_objects[type] = {} - end + @included_objects[type] = {} unless @included_objects.key?(type) if already_serialized?(type, id) - if primary - set_primary(type, id) - end + set_primary(type, id) if primary else - @included_objects[type].store(id, {primary: primary, object_hash: object_hash}) + @included_objects[type].store(id, primary: primary, object_hash: object_hash) end end def format_key(key) @key_formatter.format(key)