GraphQL-RubyのEnum classes are never instantiated and their methods are never called.とは何か
GraphQL-RubyのEnumを翻訳するにあたり、Enum classes are never instantiated and their methods are never called.
に疑問を持ったのでソースコードを読むことにしました。
version
graphq-ruby: 1.9.5 ruby: 2.6.5
実装は、GraphQL::Schema::Enumにあります。GraphQL-Rubyは、既に巨大なOSSであるためテストから概要を確認しました。
enumという変数は、spec/supportに定義されておりGraphQL::Schema::Enum
を継承しています。
テスト項目から、GraphQL::Schema::Enum
を継承したクラス自体にdescriptionを定義できることやブロックでvalueを追加することがわかりました。
describe GraphQL::Schema::Enum do let(:enum) { Jazz::Family } ~~~~ describe "type info" do it "tells about the definition" do assert_equal "Family", enum.graphql_name assert_equal 29, enum.description.length assert_equal 7, enum.values.size end it "inherits values and description" do new_enum = Class.new(enum) do value :Nonsense value :PERCUSSION, "new description" end # Description was inherited assert_equal 29, new_enum.description.length # values were inherited without modifying the parent assert_equal 7, enum.values.size assert_equal 8, new_enum.values.size perc_value = new_enum.values["PERCUSSION"] assert_equal "new description", perc_value.description end it "accepts a block" do assert_equal "Neither here nor there, really", enum.values["KEYS"].description end ~~~~ end ~~~~ end
つまり、valueは以下のように複数の定義方法があるということです。
class Types::BaseEnum < GraphQL::Schema::Enum end class Types::MediaCategory < Types::BaseEnum description "Groups of media category" value "AUDIO", "An audio file, such as music or spoken word" value :TEXT, "Written words" value "IMAGE" do "A still image, such as a photo or graphic" end value "VIDEO", value: :video end
利用方法がわかったので、実際のソースコードを追っていきます。
GraphQL::Schema::Enum
には、クラスメソッドしか存在していません。
では、各valueの値はどこで定義されているのでしょうか?コードを見れば、明らかですがvalueメソッド
が正解です。
def value(*args, **kwargs, &block) kwargs[:owner] = self value = enum_value_class.new(*args, **kwargs, &block) own_values[value.graphql_name] = value nil end
このメソッドで重要な行は、value = enum_value_class.new(*args, **kwargs, &block)
です。
enum_value_class
は、superclass <= GraphQL::Schema::Enum
がfalseになるまで再帰的に繰り返します。
ここで、GraphQL::Schema::Enumが定義された時にenum_value_class(GraphQL::Schema::EnumValue)
を実行しているためGraphQL::Schema::Enum
に対してenum_value_class
を実行した結果はGraphQL::Schema::EnumValue
です。
よって、value = enum_value_class.new(*args, **kwargs, &block)
はGraphQL::Schema::EnumValue
のinitializeメソッドを呼び出します。
def enum_value_class(new_enum_value_class = nil) if new_enum_value_class @enum_value_class = new_enum_value_class end @enum_value_class || (superclass <= GraphQL::Schema::Enum ? superclass.enum_value_class : nil) end
GraphQL::Schema::EnumValueは、とても単純でオブジェクトの初期値をセットするだけです。
to_graphql
メソッドは、クラスベース以前の.define-style
と互換性を保つために用意されているので気にする必要はありません。
def value(*args, **kwargs, &block) kwargs[:owner] = self value = enum_value_class.new(*args, **kwargs, &block) ## ここから own_values[value.graphql_name] = value nil end
最後に、privateメソッドであるown_values
を呼び出しGraphQL::Schema::EnumValue
のオブジェクトを@own_values
に代入します。
private def own_values @own_values ||= {} end
以上からvalue
メソッドがGraphQL::Schema::EnumValue
のオブジェクトを@own_valuesに代入していることがわかりました。
さて、テスト項目に振り返るとvalues
メソッドを呼び出していることがわかります。
it "tells about the definition" do assert_equal "Family", enum.graphql_name assert_equal 29, enum.description.length assert_equal 7, enum.values.size end
values
メソッドは、先ほど説明した@own_values
を返しているだけということがわかります。
def values inherited_values = superclass <= GraphQL::Schema::Enum ? superclass.values : {} # Local values take precedence over inherited ones inherited_values.merge(own_values) end
これで、以下のコードが内部で何をしているかわかりました。
class Types::BaseEnum < GraphQL::Schema::Enum end class Types::MediaCategory < Types::BaseEnum description "Groups of media category" value "AUDIO", "An audio file, such as music or spoken word" value :TEXT, "Written words" value "IMAGE" do "A still image, such as a photo or graphic" end value "VIDEO", value: :video end
最後に
ソースコード読解を通して以下について大まかな把握ができたので、まとめます。
GraphQL-RubyのEnum classes are never instantiated and their methods are never called.とは何か
最初に説明した通り、GraphQL::Schema::Enum
はインスタンスメソッドを持ちません。また、クラスを定義した時にenum_value_class(GraphQL::Schema::EnumValue)
を呼び出すことでDSL風にEnumValueの設定できるようにしています。これらは、全てクラスメソッドで完結しているため仮にインスタンスメソッドを自前に実装したとしても呼び出されることがないことを示唆しています。
憶測ではありますが、Enumは他のオブジェクトから影響を受けるべきではありません。よって他のオブジェクトによって汚染されやすいインスタンスメソッドでは、クラスメソッドのみで完結するようにしているのではないかと思いました。
初めてのGraphQL ―Webサービスを作って学ぶ新世代API
- 作者:Eve Porcello,Alex Banks
- 出版社/メーカー: オライリージャパン
- 発売日: 2019/11/13
- メディア: 単行本(ソフトカバー)
- 作者:Paolo Perrotta
- 出版社/メーカー: オライリージャパン
- 発売日: 2015/10/10
- メディア: 大型本