ruby-on-railsgrape-apigrape-entity

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


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.


Solution

  • 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