Grape Entity for a Hash with string keys not working

1.2k Views Asked by At

I am using 'grape-entity', '~> 0.7.1'

I have a hash in the format:

temp_data = [{sheet_index: 0, other_names: []},{'sheet_index' => 1, 'other_names': ['a']}]

And I have the following entities

 class Sheet < Grape::Entity
   expose :sheet_index, documentation: {type: Integer, desc: "Sheet index"}
   expose :other_names, documentation: {type: Array, desc: "Other names"}
 end

 class Sheets < Grape::Entity
  present_collection true

  expose :items, as: 'sheet_history', using Entities::Sheet
 end


# response from the entities
present temp_data, with: Entities::Sheets

Now I need to make sure that no matter the type of keys in my Hash it should still give me the correct output for the above case

expected_response = {"sheet_history" => [{"sheet_index"=>0, "other_names"=>[]}, {"sheet_index"=>1, "other_names"=>["a"]}]}

but the response I am getting is in the format below

actual_response = {"sheet_history" => [{"sheet_index"=>0, "other_names"=>[]}, {"sheet_index"=>nil, "other_names"=>nil}]}

so in the actual response sheet_index and other_names of the second element are nil because their keys were Strings, not Symbols. (Refer to temp_data.)

I have referred to https://github.com/ruby-grape/grape-entity/pull/85 to get the above implementation but still am not able to make it work without using HashWithIndifferentAccess or OpenStructs

1

There are 1 best solutions below

0
Allison On

You are missing a colon after using, but I wouldn't set up multiple entities like that as it's likely to result in wonky behavior. Try this:

# Dummy definition of your class
class Item
  include ActiveModel::Serialization

  attr_accessor :sheet_index
  attr_accessor :other_names

  def initialize(index, names)
    @sheet_index = index
    @other_names = names
  end
end
items = []
items << Item.new(0, [])
items << Item.new(1, ['a'])
=> [
  #<Item:0x00007f860f740e40 @other_names=[], @sheet_index=0>, 
  #<Item:0x00007f860f513618 @other_names=["a"], @sheet_index=1>
]
# Entity Definition
class Sheet < Grape::Entity
  # The first arg here is the key to use for a collection, 
  # the second is the key to use for a single object
  root 'sheet_history', 'sheet_history'

  expose :sheet_index, documentation: {
    type: Integer, 
    desc: "Sheet index" # Plz use locales
  }

  expose :other_names, documentation: {
    type: Array, 
    desc: "Other names" # Plz use locales
  }
end
# Test it
representation = Sheet.represent(items)
=> {
  "sheet_history"=>[
    #<Sheet:70106854276160 sheet_index=0 other_names=[]>, 
    #<Sheet:70106854275680 sheet_index=1 other_names=["a"]>
  ]
}
# This is just more a more readable, but as you can see it's 
# both mapping all the attributes correctly and 
# setting the root key that you wanted:
representation['sheet_history'].map do |r| r.serializable_hash end
=> [
  {
    :sheet_index=>0, 
    :other_names=>[]
  }, 
  {
    :sheet_index=>1, 
    :other_names=>["a"]
  }
]

# Endpoint
get do
  items = current_user.items # or whatever
  present items, with: Entities::Sheet
end

You can send your array of hashes to the represent method, but it doesn't like the stringified key. Ideally you should be passing DB objects to your entity instead of hashes but, if you for some reason cannot, I would pass temp_data.map(&:symbolize_keys) as your argument to the entity to ensure the top-level keys in the hash it's parsing are symbols.