こんにちは。
最近 メタプログラミングRuby を読んだので、印象に残ったところをブログにまとめています。
今回は、Rubyのアクセスメソッド attr_writer, attr_accessor でインスタンス変数をセットするときにレシーバ(self.)を省略できないことについてまとめました。
サンプルコード
かなりシンプルなこちらのコードを実行すると何が出力されるでしょうか?
class Sample attr_accessor :name def set_name name = 'デフォルト太郎' end end sample = Sample.new sample.set_name puts sample.name #=> ?
ほぼ同じ内容をTwitterのアンケート機能で聞いてみた結果がこちらです。
```ruby
— ゆうすけ@Railsエンジニア (@ysk_pro) 2020年6月27日
class A
attr_accessor :name
def set_name
name = 'aaa'
end
end
a = https://t.co/w0W8pYqeGP
a.set_name
puts https://t.co/91gFYoZrlz #=> ?
```
attr_accessor は Sample クラス内に以下のコードを自動で定義してくれるので、name= メソッドによってインスタンス変数がセットされて「デフォルト太郎」と出力されそうな気がしますが、実際は何も出力されません。
def name @name end def name= @name = name end
この予想に反した結果となっている原因は、set_name メソッドにあります。
「name = 'デフォルト太郎'」が、ローカル変数の name に値を代入しているのか、attr_accessor で生成された「name =」メソッドをレシーバを省略して呼び出しているかが Ruby に判断できず、Ruby では前者(ローカル変数への代入)を採用することになっています。
よって、set_name メソッドはローカル変数の name に値を代入しているだけとなり、sample.name は 定義されていない インスタンス変数 @name を参照しているため、何も出力されないという挙動になっていました。
set_name メソッドを以下のようにすれば「デフォルト太郎」と出力されるようになります。
def set_name self.name = 'デフォルト太郎' end
先ほどは省略していたレシーバ(self.)を明記することで、「name =」メソッドを呼び出していることが明確になったためです。
また、アクセスメソッドを使用せず、インスタンス変数に直接値を入れる以下の形でも「デフォルト太郎」が出力される挙動にすることができます。
def set_name @name = 'デフォルト太郎' end
おわりに
ここまでお読みいただきありがとうございます。
意図しない挙動を避けるためのコードを2種類紹介しましたが、実務のコードでは self. をつける形もインスタンス変数に直接値を入れる形も見かけるので、どちらを選ぶかは好みな気がします。
メタプログラミングRuby では、今回取り上げたような Ruby の小ネタ的な話も色々と紹介されており面白いのでおすすめです。
- 作者:Paolo Perrotta
- 発売日: 2015/10/10
- メディア: 大型本
(追記)
メタプログラミングRubyを読んで印象に残ったところまとめの第2弾として「自己yield」についてまとめました。
是非合わせてご覧ください。
ysk-pro.hatenablog.com