Help us understand the problem. What is going on with this article?

attr_accessorで挫折した君へ

attr_accessorで挫折した君へ

by okaeri_ryoma
1 / 2

はじめに

僕は恥ずかしながらattr_accessorをRuby歴7ヶ月にして理解しました。学びたての頃は、『classの下についてる変なやつ』としか思っておらず、何のために置かれてるのかわからないまま、とりあえず置いとけ、エイっ!!という感じで使っていました(ひどい)

ということで、attr_accessorで挫折した僕が、attr_accessorで挫折した人にattr_accessorを挫折しないように分かりやすく説明したいと思います。

ゴール

本記事では、以下のコードを完全に理解することをゴールとして説明します。
すでに理解できてるよという方はそっとブラウザバックしてください。

user.rb
class User
  attr_accessor :name, :age

  def initialize(name, age)
    @name = name
    @age = age
  end
end

大前提

はじめに、大前提を説明して目線を合わせます。

『基本的に、オブジェクトの値をクラス外から参照・書き込みすることはできない』

どういうこと?と思った方は試しに以下のコードを実行してみてください。

user.rb
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でやったときは出来てたのに何で今回は出来ないの??』

と思ったかもしれません。分かります。僕も全く同じように考えていました。
その答えは本記事の中でじっくり説明しますので、もう少し読み進めて行きましょう。

力技で解決してみる

何も設定しなけば、オブジェクトの値の読み書きができないことが分かりました。
それなら、力技で解決するためにこんなコードを書いてみました。

user.rb
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を参照しているため、正常に値を取り出すことができます。書き込みについても同様です。

それでは、以下を実行してみましょう。

user.rb
class User
  省略
end
user = User.new("山田",18)

p user.name
p user.name = "佐藤"

結果

"山田"
"佐藤"

今度はうまく行きました。
これで解決ですね、めでたしめでたし。

。。。、となってしまったら大変です。なぜなら、このやり方だと属性(name、age)が増えるたびに読み書き用のメソッドを作らなければいけません。

今回でいうならこんな感じ。

user.rb
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

つまり、先ほどの長ったらしいコードは以下のようにシンプルになります。

user.rb
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

これを使うと、かなりスッキリ。

user.rb
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

になります。最終形態は以下の通り。

user.rb
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という不気味なメソッドへの理解が少しでも深まれば幸いです。
最後までお読みいただきありがとうございました!

okaeri_ryoma
HRテック企業でRailsのエンジニアをしています。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした