Feedforce Developer Blog

フィードフォース開発者ブログ

Ruby 3.0 の「キーワード引数の分離」で委譲用のメソッドが壊れた場合の対応方法

こんにちは、id:daido1976 です。

Ruby 3.0 の「キーワード引数の分離」が原因で委譲用のメソッドが壊れた場合、特に Ruby 2.6 ~ 3.0 で互換性を保ちながら対応する場合の日本語記事が見当たらなかったので、書かせていただきます。

インスタンスメソッドが壊れた場合

以下のコードは Ruby 3.0 より前のバージョンでは動きますが、3.0 からは MyClass#other_method の実行時に ArgumentError が起きるようになります。(2.7 の時も警告が出ます)

class MyClass
  def delegating_method(*args)
    other_method(*args)
  end

  def other_method(value:)
    p value
  end
end

MyClass.new.delegating_method(value: 'value')

こちらは公式の Separation of positional and keyword arguments in Ruby 3.0#a-compatible-delegationruby2_keywords gemModule#ruby2_keywords を参考に以下のようなコードで対応できます。

class MyClass
  def delegating_method(*args)
    other_method(*args)
  end

  def other_method(value:)
    p value
  end

  # この 1 行を追加する
  ruby2_keywords :delegating_method if respond_to?(:ruby2_keywords, true)
end

MyClass.new.delegating_method(value: 'value')

ポイントは Ruby 2.7 や 3.0 でも旧来のスタイルで実行できるようにするための Module#ruby2_keywords が Ruby 2.6 以前では定義されてないので、Object#respond_to? などでそのケアをしてあげる必要があることです。

クラスメソッドが壊れた場合

こちらがこの記事を書いた目的です。

サンプルコードに現実味を持たせるためにインスタンスメソッドの例とは少し変えています。クラスメソッドで引数を受けて、そのまま委譲して初期化するようなコードです。

このコードも Ruby 3.0 からは ArgumentError が発生します。

class MyClass
  def self.delegating_method(*args)
    new(*args)
  end

  def initialize(value:)
    p value
  end
end

MyClass.delegating_method(value: 'value')

こちらはインスタンスメソッドの場合と違い、以下のように class << self で特異クラスをオープンしてから、Module#ruby2_keywords の対応をしてあげる必要があります。

class MyClass
  def self.delegating_method(*args)
    new(*args)
  end

  def initialize(value:)
    p value
  end

  # この 3 行を追加する
  class << self
    ruby2_keywords :delegating_method if respond_to?(:ruby2_keywords, true)
  end
end

MyClass.delegating_method(value: 'value')

定義されたクラスメソッドの居場所が分からなくて、久しぶりに『メタプログラミングRuby』の 5 章を開きました。

Also see. https://docs.ruby-lang.org/ja/latest/doc/spec=2fdef.html#singleton_class

【おまけ】Ruby 2.6 ~ 3.0 で互換性を保たなくて良い場合

公式の Separation of positional and keyword arguments in Ruby 3.0#delegation-ruby-3 の通りにやれば OK です。