From 6331474abf72244042b4022368331e3ce5ce739a Mon Sep 17 00:00:00 2001 From: danhodge Date: Mon, 14 Sep 2015 22:07:27 -0400 Subject: [PATCH] support dynamic attributes & merging collection values Work in progress. Allows the parent attribute of a nested attribute structure to be a dynamic attribute that is evaluated during rendering. Adds a 'combine' option that can be used when exposing a collection of items in a single presenter that controls how the individual items are combined. No value maintains the existing behavior, where each item is rendered as JSON and appended to the Array, while a value of :merge results in each item's JSON representation being Hash#merge'd into a single Hash. --- lib/grape_entity/entity.rb | 2 +- lib/grape_entity/exposure/base.rb | 6 +++- lib/grape_entity/exposure/nesting_exposure.rb | 10 ++++++- spec/grape_entity/entity_spec.rb | 28 +++++++++++++++++++ 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/lib/grape_entity/entity.rb b/lib/grape_entity/entity.rb index 89a0f1dd..07070d6c 100644 --- a/lib/grape_entity/entity.rb +++ b/lib/grape_entity/entity.rb @@ -498,7 +498,7 @@ def to_xml(options = {}) # All supported options. OPTIONS = [ - :rewrite, :as, :if, :unless, :using, :with, :proc, :documentation, :format_with, :safe, :attr_path, :if_extras, :unless_extras + :rewrite, :as, :if, :unless, :using, :with, :proc, :documentation, :format_with, :safe, :attr_path, :if_extras, :unless_extras, :combine ].to_set.freeze # Merges the given options with current block options. diff --git a/lib/grape_entity/exposure/base.rb b/lib/grape_entity/exposure/base.rb index c0c16199..27c75ac6 100644 --- a/lib/grape_entity/exposure/base.rb +++ b/lib/grape_entity/exposure/base.rb @@ -64,7 +64,11 @@ def serializable_value(entity, options) if partial_output.respond_to?(:serializable_hash) partial_output.serializable_hash(options) elsif partial_output.is_a?(Array) && partial_output.all? { |o| o.respond_to?(:serializable_hash) } - partial_output.map(&:serializable_hash) + if @options[:combine] == :merge + partial_output.reduce({}) { |memo, output| memo.merge(output.serializable_hash) } + else + partial_output.map(&:serializable_hash) + end elsif partial_output.is_a?(Hash) partial_output.each do |key, value| partial_output[key] = value.serializable_hash if value.respond_to?(:serializable_hash) diff --git a/lib/grape_entity/exposure/nesting_exposure.rb b/lib/grape_entity/exposure/nesting_exposure.rb index 4e2dfc49..01b7a8ab 100644 --- a/lib/grape_entity/exposure/nesting_exposure.rb +++ b/lib/grape_entity/exposure/nesting_exposure.rb @@ -57,7 +57,15 @@ def serializable_value(entity, options) normalized_exposures(entity, new_options).each_with_object({}) do |exposure, output| exposure.with_attr_path(entity, new_options) do result = exposure.serializable_value(entity, new_options) - output[exposure.key] = result + + exposure_key = exposure.key.to_s + actual_key = if exposure_key.start_with?('__') + entity.delegate_attribute(exposure_key[2..-1].to_sym) + else + exposure.key + end + + output[actual_key] = result end end end diff --git a/spec/grape_entity/entity_spec.rb b/spec/grape_entity/entity_spec.rb index 06229856..2102b44d 100644 --- a/spec/grape_entity/entity_spec.rb +++ b/spec/grape_entity/entity_spec.rb @@ -842,6 +842,34 @@ class Parent < Person expect(representation).to eq(id: nil, name: nil, user: { id: nil, name: nil, email: nil }) end end + + context 'dynamic attribute' do + it 'determines the attribute value during representation' do + element_entity = Class.new(Grape::Entity) + element_entity.expose(:__date) do + element_entity.expose :fname, as: :first_name + element_entity.expose :last_name + end + + collection_entity = Class.new(Grape::Entity) + collection_entity.present_collection(true) + collection_entity.expose(:items, using: element_entity, combine: :merge) + + values = [ + OpenStruct.new(date: '2015-09-01', fname: 'First', last_name: 'L.'), + OpenStruct.new(date: '2015-08-01', fname: 'F.', last_name: 'Last') + ] + + serialized = collection_entity.represent(values).serializable_hash + + expect( + serialized[:items] + ).to eq({ + '2015-09-01' => { first_name: 'First', last_name: 'L.' }, + '2015-08-01' => { first_name: 'F.', last_name: 'Last' } + }) + end + end end end