WEB+DB PRESS Vol.121の「特集 Ruby 3」を読んでいて、「その他の追加機能」に以下がありました。
private attr_reader :foo
のようにシンボルを書けるようになったprivate
がシンボルの配列を受け取れるようになった
1つ目はともかく2つ目がよくわからなかったので調べてみました。
Ruby 3.0で改善された点
結論から言うと、Ruby 3.0からは以下のように書けるようになりました。
class Foo
private attr_accessor :foo, :bar
# ここから下はpublic
end
- たとえばprivateなアクセサメソッドが欲しい場合なら、
private attr_accessor
にシンボルまたは文字列を渡す形で1行に書けるようになった - 引数ありのアクセサメソッドはその行にだけ効くので、その後
public
で解除しなくてもよい - それによって
private attr_accessor
をクラスの冒頭に簡潔に書けるようになった
さらにありがたい改修も含まれています。
attr_accessor
/attr_reader
/attr_writer
にシンボル(文字列でもよい)を渡すと、それぞれのアクセサメソッドに応じて適切なゲッター/セッターメソッドをシンボルの配列で返すようになった
具体的にはこうです。
# Ruby 3.0.0
class Foo
@@accessor = attr_accessor :foo, :bar
@@reader = attr_reader :baz, :bam
@@writer = attr_writer :hoge, :huga
def accessor
p @@accessor
end
def reader
p @@reader
end
def writer
p @@writer
end
end
Foo.new.accessor #=> [:foo, :foo=, :bar, :bar=]
Foo.new.reader #=> [:baz, :bam]
Foo.new.writer #=> [:hoge=, :huga=]
つまり、attr_accessor
の場合はゲッターとセッター、attr_reader
の場合はゲッターだけ、attr_writer
の場合はセッターだけを、シンボルの配列で返すようになりました。
従来は、アクセサメソッドを定義した後にアクセス制御をかけようとすると、以下のように:foo
や:foo=
を毎回自分で指定しなければならず、ついつい:foo=
のセッターを書き忘れてしまったりしました。
# Ruby 2.7.2まで
class Foo
attr_accessor :foo, :bar
private :foo, :foo=, :bar, :bar=
end
なお、上のような書き方は、アクセサメソッドをprivate
で保護しながら、クラス内のインスタンス変数にアクセサメソッド経由でアクセスする方法です。クラス内で@
を書かずに.
でインスタンス変数にアクセスできるので、この書き方を好む人もいるそうです。
参考: Rubyのインスタンス変数の直接参照について - 雑草SEの備忘録
Ruby 3.0では、それと同じことを以下のように1行で簡潔に書けるようになったわけです。地味にありがたい機能です。
class Foo
private attr_accessor :foo, :bar
end
Ruby 2.7.2まではどうだったか
クラス内のアクセス制御メソッドは、引数を与えればその引数にだけアクセス制御が効きますが、引数なしだと以後の行すべてに効きます。
以下のように、1行の中でprivate
などのアクセス制御メソッドに続けてattr_*
などのアクセサメソッドを書くとエラーになります。書けそうなのに書けなかったんですね。
class Foo
private attr_accessor :foo, :bar # TypeError (nil is not a symbol nor a string)
def debug
@aa
end
end
これを回避するには、たとえばprivate
などのアクセス制御メソッドと、attr_*
を別の行に書く必要がありました。
アクセス制御メソッドはクラスの冒頭に書きたいところですが、以下のように冒頭でprivate
を使った場合、以後の行をprivate
のままにしたくなければその後public
を呼んで、以後のアクセス制御を解除する必要がありました。
class Foo
private
attr_accessor :foo, :bar
public
def debug
@aa
end
end
public
を呼びたくない場合はprivate
とアクセス制御メソッドをクラスの末尾に書くことになりますが、そうするとattr_*
をクラスの冒頭に置けません。
class Foo
def debug
@aa
end
private
attr_accessor :foo, :bar
end
いずれにしろ1行では書けませんでした。
アクセサメソッドはnil
を返していた
Module#attr_reader
(Ruby 2.7.0 リファレンスマニュアル)Module#attr_writer
(Ruby 2.7.0 リファレンスマニュアル)Module#attr_accessor
(Ruby 2.7.0 リファレンスマニュアル)
attr_accessor
、attr_reader
、attr_writer
は従来nil
を返していました。public
やprivate
などと1行内で組み合わせる利用法は想定されていなかったのだろうと想像しました。
# Ruby 2.7.2
class Foo
@@accessor = attr_accessor :foo, :bar
@@reader = attr_reader :baz, :bam
@@writer = attr_writer :hoge, :huga
def accessor
p @@accessor
end
def reader
p @@reader
end
def writer
p @@writer
end
end
Foo.new.accessor #=> nil
Foo.new.reader #=> nil
Foo.new.writer #=> nil
なお、Rubyにはattr
という短いアクセサメソッドも一応あり、Ruby 1.9以降はattr_reader
と同等ですが、attr
はRuboCopで怒られます。
参考: 5-11【統一】attr
は原則使わない: Rubyスタイルガイドを読む: クラスとモジュール(2)クラス設計・アクセサ・ダックタイピングなど
アクセス制御メソッドはシンボルを配列で受け取れなかった
Module#private
(Ruby 2.7.0 リファレンスマニュアル)Module#public
(Ruby 2.7.0 リファレンスマニュアル)Module#protected
(Ruby 2.7.0 リファレンスマニュアル)
Ruby 2.7.2までは、private
/public
/protected
には文字列かシンボルしか渡せませんでした。以下のように配列を渡すとエラーになりました。
# Ruby 2.7.2
class Foo
def foo
"foo"
end
def bar
"bar"
end
private [:foo, :bar] # TypeError ([:foo, :bar] is not a symbol nor a string)
end
その他
#17314のコメントを見ると、シンボルの配列を受け取れるようにする改修は元々public
/protected
/private
を対象としていたのが、結果としてprivate_class_method
とpublic_class_method
とトップレベルのprivate
とprivate
でも同じことができるようになっていたことがマージ後にわかったそうです。
# #17314#17より
class Foo
def self.foo; end
def self.bar; end
private_class_method [:foo, :bar] # No error
end
# #17314#17より
def foo = nil
def bar = nil
private [:foo, :bar]
public [:foo, :bar]
注
RubyのModuleにある可視性変更用の
private
/public
/protected
メソッドを本記事では「アクセス制御メソッド」と総称します。また、RubyのModuleにある
attr_accessor
/attr_reader
/attr_writer
を本記事では「アクセサメソッド」と総称します。アクセサメソッドで定義されるメソッドについて、便宜上「ゲッター」「セッター」というJavaの用語も使っています。