grape-entity icon indicating copy to clipboard operation
grape-entity copied to clipboard

Ability to "flatten" nested entities into parent (e.g. for CSV)

Open joelvh opened this issue 11 years ago • 12 comments

This feature allows you to specify another entity to copy exposures from. It's useful when flattening a hierarchy for CSV. It also adds the :object option to exposures that can define an alternate object to get values from. (Specifying an alternate object is necessary when copying exposures because the exposures don't refer to the original entity/model anymore.)

joelvh avatar Jan 08 '14 01:01 joelvh

This would need tests, README and CHANGELOG entries. And of course a passing build. Thanks for contributing!

dblock avatar Jan 08 '14 21:01 dblock

@dblock updated with docs and tests.

joelvh avatar Jan 09 '14 03:01 joelvh

@dblock updated the example in README

joelvh avatar Jan 09 '14 03:01 joelvh

I want to play devil's advocate for this feature for a second. Can't this be accomplished outside of presenters? I mean flattening a JSON generically may be easier (like this or this).

dblock avatar Jan 09 '14 21:01 dblock

@dblock Interestingly enough, we took that approach initially, and then wanted to try and utilize the Entity paradigm to help document the schema automatically.

Initially, we added an Entity#to_csv method that took the JSON representation and flattened it (similarly to the links you posted). However, we are using grape-swagger to generate schema documentation for the Swagger UI for testing the API. Flattening the data resulted in the documentation not being accurate -- additional properties magically appearing when using CSV.

The solution was to flatten the entities by copying the exposures so that the schema would describe all attributes, regardless of format. However, the only thing that I wasn't able to implement cleanly in this feature (that we had in our initial hacked-together version of this), was appending " (CSV only)" to the end of each description -- which would indicate why some attributes don't always appear.

You're right, we could simply flatten the data, but that didn't seem to be self-documenting enough and defeated the purpose of using Grape Entity for us :-/

joelvh avatar Jan 09 '14 22:01 joelvh

I'd love to hear what others think of this - will leave it open for a bit. In the meantime you might want to squash the commits/rebase this PR.

dblock avatar Jan 10 '14 22:01 dblock

I stumbled upon this PR looking for that exact functionality. From my point of view it's a perfectly legitimate feature. Consider for instance this case where the internal model doesn't match the representation in the API:

class ItemRevision
  (...) # versioned attributes a, b, c
end
class Item
  (...) # unversioned attributes x, y, z
  has_many ItemRevisions
end

class ItemRevisionEntity < Grape::Entity
  expose :a, :b, :c
end
class ItemEntity < Grape::Entity
  expose :x, :y, :z
  merge_with ItemRevisionEntity { object.current_revision }
  expose :revisions, using: ItemRevisionEntity, if: {history: true}
end

I.e., the versioning might be irrelevant for some public API and is therefore hidden by only including the current revision, while the revision entity might be useful on its own in other contexts.
Moreover, I agree with Joel that grape-entity derives most of its usefulness from authoritatively defining the (e.g. ReST) resource. Consequently I think it should reflect the exact format that calls to the API will give you.

If I can help please let me know, I'll be using this one way or the other.

gsps avatar Feb 09 '14 00:02 gsps

I'll leave this open for a bit. FWIW, this morning my inclination is to merge this.

dblock avatar Feb 10 '14 11:02 dblock

PR allows make entity partials in grape-entity. :+1: Example:

class DuckOptionsEntity < Grape::Entity
  expose :duck_specific_attribute
end
class DogOptionsEntity < Grape::Entity
  expose :dog_specific_attribute
end
class AnimalEntity < Grape::Entity
  expose :nickname, :type
  merge_with DogOptionsEntity, if: lambda { |object,options| object.dog? } 
  merge_with DuckOptionsEntity, if: lambda { |object,options| object.duck? } 
end

Usage:

present Animal.all, with: AnimalEntity

Result

[
  {
    "nickname": "puppy",
    "type": "dog",
    "dog_specific_attribute": "value"
  },
  {
    "nickname": "scrooge",
    "type": "duck",
    "duck_specific_attribute": "value"
  }
]

Is there any way to make it with standard grape-enitity?

donbobka avatar Apr 24 '14 19:04 donbobka

I am still sitting on this, I'll take a close look. Would appreciate other people's opinion. Generally, I think I want to weed the "flatten" part out of this, the merge_with part is great.

dblock avatar Apr 25 '14 11:04 dblock

:+1: on getting the merge_with bits pulled in

brianphillips avatar May 11 '15 13:05 brianphillips

@brianphillips Want to try and extract a merge_with PR out of this ?

dblock avatar May 11 '15 13:05 dblock