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

Expose collection of items of different types without keys

Open ghost opened this issue 10 years ago • 6 comments

Hi.

I've recently tried to expose collection of objects of different types, but it seems that it's not possible with a current version.

Here's the result I'm trying to achieve:

{
  included: [
    {
      id: 1,
      type: 'posts',
      attributes: {
        title: 'Title 1',
        date: '2015-05-01'
      }
    },
    {
      id: 2,
      type: 'posts',
      attributes: {
        title: 'Title 2',
        date: '2015-05-02'
      }
    },
    {
      id: 1,
      type: 'users',
      attributes: {
        first_name: 'John',
        last_name: 'Doe',
        email: '[email protected]'
      }
    }
  ]
}

And classes:

class Result < Grape::Entity
  expose :included, documentation: { is_array: true } do
    expose :posts, using: Post
    expose :user, using: User
  end
end

class Post < Grape::Entity
  expose :id
  expose :type
  expose :attributes do
    expose :title
    expose :date
  end
end

class User < Grape::Entity
  expose :id
  expose :type
  expose :attributes do
    expose :first_name
    expose :last_name
    expose :email
  end
end

Is there something I am missing?

ghost avatar Jul 08 '15 16:07 ghost

Oh yeah, and the response I'm getting is:

{
  included: [
   posts: [
   {
      id: 1,
      type: 'posts',
      attributes: {
        title: 'Title 1',
        date: '2015-05-01'
      }
    },
    {
      id: 2,
      type: 'posts',
      attributes: {
        title: 'Title 2',
        date: '2015-05-02'
      }
   }
   ],
   user:  {
      id: 1,
      type: 'users',
      attributes: {
        first_name: 'John',
        last_name: 'Doe',
        email: '[email protected]'
      }
    }
  ]
}

ghost avatar Jul 08 '15 16:07 ghost

It will be possible to achieve this when #56 will be fixed. I made an attempt here: #151. When #56 will be fixed, try this example (you can already try this on my exposures branch)

require 'grape_entity'
require 'date'
require 'json'

Post = Struct.new(:id, :title, :date) do
  def type
    'posts'
  end
end

User = Struct.new(:id, :first_name, :last_name) do
  def type
    'users'
  end
end

class PostAttributes < Grape::Entity
  expose :title
  expose :date
end

class UserAttributes < Grape::Entity
  expose :first_name
  expose :last_name
end

class ResultEntity < Grape::Entity
  class Included < Grape::Entity
    expose :id
    expose :type
    expose :attributes,
      using: PostAttributes,
      if: ->(obj, opts) { obj.is_a? Post },
      proc: ->(obj, opts) { obj }
    expose :attributes,
      using: UserAttributes,
      if: ->(obj, opts) { obj.is_a? User },
      proc: ->(obj, opts) { obj }
  end

  def included
    object[:posts] + [object[:user]]
  end

  expose :included, using: Included
end

model = {
  posts: [
    Post.new(11, 'post #1', Date.parse('2015-01-01')),
    Post.new(12, 'post #2', Date.parse('2015-02-01'))
  ],
  user: User.new(1, 'Vladimir', 'Kochnev')
}

puts JSON.pretty_generate ResultEntity.represent(model, serializable: true)

Output:

{
  "included": [
    {
      "id": 11,
      "type": "posts",
      "attributes": {
        "title": "post #1",
        "date": "2015-01-01"
      }
    },
    {
      "id": 12,
      "type": "posts",
      "attributes": {
        "title": "post #2",
        "date": "2015-02-01"
      }
    },
    {
      "id": 1,
      "type": "users",
      "attributes": {
        "first_name": "Vladimir",
        "last_name": "Kochnev"
      }
    }
  ]
}

Main caveat of this approach: as more included entities will be added, more :if conditions you would need. It may be even good in some cases but to fully bypass I'd like to implement lambda passing to using option.

marshall-lee avatar Jul 21 '15 21:07 marshall-lee

Also it looks like you're trying to implement JSON API compliant entities. I think it deserves its own implementation. Something like Grape::Entity::JsonApi.

marshall-lee avatar Jul 22 '15 16:07 marshall-lee

Thanks for the heads up. And yes, I was trying to implement JSON API v1.0, though unsuccessfully with Grape::Entity, so I switched over to Roar. Another problem is that Grape Swagger does not really support deeply nested models/parameters, but that's another story.

ghost avatar Jul 22 '15 16:07 ghost

I have been using Roar in production as well, and cannot encourage everyone to stick to a Hypermedia standard enough. So, I would welcome Grape::Entity support for HAL and JSON API as well.

dblock avatar Jul 23 '15 11:07 dblock

Hey guys, I was also trying to implement a JSON API using grape / grape-entity / grape-sagger. So far, I spent half a day trying to figure out which solution might fit all my needs...

francois-belle avatar Apr 10 '19 09:04 francois-belle