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

Expose default value doesn't work for me

Open j-boers-13 opened this issue 2 years ago • 2 comments

Hi,

First of all thanks for the amazing work! I am using grape entity to serialize ActiveRecord (Rails) objects to a hash, using .respresent(object)

My grape entity looks like this:

class Entities::ProductEntity < Grape::Entity
  expose :platform_id, as: :id
  expose :name
  expose :visible
  expose :main_image
  expose :url
  expose :short_description, default: ''
  expose :brand_id
  expose :category_ids
  expose :relevant_product_ids

  def brand_id
    object.brand&.platform_id if object.respond_to?(:brand)
  end

  def category_ids
    object.categories&.pluck(:platform_id) if object.respond_to?(:categories)
    []
  end

  def relevant_product_ids
    object.relevant_products&.pluck(:platform_id) if object.respond_to?(:relevant_products)
    []
  end
end

The default option on short_description doesn't work, neither did it work if i tried to use it in another entity with default: []

Am i doing something wrong? Or am I on an old version?

I am using https://github.com/ruby-grape/grape-entity#default-value this syntax, and am on grape-entity version 0.10.2

Hope you have some time to help me out, but no worries if not.

Thanks in advance

j-boers-13 avatar Feb 06 '23 12:02 j-boers-13

I'm having a similar issue. When I pass default: [] to a field exposed with using:, it doesn't work.

require 'grape'
require 'grape-entity'

class Address < Grape::Entity
  expose :address_line
  expose :city
end

class Account < Grape::Entity
  expose :name
  expose :state, default: 'pending'
  expose :addresses, using: Address, default: []
end

Account.represent({ name: 'alice' }).as_json
# => {:name=>"alice", :state=>"pending", :addresses=>nil}

The default value for state is used, but for addresses it is nil and not an empty array, as I would expect.

I tried to debug the code to find the issue and I noticed that when I pass serializable: true to as_json, it seems to work:

Account.represent({ name: 'alice' }).as_json(serializable: true)
# => {:name=>"alice", :state=>"pending", :addresses=>[]}

I wasn't able to find why passing this option works.

lenon avatar Feb 24 '23 14:02 lenon

I had just the same case. This is messed up and very not intuitive. You cannot set a default to sth which is empty...

Below spec fails: expected #<Grape::Entity::Exposure::NestingExposure::OutputBuilder({:a=>nil, :b=>nil, :c=>nil})> to match {:a=>"", :b=>[], :c=>{}}

  it 'sets default to empty structure when key is missing' do
    class Dummy < Grape::Entity
      expose :a, default: "sth"
      expose :b, default: []
      expose :c, default: {}
    end
    expect(Dummy.represent({}).as_json).to match(
      {
        a: "sth",
        b: [],
        c: {}
      }
    )
  end

Below spec passes

  it 'sets default  when key is missing' do
    class Dummy < Grape::Entity
      expose :a, default: "sth"
      expose :b, default: [:element]
      expose :c, default: { key: 'value' }
    end
    expect(Dummy.represent({}).as_json).to match(
      {
        a: "sth",
        b: [:element],
        c: { key: 'value' }
      }
    )
  end
end

I have not analysed this gem code as that would take some time which I want to spend elsewhere.

In local project I have created a method:

      def expose_with_default(key:, default:, as: nil, **options)
          as ||= key.to_sym
          expose key, as: as, **options do |object, _|
            if object[key].nil?
              default
            else
               object[key]
            end
          end
        end

to handle default properly and do not have to do such modifications in every single case.

Regardless of that we are unfortunately moving away from grape-entity because quirks like this and its performance not sufficient for our needs.

lukasz-wojcik avatar Apr 28 '23 12:04 lukasz-wojcik