@kyanny's blog

My thoughts, my life. Views/opinions are my own.

Grape の present に polymorphic な感じのオブジェクト(の Array)を渡すときの Entity の書き方

https://github.com/kyanny/playground/tree/gh-pages/grape-entity-polymorphic

Grape の present メソッドは :with オプションで引数オブジェクトに適用するエンティティクラスを指定できるが、 :with オプション無しの場合は grape 自身がオブジェクトからエンティティクラスを類推する。

present の引数オブジェクトが Array の場合、 grape は Array の先頭のオブジェクトをもとにエンティティクラスを類推し、そのエンティティクラスが Array の全ての要素に適用される (#present, #entity_class_for_obj) なので、 polymorphism 気取りで「だいたい同じだけど呼び出しに反応する属性・メソッドが異なるオブジェクト」を一緒くたにして present に渡すと、 Array 内での並び順によって例外が発生することがある。

https://github.com/kyanny/playground/tree/gh-pages/grape-entity-polymorphic#ssd-class-entity-without-if-guard

{"error":"HDD::Entity missing attribute `rpm' on #\u003cSSD:0x007fe4653a0d80\u003e"}

このように、片方がもう片方のサブセットみたいなオブジェクト群をまとめて present したい場合は、属性・メソッドが足りないオブジェクトのほうのエンティティクラスで以下のように :if オプションを使い、反応できない属性・メソッド呼び出しを回避するとうまくいく。

# Grape::Entity::DSL を使う場合
entity :rpm, if: ->(obj, opt) { obj.respond_to?(:rpm) }

# Grape::Entity を継承する場合
expose :rpm, if: -> (obj, opt) { obj.respond_to?(:rpm) }

[{"capacity_gb":500,"rpm":5400},{"capacity_gb":2000,"rpm":7200},{"capacity_gb":256,"rpm":null},{"capacity_gb":512,"rpm":null}]