結論
class NilIfEmpty < GraphQL::Schema::FieldExtension
def after_resolve(value:, **_rest)
value.presence
end
end
class Types::BaseField < GraphQL::Schema::Field
def initialize(*args, **kwargs, &block)
super
# NOTE: unwrap を使うと、String! のような null: false のフィールドでも if の中を通せる
if type.unwrap == GraphQL::Types::String
extension(NilIfEmpty)
end
end
end
背景など
Rails × GraphQL Ruby で GraphQL サーバーを実装していて、非必須な String 型のデータのためにDBレベルでカラムに空文字のデフォルト設定がついている場合、GraphQL のレイヤーで null: true
としていても、値がない場合でも空文字が返ってしまいます。
クライアント側としては、型のある言語を採用している場合などは特に、非必須な String フィールドの値は「null でも空文字でもない値 or null」の形になっていると扱いやすいため、値がない場合には必ず null が返るようにしたいと考えました。
これを実現するため、DBレベルでデフォルト値の設定をしない、という方法があると思いますが、非必須なカラムで nil(null)
と空文字の両方を考慮する必要が出てくる、などの理由で、安易に踏み込めないのかなと思います(管理画面などの実装も同じ Rails アプリケーションで実現している場合などは特に...)
GraphQL のレベルで解決するとして、フィールドと同名のメソッドを定義し、object.xxxxx.presence
みたいな処理を書けば一応実現はできます。ただ、すべての String 型フィールドでこのメソッドを定義するのは苦しいです。
そこで、上記コードのように Field Extensions と GraphQL::Schema::FieldExtension#after_resolve を使い、GraphQL::Types::String の場合は必ず .presence
を通った値が返されるようにするのがよさそうに思いました。
補足
もし、空文字と null を区別したいフィールドが一部存在する場合は、そのフィールドだけでこの Extension を適用しないオプションを設定するのかなと思います。こちらは未検証ですが、ドキュメントを読む限りは、以下のように書くことができそうです。
# ドキュメントからそのまま引用しています↓
class Types::BaseField < GraphQL::Schema::Field
# @param custom_extension [Boolean] if false, `MyCustomExtension` won't be added
# @example skipping `MyCustomExtension`
# field :no_extension, String, custom_extension: false
def initialize(*args, custom_extension: true, **kwargs, &block)
super(*args, **kwargs, &block)
# Don't apply this extension if the field is configured with `custom_extension: false`:
if custom_extension
extension(MyCustomExtensions)
end
end
end