Method: JSONAPI::Serializer.find_recursive_relationships

Defined in:
lib/jsonapi-serializers/serializer.rb

.find_recursive_relationships(root_object, root_inclusion_tree, results, options) ⇒ Object

Recursively find object relationships and returns a tree of related objects. Example return:

['comments', '1'] => {object: <Comment>, include_linkages: ['author'],
['users', '1'] => <User>, include_linkages: [],
['users', '2'] => <User>, include_linkages: [],

}



363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
# File 'lib/jsonapi-serializers/serializer.rb', line 363

def self.find_recursive_relationships(root_object, root_inclusion_tree, results, options)
  root_inclusion_tree.each do |attribute_name, child_inclusion_tree|
    # Skip the sentinal value, but we need to preserve it for siblings.
    next if attribute_name == :_include

    serializer = JSONAPI::Serializer.find_serializer(root_object, options)
    unformatted_attr_name = serializer.unformat_name(attribute_name).to_sym

    # We know the name of this relationship, but we don't know where it is stored internally.
    # Check if it is a has_one or has_many relationship.
    object = nil
    is_collection = false
    is_valid_attr = false
    if serializer.has_one_relationships.has_key?(unformatted_attr_name)
      is_valid_attr = true
      attr_data = serializer.has_one_relationships[unformatted_attr_name]
      object = serializer.has_one_relationship(unformatted_attr_name, attr_data)
    elsif serializer.has_many_relationships.has_key?(unformatted_attr_name)
      is_valid_attr = true
      is_collection = true
      attr_data = serializer.has_many_relationships[unformatted_attr_name]
      object = serializer.has_many_relationship(unformatted_attr_name, attr_data)
    end

    if !is_valid_attr
      raise JSONAPI::Serializer::InvalidIncludeError.new(
        "'#{attribute_name}' is not a valid include.")
    end

    if attribute_name != serializer.format_name(attribute_name)
      expected_name = serializer.format_name(attribute_name)

      raise JSONAPI::Serializer::InvalidIncludeError.new(
        "'#{attribute_name}' is not a valid include.  Did you mean '#{expected_name}' ?"
      )
    end

    # We're finding relationships for compound documents, so skip anything that doesn't exist.
    next if object.nil?

    # We only include parent values if the sential value _include is set. This satifies the
    # spec note: A request for comments.author should not automatically also include comments
    # in the response. This can happen if the client already has the comments locally, and now
    # wants to fetch the associated authors without fetching the comments again.
    # http://jsonapi.org/format/#fetching-includes
    objects = is_collection ? object : [object]
    if child_inclusion_tree[:_include] == true
      # Include the current level objects if the _include attribute exists.
      # If it is not set, that indicates that this is an inner path and not a leaf and will
      # be followed by the recursion below.
      objects.each do |obj|
        obj_serializer = JSONAPI::Serializer.find_serializer(obj, options)
        # Use keys of ['posts', '1'] for the results to enforce uniqueness.
        # Spec: A compound document MUST NOT include more than one resource object for each
        # type and id pair.
        # http://jsonapi.org/format/#document-structure-compound-documents
        key = [obj_serializer.type, obj_serializer.id]

        # This is special: we know at this level if a child of this parent will also been
        # included in the compound document, so we can compute exactly what linkages should
        # be included by the object at this level. This satisfies this part of the spec:
        #
        # Spec: Resource linkage in a compound document allows a client to link together
        # all of the included resource objects without having to GET any relationship URLs.
        # http://jsonapi.org/format/#document-structure-resource-relationships
        current_child_includes = []
        inclusion_names = child_inclusion_tree.keys.reject { |k| k == :_include }
        inclusion_names.each do |inclusion_name|
          if child_inclusion_tree[inclusion_name][:_include]
            current_child_includes << inclusion_name
          end
        end

        # Special merge: we might see this object multiple times in the course of recursion,
        # so merge the include_linkages each time we see it to load all the relevant linkages.
        current_child_includes += results[key] && results[key][:include_linkages] || []
        current_child_includes.uniq!
        results[key] = {object: obj, include_linkages: current_child_includes}
      end
    end

    # Recurse deeper!
    if !child_inclusion_tree.empty?
      # For each object we just loaded, find all deeper recursive relationships.
      objects.each do |obj|
        find_recursive_relationships(obj, child_inclusion_tree, results, options)
      end
    end
  end
  nil
end