GraphQL-Ruby1.9+から子fieldが選択されたかによって処理を変更できるようになった
プロダクトでGraphQL-Rubyのgemを最新バージョンにアップデートしました!
1.9ではfieldにextras:[:lookahead]
を追加できます。
この機能は、子field要素がリクエスト対象であるかで条件分岐を可能にします。
ドキュメントを見るだけでは挙動がわかりにくいので実際にリクエストした時に内部では、どのような処理を行っているかをソースコードを追いながら紹介します。
実際のソースコードは、GitHubにあります。
messageはroom_idという外部キーを持ち、roomとhas_manyの関係です。
[room_type.rb] module Types class RoomType < Types::BaseObject field :messages, [Types::MessageType], null: true, extras: [:lookahead] def messages(lookahead:) binding.pry object.messages end end end [message_type.rb] module Types class MessageType < Types::BaseObject field :id, ID, null: false field :body, String, null: false end end
リクエストするクエリは以下の通りです。
query { rooms { messages { id } } }
リクエストを送るとbinding.pryで処理が止まるので、lookahead.selects?(:id)
とlookahead.selects?(:body)
を実行すると返り値はそれぞれtureとfalseです。
lookahead.selects?(:body)
がfalseとなるのは、リクエスト対象にbodyを含んでいないからです。
それでは、GraphQL::Execution::Lookahead
のselects?メソッドを追っていきます。
Add Query#lookahead #1931を確認すると、GraphQL::Execution::Lookahead.new(query: self, root_type: root_type, ast_nodes: [ast_node])
からGraphQL::Execution::Lookahead
オブジェクトが生成されることがわかります。
def selects?(field_name, arguments: nil) selection(field_name, arguments: arguments).selected? end
GraphQL::Execution::Lookaheadのpublicメソッドであるselects?
は、filed_nameとargumentsを引数として渡すことができます。そのまま、selectionに受け取った引数を渡しているのでselectionメソッドを確認します。
def selection(field_name, selected_type: @selected_type, arguments: nil) next_field_name = normalize_name(field_name) next_field_defn = FieldHelpers.get_field(@query.schema, selected_type, next_field_name) if next_field_defn next_nodes = [] @ast_nodes.each do |ast_node| ast_node.selections.each do |selection| find_selected_nodes(selection, next_field_name, next_field_defn, arguments: arguments, matches: next_nodes) end end if next_nodes.any? Lookahead.new(query: @query, ast_nodes: next_nodes, field: next_field_defn, owner_type: selected_type) else NULL_LOOKAHEAD end else NULL_LOOKAHEAD end end
初めのnormalize_nameは、privateメソッドで実装を見るとシンボルの時にはキャメルケースのStringオブジェクトにして返します。
このことから、lookahead.selects?("roomId")
とlookahead.selects?(:room_id)
どちらで渡してもいいことがわかります。
def normalize_name(name) if name.is_a?(Symbol) Schema::Member::BuildType.camelize(name.to_s) else name end end
残りの行は、子filedにfield_nameが存在するかを判定しています。存在しない場合は、NullLookaheadオブジェクトを生成します。
NULL_LOOKAHEAD = NullLookahead.new
つまり、selectionメソッドの返り値はGraphQL::Execution::Lookahead
または、GraphQL::Execution::Lookahead::NullLookahead
のオブジェクトです。
selects?
に戻るとselectionメソッドの返り値に対してselected?
メソッドを実行しています。
GraphQL::Execution::Lookahead
のselected?
メソッドはtrue、GraphQL::Execution::Lookahead::NullLookahead
は、falseを返します。
よって、ドキュメントに記載されているように子fieldが選択されたかによって処理を変更できます。
def files(lookahead:) if lookahead.selects?(:full_path) # This is a query like `files { fullPath ... }` else # This query doesn't have `fullPath` end end
参考URL
Add a basic lookahead object #1894
Class: GraphQL::Execution::Lookahead
初めてのGraphQL ―Webサービスを作って学ぶ新世代API
- 作者:Eve Porcello,Alex Banks
- 出版社/メーカー: オライリージャパン
- 発売日: 2019/11/13
- メディア: 単行本(ソフトカバー)