lib/grape_entity/exposure/nesting_exposure.rb
# frozen_string_literal: true
module Grape
class Entity
module Exposure
class NestingExposure < Base
attr_reader :nested_exposures
def setup(nested_exposures = [])
@nested_exposures = NestedExposures.new(nested_exposures)
end
def dup_args
[*super, @nested_exposures.map(&:dup)]
end
def ==(other)
super && @nested_exposures == other.nested_exposures
end
def nesting?
true
end
def find_nested_exposure(attribute)
nested_exposures.find_by(attribute)
end
def valid?(entity)
nested_exposures.all? { |e| e.valid?(entity) }
end
def value(entity, options)
map_entity_exposures(entity, options) do |exposure, nested_options|
exposure.value(entity, nested_options)
end
end
def serializable_value(entity, options)
map_entity_exposures(entity, options) do |exposure, nested_options|
exposure.serializable_value(entity, nested_options)
end
end
def valid_value_for(key, entity, options)
new_options = nesting_options_for(options)
key_exposures = normalized_exposures(entity, new_options).select { |e| e.key(entity) == key }
key_exposures.map do |exposure|
exposure.with_attr_path(entity, new_options) do
exposure.valid_value(entity, new_options)
end
end.last
end
# if we have any nesting exposures with the same name.
# delegate :deep_complex_nesting?(entity), to: :nested_exposures
def deep_complex_nesting?(entity)
nested_exposures.deep_complex_nesting?(entity)
end
private
def nesting_options_for(options)
if @key
options.for_nesting(@key)
else
options
end
end
def easy_normalized_exposures(entity, options)
nested_exposures.select do |exposure|
exposure.with_attr_path(entity, options) do
exposure.should_expose?(entity, options)
end
end
end
# This method 'merges' subsequent nesting exposures with the same name if it's needed
def normalized_exposures(entity, options)
return easy_normalized_exposures(entity, options) unless deep_complex_nesting?(entity) # optimization
table = nested_exposures.each_with_object({}) do |exposure, output|
should_expose = exposure.with_attr_path(entity, options) do
exposure.should_expose?(entity, options)
end
next unless should_expose
output[exposure.key(entity)] ||= []
output[exposure.key(entity)] << exposure
end
table.map do |key, exposures|
last_exposure = exposures.last
if last_exposure.nesting?
# For the given key if the last candidates for exposing are nesting then combine them.
nesting_tail = []
exposures.reverse_each do |exposure|
nesting_tail.unshift exposure if exposure.nesting?
end
new_nested_exposures = nesting_tail.flat_map(&:nested_exposures)
NestingExposure.new(key, {}, [], new_nested_exposures).tap do |new_exposure|
if nesting_tail.any? { |exposure| exposure.deep_complex_nesting?(entity) }
new_exposure.instance_variable_set(:@deep_complex_nesting, true)
end
end
else
last_exposure
end
end
end
def map_entity_exposures(entity, options)
new_options = nesting_options_for(options)
output = OutputBuilder.new(entity)
normalized_exposures(entity, new_options).each_with_object(output) do |exposure, out|
exposure.with_attr_path(entity, new_options) do
result = yield(exposure, new_options)
out.add(exposure, result)
end
end
end
end
end
end
end
require 'grape_entity/exposure/nesting_exposure/nested_exposures'
require 'grape_entity/exposure/nesting_exposure/output_builder'