How do I override the root key in a Grape API response payload?

512 views Asked by At
module Entities
  class StuffEntity < Grape::Entity
    root 'stuffs', 'stuff'
    ...

How can I DRY up my code by reusing this entity while still having the flexibility to rename the root keys ('stuffs' and 'stuff') defined in the entity?

I might need to do this in a scenario where I'm exposing a subset of a collection represented by an existing entity or exposing an associated collection that can be represented by an existing entity.

1

There are 1 answers

0
Allison On BEST ANSWER

Hiding the root key when you're exposing an associated object or collection

Let's say I have an object with a name attribute and some collection of scoped_stuff that I want to expose as some_stuffs. I could do that with an entity like this:

module Entities
  class CoolStuffEntity < Grape::Entity
    root 'cool_stuffs', 'cool_stuff'
    
    expose :some_stuffs, documentation: {
      type: Entities::StuffEntity
    } do |object, _options|
      Entities::StuffEntity.represent(
        object.class.scoped_stuff,
        root: false
      )
    end
    
    expose :name, documentation: { type: 'string' }
  end
end

Passing root: false to the represent method ensures that the nested association is represented without a root key. Here are what the representations look like with and without that argument:

# Without root: false
cool_stuff: {
  some_stuffs: { 
    stuffs:    [ /* collection represented by StuffEntity */ ] 
  },
  name: 'Something'
}

# With root: false
cool_stuff: { 
  some_stuffs: [ /* collection represented by StuffEntity */ ],
  name:        'Something'
}

In this instance, passing root: false ensures that the nested entity's root key isn't included in our representation.

Setting a root key name when presenting an entity with no defined root

Let's say we have this entity where we did not specify root:

module Entities
  class StuffEntity < Grape::Entity
    expose :name, documentation: { type: 'string' }
  end
end

The serializable hash for an object represented with this entity will look like: { name: 'Object name' }

In our API, we can specify the response key like so:

get do
  stuff_object = Stuff.find_by(user_id: current_user)

  present stuff_object, 
    with: Entities::StuffEntity,
    root: :stuff
end

So that our response will look like this: { stuff: { name: 'Object name' } }

Note that 'root' accepts string and symbol arguments here.

If you want to rename the root key in your API response

So what if I have an entity where I specified a root key and I want the key in my response to be different (e.g., exposing a subset of the collection)? Instead of using present, I can use represent again. Except this time, instead of disabling the root key by passing 'false', I can give it a key name:

get do
  my_stuff = Stuff.find_by(user_id: current_user)

  Entities::StuffEntity.represent(
    my_stuff,
    root: :my_stuff
  )
end