はじめに
僕は恥ずかしながらattr_accessor
をRuby歴7ヶ月にして理解しました。学びたての頃は、『classの下についてる変なやつ』としか思っておらず、何のために置かれてるのかわからないまま、とりあえず置いとけ、エイっ!!という感じで使っていました(ひどい)
ということで、attr_accessorで挫折した僕が、attr_accessorで挫折した人にattr_accessorを挫折しないように分かりやすく説明したいと思います。
ゴール
本記事では、以下のコードを完全に理解することをゴールとして説明します。
すでに理解できてるよという方はそっとブラウザバックしてください。
class User
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
end
大前提
はじめに、大前提を説明して目線を合わせます。
『基本的に、オブジェクトの値をクラス外から参照・書き込みすることはできない』
どういうこと?と思った方は試しに以下のコードを実行してみてください。
class User
def initialize(name, age)
@name = name
@age = age
end
end
user = User.new("山田",18)
user.name
user.age
おそらく、
undefined method `name' for #<User:0x00007faf4c182dd0 @name="山田", @age=18> (NoMethodError)
と怒られたはずです。
ちなみに、userの名前を書き換えるとどうなるでしょうか?
user.name = "佐藤"
=> undefined method `name=' for #<User:0x00007fee848daac0 @name="山田", @age=18> (NoMethodError)
やはり怒られました...
そうなんです。何も設定をしなければ、いつも当たり前にやっていた参照、書き換えは実行できないんです。
『え??いつもRailsでやったときは出来てたのに何で今回は出来ないの??』
と思ったかもしれません。分かります。僕も全く同じように考えていました。
その答えは本記事の中でじっくり説明しますので、もう少し読み進めて行きましょう。
力技で解決してみる
何も設定しなけば、オブジェクトの値の読み書きができないことが分かりました。
それなら、力技で解決するためにこんなコードを書いてみました。
class User
def initialize(name, age)
@name = name
@age = age
end
def name
@name
end
def name=(name)
@name = name
end
end
1つ目が、nameの値を読むためのメソッド、
2つ目が、nameの値を書き込むためのメソッドです。
※ name=(name)という変な書き方にアレルギー反応が出た人がいるかもしれませんが、「値を更新するときはこう書くんだ」ぐらいで覚えてもらえれば大丈夫です。
これを書けば、user.name
としたとき、nameメソッドが呼び出され、その中で@name
を参照しているため、正常に値を取り出すことができます。書き込みについても同様です。
それでは、以下を実行してみましょう。
class User
省略
end
user = User.new("山田",18)
p user.name
p user.name = "佐藤"
結果
"山田"
"佐藤"
今度はうまく行きました。
これで解決ですね、めでたしめでたし。
。。。、となってしまったら大変です。なぜなら、このやり方だと属性(name、age)が増えるたびに読み書き用のメソッドを作らなければいけません。
今回でいうならこんな感じ。
class User
def initialize(name, age)
@name = name
@age = age
end
def name
@name
end
def name=(name)
@name = name
end
def age
@age
end
def age=(age)
@age = age
end
end
これは流石に面倒の極みです。
ということで、Rubyはこれを解決するためにアクセスメソッドというものを準備してくれています。ありがたや。
すごく便利なアクセスメソッド
では、先ほどの面倒なコードをアクセスメソッドを使ってシンプルにしていきましょう。
attr_readerメソッド
attr_readerメソッドは以下の2つのメソッドの代わりになります。
# attr_reader :name, :age は以下の2つと同じ意味
def name
@name
end
def age
@age
end
つまり、先ほどの長ったらしいコードは以下のようにシンプルになります。
class User
attr_reader :name, :age
def initialize(name, age)
@name = name
@age = age
end
def name=(name)
@name = name
end
def age=(age)
@age = age
end
end
メソッドを2つ省略できたので、かなりシンプルになりました。もう少しスッキリさせましょう。
attr_writerメソッド
情報を書き込みするメソッドを省略するために、attr_writerメソッドがあります。
これを使うと以下のメソッドを省くことができます。
# attr_writer :name, :age は以下の2つと同じ意味
def name=(name)
@name = name
end
def age=(age)
@age = age
end
これを使うと、かなりスッキリ。
class User
attr_reader :name, :age
attr_writer :name, :age
def initialize(name, age)
@name = name
@age = age
end
end
最初のコードと比べたら、別人のようですね!
でも、まだ甘いです。実はもっとシンプルにできます。
attr_accessorメソッド
もう十分でしょ、と思うかもしれませんがRubyにはattr_readerメソッドとattr_writerメソッドを兼ね備えた万能のメソッドが存在します。
それが、__attr_accessorメソッド__です!
これを使えば、
attr_reader :name, :age
attr_writer :name, :age
が、
attr_accessor :name, :age
になります。最終形態は以下の通り。
class User
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
end
最終的にたったの8行におさまりました。アクセスメソッドの威力はハンパないですね!
つまり、これまで何となくクラスの頭に置いていたattr_accessorメソッドは、オブジェクトの値の読み書きを可能にしてくれる万能なメソッドだったというわけです。
じゃあ何でRailsでは何もしてないのにインスタンス変数の読み書きができるのさ
普段、Railsで開発しているとき、特に何も意識しなくても、以下のようにオブジェクトの値を読み書きできますよね?
user.name
=> "佐藤"
user.name = "山田"
user.name
=> "山田"
これはなぜでしょうか?
答え
Railsにおいて、テーブルに紐づく全てのModelはActiveRecordを継承しているから
解説
rails generate
で作成されたModelは、自動的にActiveRecordというクラスを継承するようにRailsが設定しています。
class User < ApplicationRecord
end
superclassで先祖を辿っていくと、
irb> User.superclass
=> ApplicationRecord
irb > ApplicationRecord.superclass
=> ActiveRecord::Base
確かにActiveRecordというクラスがありました。
このActiveRecordにおいて、usersテーブルの各カラムをアクセスメソッド(attr_accessor)として登録しているため、
僕たちが意識しなくても、オブジェクトの値の読み書きが行えているわけですね。
ActiveRecord様には足を向けて寝れません。笑
まとめ
以上を簡単にまとめるとこうなります。
- 基本的にクラス外からオブジェクトの値を読み書きすることができない
- 1つの属性ごとに読み書き専用のメソッドを作ればいつも通り読み書きできるようになる
- しかし、毎回メソッドを書くのはめんどくさい
- そこで、アクセスメソッドの出番!
- attr_readerメソッドを使うと、「読み」用のメソッドを省略できる
- attr_writerメソッドを使うと「書き込み」用のメソッドを省略できる
- attr_accessorメソッドを使えば「読み」「書き」どちらも省略できる
- Railsでオブジェクトの値を自由に読み書きできるのはActiveRecordを継承しているから
この記事でattr_accessorという不気味なメソッドへの理解が少しでも深まれば幸いです。
最後までお読みいただきありがとうございました!