Note

3年後の自分のために書いています

graphql-ruby の lib/graphql/schema.rb のコードに興奮した

GraphQL はほとんど関係なく Ruby の話です。

GraphQL Ruby を実行する時はだいたいこんな感じで使う。

# app/graphql/my_schema.rb
class MySchema < GraphQL::Schema
  # ...
end

# app/controllers/graphql_controller.rb
result = MySchema.execute(
  params[:query],
  variables: params[:variables],
  context: { current_user: current_user },
)
render json: result

だが、MySchema の親クラスである GraphQL::Schema (lib/graphql/schema.rb) のコード を読んでもインスタンスメソッドの GraphQL::Schema#execute しか定義されていない。

呼び出し側でどうして MySchema.execute(args) のように特異メソッド的に呼べるのだろうと不思議だった。

以下が答えでした。

require 'forwardable'

class MyClass
  alias :my_definition :itself
  
  def execute(arg)
    arg
  end
  
  class << self
    extend Forwardable

    def_delegator :my_definition, :execute

    def my_definition
      self.new
    end
  end
end

> MyClass.execute('hoge')
#=> "hoge"

興奮した。

参考

instance method Object#itself (Ruby 2.6.0)

恒等写像 - Wikipedia

クラス/メソッドの定義/alias (Ruby 2.6.0)

module Forwardable (Ruby 2.6.0)

追記

alias :my_definition :itself はこの例には余計だった。

lib/graphql/schema.rb で定義している場所と理由。

こっちはもう少し調べないとわからなさそうです。

ていうかこの例に関していうと委譲がポイントでしたね。

require 'forwardable'

class MyClass
  def execute(arg)
    arg
  end
  
  class << self
    extend Forwardable

    def_delegator :my_definition, :execute, :my_execute

    def my_definition
      self.new
    end
  end
end

> MyClass.my_execute('hoge')
#=> "hoge"
> MyClass.instance_methods(false)
#=> [:execute]
> MyClass.singleton_methods(false)
#=> [:my_execute, :my_definition]

ちょっとスッキリしましたが、委譲難しい。

追記の参考

instance method Forwardable#def_delegator (Ruby 2.6.0)

instance method Module#instance_methods (Ruby 2.6.0)

instance method Object#singleton_methods (Ruby 2.6.0)

さらに追記

require 'forwardable'

class MyClass
  alias :my_definition :itself
  
  def execute(arg)
    arg
  end
  
  class << self
    extend Forwardable

    def_delegator :my_definition, :execute, :my_execute

    def my_definition
      self.new
    end
  end
end

> MyClass.my_execute('hoge')
#=> "hoge"
> MyClass.instance_methods(false)
#=> [:execute, :my_definition]
> MyClass.singleton_methods(false)
#=> [:my_execute, :my_definition]

alias :my_definition :itself を追加することで、 MyClass#my_definition が定義されるのか。

けっこうスッキリしたので、今日はここまでにしておきます。