0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【初心者必読】Ruby の誤解あるあるAdvent Calendar 2020

Day 9

Ruby の attr_accessor はアクセッサーじゃない

Posted at

Ruby の初心者向け記事の定番間違いの一つに,attr_accessor のことを「アクセッサー」と呼ぶ,というものがあります。
単に言葉の表現が間違っているだけの場合と,そもそも正しく理解していない場合とがあるように思われます。

Ruby の場合,アクセッサーというのは,

  • インスタンス変数の値を読み出すためのメソッド(ゲッターと呼ぶ)
  • インスタンス変数に値をセットするメソッド(セッターと呼ぶ)

の総称です。
以下の典型的な例を見ましょう

class Person
  def name
    @name
  end

  def name=(name)
    @name = name
  end
end

a_person = Person.new
a_person.name = "Kaoru"
puts a_person.name # => "Kaoru"

二つのメソッドを定義していますが,name がゲッターで,name= がセッターです。この二つを合わせてアクセッサーと呼ぶわけです。
慣習として,

  • ゲッターのメソッド名はインスタンス変数名から @ を除いたもの
  • セッターのメソッド名はインスタンス変数名から @ を除いたものの末尾に = を付加したもの

とします(ルールではなく慣習です)。

名前の末尾が = であるようなメソッドは「代入メソッド」と呼ばれ,Ruby の構文上,ちょっぴり特別扱いされます。
上の例で

a_person.name = "Kaoru"

と書いたように,メソッド呼び出しの際に = の前にスペースを入れても構わないのです。
上のコードは代入式の一種です1

それだけではありません。代入メソッドを使った自己代入式も書けます。
以下のコードを見ましょう(クラス定義は先ほどのとおりだとします)。

a_person = Person.new

# (1)
a_person.name ||= "Kaoru"
puts a_person.name # => "Kaoru"

# (2)
a_person.name ||= "Haruka"
puts a_person.name # => "Kaoru"

(1) では,インスタンス変数 @name にまだ値がセットされていない(読み出すと nil になる)状態で自己代入演算子 ||= を使っているので,@name の値が "Kaoru" になります。

しかし,(2) では,@name に文字列オブジェクトが代入された状態で実行されるので代入は起こりません。そのため,a_person.name の返り値は "Haruka" ではなく "Kaoru" になっています。

前ふりが長くなりました。ここから本題に入りましょう。
先ほどのクラス定義を見ると,ゲッター,セッターの定義で(単純とはいえ)それぞれ 3 行も費やしています。
インスタンス変数が 5 個あって,それぞれゲッターもセッターも定義するとなると,30 行も必要になります。これは馬鹿馬鹿しいですね。

そこで,このような単純なゲッター,セッターを定義するなら2

class Person
  attr_accessor :name
end

と書けば済むようになっています。

この attr_accessor のことを「アクセッサー」と書いている記事をよく目にします。
しかし,attr_accessor はアクセッサーを定義してくれるものであって,これ自体がアクセッサーなのではありません。
おそらく名前の一部に「accessor」と付いているのに引きずられて「アクセッサー」と思い込んでしまうのでしょう。

ところで,attr_accessor というのは一体何者でしょうか?
他言語から来た方は,Ruby のクラス定義構文の一部分と思うかもしれませんが,そんな文法はありません。
attr_accessor はただのメソッドです。
参考:Module#attr_accessor (Ruby 3.0.0 リファレンスマニュアル)

つまり,

attr_accessor :name

は,:name というシンボルを引数として,attr_accessor というメソッドを呼び出しているだけなのです。
こうすることで,クラスにアクセッサーが定義されます。
attr_accessorメソッドを定義するメソッドなわけです。

少し補足すると

  • クラスもまたオブジェクトである
  • クラス定義式(class Personend)の中では,そのクラスが self となる
  • つまり,クラス定義式内でレシーバーをあらわに書かないメソッド呼び出しを行うと,当該のクラスがレシーバーとなる

ということです。
最後の点を確認するコードを以下に示します。

class Hoge
  p name # => "Hoge"
end

このコードで,name はローカル変数ではありません。この箇所以前に name への代入式が存在しないので,ローカル変数ではありえないのです。
とすると,メソッド呼び出しのはずです。
このメソッド呼び出しにはレシーバーが書かれていません。
ここはクラス定義式の中ですから,name のレシーバーは Hoge クラスです。
HogeClass というクラスのインスタンスですから,name はクラス名・モジュール名を返すメソッドです。だから "Hoge" が表示されます。
参考:Module#inspect (Ruby 3.0.0 リファレンスマニュアル)

以上が理解できると,

class Person
  attr_accessor :name
end

class Person
end

Person.attr_accessor(:name)

と書くのと同じであることが分かるでしょう。

  1. Ruby の代入式は,変数への代入だけではないってことですね。

  2. 「単純な」と書いたのは理由があります。単純でないアクセッサーを定義したいこともあるからです。とくにセッターの場合,与えられた引数に何らかの正規化(全角/半角の統一とか余計なスペースの削除とか)を施してからインスタンス変数に代入したいことがあります。こういう場合,attr_accessor は使えません。

0
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?