Skip to content

Instantly share code, notes, and snippets.

@hoffm
Last active May 21, 2018 15:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hoffm/ed05817d28c261aa4dd078b30d61a7c9 to your computer and use it in GitHub Desktop.
Save hoffm/ed05817d28c261aa4dd078b30d61a7c9 to your computer and use it in GitHub Desktop.
Monkey patch to allow Grape's `present` method to represent list of objects with heterogeneous entities
# ###
# # OVERVIEW
# ###
#
# Monkey patch of Grape DSL to augment behavior in cases where
# we are presenting lists of objects of different types. The
# primary use case for this is the presentation of lists of
# records that inherit from an abstract parent, i.e. single-
# table inheritance.
#
# Grape's default behavior is to present each member of a list
# using the entity associated with the first member of the list.
# The new behavior presents each member of a heterogeneous list
# using that object's own associated entity.
#
# The original `present` method is defined here:
#
# https://github.com/ruby-grape/grape/blob/v1.0.2/lib/grape/dsl/inside_route.rb#L292-L333
#
# ###
# # DETAILS
# ###
#
# More precisely, this code changes the behavior of the `present`
# macro when all of the following conditions are met:
#
# 1. The object to be presented is a list, i.e. it responds to
# `#map`
# 2. No entity is explicitly specified via the `with` option.
# 3. The members of the list are instances of models that
# collectively specify more than one entity class. (Models
# specify an entity class by assigning it to an `Entity`
# constant namespaced to the model.)
#
# When any of these conditions fails, we fallback on the original
# behavior of `present`. However, when all three conditions obtain,
# we invoke a new method, `heterogeneous_present`.
#
# The new method creates a representation of each member of the
# list and combines them into the overall representation. This
# allows each member to be rendered using its own entity, and
# therefore to be represented using its type's specific shape.
#
# ###
# # EXAMPLE
# ###
#
# The new behavior allows presentation of heterogeneous lists.
# For example, suppose you have `Dog` and `Cat` models that
# each inherit from an abstract `Pet` model. `Dog` and `Cat`
# each have fields that the other lacks. These type-specific
# fields are exposed in the models' respective entities. The
# code is this file allows us to render an index view of pets
# with output like the following:
#
# {
# "data" => [
# {"_type" => "Dog", "name" => "meatball", "dog_field" => "ruff"},
# {"_type" => "Cat", "name" => "puffers", "cat_field" => "purr"}
# ],
# "page" => 1
# }
#
# Such a presentation is impossible using the Grape library alone.
module Grape
module DSL
module InsideRoute
alias_method :homogeneous_present, :present
def present(*args)
original_args = args.dup
options = args.count > 1 ? args.extract_options! : {}
key, object = if args.count == 2 && args.first.is_a?(Symbol)
args
else
[nil, args.first]
end
no_explicit_entity = options[:with].nil?
object_is_mappable_list = object.respond_to?(:map) && !object.is_a?(Hash)
if no_explicit_entity && object_is_mappable_list
entities = object.map{ |member| entity_class_for_obj(member, options) }
if entities.uniq.count > 1
return heterogeneous_present(key, object, entities, options)
end
end
homogeneous_present(*original_args)
end
def heterogeneous_present(key, objects, entities, options)
root = options.delete(:root)
representation = objects.zip(entities).map do |object, entity|
entity.present? ? entity_representation_for(entity, object, options) : object
end
representation = { root => representation } if root
if key
representation = (@body || {}).merge(key => representation)
elsif entities.all(&:present?) && @body
raise ArgumentError, "Representation of type #{representation.class} cannot be merged." unless representation.respond_to?(:merge)
representation = @body.merge(representation)
end
body representation
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment