graphql-guardをgraphql-ruby 1.8で使うためのある方法
問題
graphql-ruby 1.8ではclass-based APIが導入されました。フィールド定義は型を表すクラスのコンテキストでクラスメソッド field を呼ぶ形で定義します。
class Types::Query < Types::BaseObject
field :viewer, Types::User, null: true
def viewer
context[:current_user]
end
end
このときgraphql-guard(今回はv1.1.0)を次のように使おうとすると #<ArgumentError: unknown keyword: guard> になります。
class Types::Query < Types::BaseObject
field :viewer, Types::User, guard ->(obj, args, ctx) { !ctx[:current_user].nil? }, null: true
def viewer
context[:current_user]
end
end
class-based APIでフィールドを表すクラスが GraphQL::Field から GraphQL::Schema::Field にかわり、従来のように guard をキーとするProcを指定するだけでは、フィールドのメタデータに guard を設定することができなくなったためエラーになっています。
ある解決策
フィールドをカスタマイズして guard をメタデータとして持たせられるようにします。やりかたはこのドキュメントを参照。
GraphQL - Extending the GraphQL-Ruby Type Definition System
class Fields::Guardable < GraphQL::Schema::Field
def initialize(*args, guard: nil, **kwargs, &block)
@guard = guard
super *args, **kwargs, &block
end
def to_graphql
field_defn = super
field_defn.tap { |d| d.metadata[:guard] = @guard }
end
end
initialize はこのフィールドを定義するときに guard を設定できるようにしています。また、スキーマ定義が処理される過程で to_graphql が実行されるので、このときにフィールド定義を表す GraphQL::Schema のインスタンスが持つ属性 metadata に guard を設定しています。
これで、class-based APIでもフィールド定義時に guard Procが設定できます。guard を定義できるフィールドにするために field_class を明示的に指定します。
class Types::Query < Types::BaseObject
field_class Fields::Guardable
field :viewer, Types::User, guard ->(obj, args, ctx) { !ctx[:current_user].nil? }, null: true
def viewer
context[:current_user]
end
end
accepts_definitionを使う[2018-06-10 追記]
accepts_definition を利用すると1.8以前で使っていたオプションをclass-based APIで使うときにgraphql-ruby側でいい感じにハンドリングしてくれます。
GraphQL - Extending the GraphQL-Ruby Type Definition System
なので、Fields::Guardable に対して次のようにすればOKでした。
class Fields::Guardable < GraphQL::Schema::Field
accepts_definition :guard
end
これを使えば従来どおり guard が使えます。
class Types::Query < Types::BaseObject
field_class Fields::Guardable
field :viewer, Types::User, null: true do
guard ->(obj, args, ctx) { !ctx[:current_user].nil? }
end
def viewer
context[:current_user]
end
end
移行時に補助的に使うメソッドのようにも見えるので、今後使い続けていいのかがちょっと微妙なところです。
雑感
- Policyオブジェクトを使うときにどうやるか
Fields::Guardableのようなモジュールをgem側で提供できるようにしたほうがよさそうかも