Skip to main content

超絶怒涛のゆるふわコードリーディング GraphQL gem篇

GraphQL gemをふんわり読んだ

ふんわり読んで見ました、とりあえずよく使われてそうなDSLをおったメモ
versionはv1.9.12 が最新リリースである時期のmasterブランチ読みました。
ゆるふわの極みです

とりあえず最初に公式のGetting Startedで出てくるやつ

 module Types
  class PostType < Types::BaseObject
    field :id, ID, null: false
    field :title, String, null: false
    field :truncated_preview, String, null: false
    field :comments, [Types::CommentType], null: true,
      description: "This post's comments, or null if this post has comments disabled."
  end
end
 

field is 何?

GraphQL::Schema::ObjectがextendしてるGraphQL::Schema::Member::HasFieldsのメソッド。

 def field(*args, **kwargs, &block)
  field_defn = field_class.from_options(*args, owner: self, **kwargs, &block)
  add_field(field_defn)
  nil
end
 

field_class is 何?

 def field_class(new_field_class = nil)
  if new_field_class
    @field_class = new_field_class
  elsif @field_class
    @field_class
  elsif self.is_a?(Class)
    superclass.respond_to?(:field_class) ? superclass.field_class : GraphQL::Schema::Field
  else
    ancestor = ancestors[1..-1].find { |a| a.respond_to?(:field_class) && a.field_class }
    ancestor ? ancestor.field_class : GraphQL::Schema::Field
  end
end
 

引数はわたしてないので一個目の分岐は飛ばして、@field_classも(多分素のままだと)設定されてないのでGraphQL::Schema::Fieldか何がしかのクラスが返されているっぽい GraphQL::Schema::Field.from_optionsはオプションを色々整えた後newしてインスタンス返してくれるメソッド。

 def self.from_options(name = nil, type = nil, desc = nil, resolver: nil, mutation: nil, subscription: nil,**kwargs, &block)
 

引数の数すごい

最終的に下のコードはGraphQL::Schema::Fieldかサブクラスかなにかのインスタンス作ってると言えそう。

   field_defn = field_class.from_options(*args, owner: self, **kwargs, &block)
 

add_field is 何

つぎはこの行

   add_field(field_defn)
 
 def add_field(field_defn)
  if CONFLICT_FIELD_NAMES.include?(field_defn.original_name) && field_defn.original_name == field_defn.resolver_method
    warn "#{self.graphql_name}'s `field :#{field_defn.original_name}` conflicts with a built-in method, use `resolver_method:` to pick a different resolver method for this field (for example, `resolver_method: :resolve_#{field_defn.original_name}` and `def resolve_#{field_defn.original_name}`)"
  end
  own_fields[field_defn.name] = field_defn
  nil
end
 

:context, :object, :method, :class 書くと警告されるっぽい。 中身はハッシュで名前をキーにしてfield_class.from_optionsでつくったインスタンスをそのまま突っ込んでる。

んでこのハッシュは下記の2つのメソッドで使ってるっぽい

 def fields
  # Local overrides take precedence over inherited fields
  all_fields = {}
  ancestors.reverse_each do |ancestor|
    if ancestor.respond_to?(:own_fields)
      all_fields.merge!(ancestor.own_fields)
    end
  end
  all_fields
end

def get_field(field_name)
  if (f = own_fields[field_name])
    f
  else
    for ancestor in ancestors
      if ancestor.respond_to?(:own_fields) && f = ancestor.own_fields[field_name]
        return f
      end
    end
    nil
  end
end
 

多分実行してるときに使ってるコード

public_sendしてますね。引数も渡せそう。
オブジェクトがもともとメソッドもってればなんでもフィールドにできる感じなのかな?

   def resolve_field_method(obj, ruby_kwargs, ctx)
    if obj.object.is_a?(Hash)
      inner_object = obj.object
      if inner_object.key?(@method_sym)
        inner_object[@method_sym]
      else
        inner_object[@method_str]
      end
    elsif obj.object.respond_to?(@method_sym)
      if ruby_kwargs.any?
        obj.object.public_send(@method_sym, **ruby_kwargs)
      else
        obj.object.public_send(@method_sym)
      end
    else
      raise <<-ERR
    Failed to implement #{@owner.graphql_name}.#{@name}, tried:

    - `#{obj.class}##{@resolver_method}`, which did not exist
    - `#{obj.object.class}##{@method_sym}`, which did not exist
    - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash

    To implement this field, define one of the methods above (and check for typos)
    ERR
    end
  end
 

前半の分岐がよくわからんけど @method_symはこんな感じでinitializeされるときに入れてる

 method_name = method || hash_key || @underscored_name
@method_sym = method_name.to_sym
 

まとめ

ちょっと読んだ後公式のドキュメントみて使い方知らないとわけわからんことに気づいたのでここで終わりです。
OSSに貢献してる人これ無償で読んで理解して書いてるってすごすぎませんか??????