LoginSignup
0
1

More than 1 year has passed since last update.

【Ruby】Struct の子クラスにキーワード引数をとってインスタンスを返す特異メソッド create を生やす

Last updated at Posted at 2021-12-06

何をするのか

タイトルのとおりです。

Ruby3.1 では、Structの子クラス.new(…) の引数が全てキーワード引数である場合、警告が出るようになるそうです。(ここ
それは、Ruby3.2 からは、keyword_init: true していなくても、位置引数に加えてキーワード引数が使えるようになるため、あらかじめ警告しておこうという趣旨のようです。[Feature #16806]

とても有り難いことです。

その絡みで、過去にタイトルと同じような提案([Feature #11925])があったものの、却下されていたことを知りました。

でも、この提案の内容も良さげな感じがします。実装も簡単そうなので、自力で生やしてみようと思いました。

コード

class Struct
  class << self
    alias _new new
    def new(*args, keyword_init: false, &block)
      sub = _new(*args, keyword_init: keyword_init, &block)
      if keyword_init
        sub.instance_eval { alias create new }
      else
        sub.instance_eval(<<~EOC, __FILE__, __LINE__ + 1)
          def create(#{args.map { |arg| "#{arg}: nil" }.join(", ")})
            new(#{args.join(", ")})
          end
        EOC
      end
      sub
    end
  end
end

簡単な説明

Original の Struct.new の中身を、エイリアス _new に退避させて、new を自作し、自作の Struct.new(…) で子クラスを作る際に、instance_eval を使って特異メソッド create を作っています。
例えば、

Person = Struct.new(:name, :age)

したとき、併せて

class << Person
  def create(name: nil, age: nil)
    new(name, age)
  end
end

したのと同じ効果があります。

なお、keyword_init: true のときは new 自身がキーワード引数をとるので、createnew のエイリアスにしてあります。

余談

Ruby3.1 からは、

sub = _new(*args, keyword_init: keyword_init, &block)

の部分は、

sub = _new(*args, keyword_init:, &block)

と書けるようです。
大変有り難い。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1