すでにメンバーの場合は

無料会員登録

GitHubアカウントで登録 Pikawakaが許可なくTwitterやFacebookに投稿することはありません。

登録がまだの方はこちらから

Pikawakaにログイン

GitHubアカウントでログイン Pikawakaが許可なくTwitterやFacebookに投稿することはありません。

Ruby

【Ruby】 attr_accessorメソッドの使い方と必要な理由とは?

ぴっかちゃん
ぴっかちゃん

attr_accessorとは、インスタンス変数の読み取り専用のメソッドと書き込み専用のメソッド(セッター/ゲッター)の両方を定義することが出来るメソッドのことです。

Rubyでは、以下のコードのように記述してクラス外部からインスタンス変数にアクセスしようとすると、「インスタンス変数にアクセスするためのメソッドが定義されていない」というエラーが発生します。

これにより、クラス外部からのインスタンス変数の参照や変更が出来ません。

user.rb | インスタンス変数にアクセスするためのメソッドが未定義の場合のエラー-->
1
2
3
4
5
6
7
8
9
10
11
12
13
class User
  def initialize(name, age)
    @name = name
    @age = age
  end
end

tanaka = User.new('田中太郎', 18)
tanaka.name
# => undefined method `name' for #<User:0x00007fd0f5880658 @name="田中太郎", @age=18> (NoMethodError)

tanaka.age = 33
# => undefined method `age=' for #<User:0x00007fc289084730 @name="田中太郎", @age=18> (NoMethodError)

しかし、以下のコードのようにattr_accessorメソッドを使うと、クラス外部からインスタンス変数へのアクセスが可能になります。

user.rb | attr_accessorメソッドを使用した場合-->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class User
  # 以下の記述でクラス外部から@name,@ageにアクセスが可能になる
  attr_accessor :name, :age

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

tanaka = User.new('田中太郎', 18)
p tanaka.age #=> 18

tanaka.age = 33
p tanaka.age # => 33

このように、attr_accessorメソッドはインスタンス変数にアクセスするためのメソッドを裏側で定義してくれます。そして、attr_accessorメソッドに指定されたインスタンス変数は、クラス外部から参照と変更の両方を行う事が出来ます。

attr_accessorメソッドの使い方

attr_accessorメソッドが何をしているのか良く分からないという方は、getter/setter(ゲッター/セッター)を理解してないことが原因の1つとしてあります。

この章では、attr_accessorメソッドの使い方だけではなく、大きく関係するgetter/setter(ゲッター/セッター)も一緒に解説するので、attr_accessorメソッドの必要性まで理解する事が出来ます。

基本構文

attr_accessorメソッドは、以下のように文字列かシンボルで読み取りたいインスタンス変数名を指定します。

基本構文 -->
1
2
3
4
5
# シンボルの場合
attr_accessor :インスタンス変数名

# 文字列の場合
attr_accessor 'インスタンス変数名'

インスタンス変数名は、以下のように@なしで記述します。

@ageをattr_accessorメソッドで指定した場合-->
1
2
3
4
5
# シンボルの場合
attr_accessor :age

# 文字列の場合
attr_accessor 'age'

上記の記述によって自動で定義される読み取り・書き込み専用メソッドは、以下の通りです。(詳細は後述します。)

attr_accessor :ageの記述によって自動で定義されるメソッド-->
1
2
3
4
5
6
7
8
9
# @ageの読み取り専用メソッド(参照可能になる)
def age
  @age
end

# @ageの書き込み専用メソッド(変更可能になる)
def age=(age)
  @age = age
end

このように、attr_accessor :ageの1行だけのコードで@ageの読み取り・書き込み専用のメソッドが定義されます。

attr_accessorメソッドの使用例

以下のattr_accessor :name, :ageのように記述すると、クラス外部から@nameと@ageの参照と変更が可能になります。

attr_accessorメソッドを定義して各インスタンス変数にアクセスした場合-->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class User
  attr_accessor :name, :age # この記述で@name,@ageへアクセス出来る

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

tanaka = User.new('田中太郎', 18)
p tanaka.name #=> "田中太郎"
p tanaka.age #=> 18  

tanaka.age = 33 
p tanaka.age # => 33 

しかしattr_accessor :name, :ageを削除して、インスタンス変数を変更・参照しようとすると、それぞれ以下のようなエラーが発生します。

attr_accessorメソッドを定義しないでインスタンス変数にアクセスした場合-->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class User
  # attr_accessor :name, :age は削除,もしくはコメントアウトした場合
  def initialize(name, age)
    @name = name
    @age = age
  end
end

tanaka = User.new('田中太郎', 18)
p tanaka.name
#=> undefined method `name' for #<User:0x00007fca58860658 @name="田中太郎", @age=18> (NoMethodError)

p tanaka.age
#=> undefined method `age' for #<User:0x00007f8e9b8a87a8 @name="田中太郎", @age=18> (NoMethodError)

tanaka.age = 33
#=> undefined method `age=' for #<User:0x00007ff6cc91c470 @name="田中太郎", @age=18> (NoMethodError)

p tanaka.age
# => undefined method `age' for #<User:0x00007f8c510e0788 @name="田中太郎", @age=18> (NoMethodError)

エラー文では、name、age、age=のメソッドが未定義だと出ています。これらのメソッドはインスタンス変数にアクセスするためのメソッドです。attr_accessor :name, :ageは、このメソッドを定義していた事が分かりますね。

しかし、インスタンス変数にアクセスする際に何故このようなメソッドが必要なのでしょうか?次の章で詳しく解説します。

アクセサメソッドの必要性

クラスで定義したインスタンス変数をクラスの外部からアクセスするには、インスタンスメソッドで経由させる必要があります。

例えば、以下のtanaka.sayはsayインスタンスメソッドを経由しているので、クラス外部からでも@nameと@ageを参照することが出来ています。

user.rb | sayインスタンスメソッド経由で@name,@ageを参照する-->
1
2
3
4
5
6
7
8
9
10
11
12
13
class User
  def initialize(name, age)
    @name = name
    @age = age
  end

  def say
    p "#{@name}#{@age}歳です。"
  end
end

tanaka = User.new('田中太郎', 18)
tanaka.say #=> "田中太郎、18歳です。"

しかし、以下のコードのようにインスタンスメソッドを経由せずに直接tanaka.@nameで参照しようとするとエラーが発生します。

user.rb | インスタンスメソッドなしで直接@nameを参照した場合-->
1
2
3
4
5
6
7
8
9
10
class User
  def initialize(name, age)
    @name = name
    @age = age
  end
end

tanaka = User.new('田中太郎', 18)
tanaka.@name
#=> syntax error, unexpected tIVAR, expecting '(' tanaka.@name

インスタンス変数を変更する場合も同様です。以下のtanaka.set_age(33)はset_ageインスタンスメソッドを経由しているので、クラス外部からでも@ageを変更することが出来ています。

user.rb | set_ageインスタンスメソッドで@ageを変更する例-->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class User
  def initialize(name, age)
    @name = name
    @age = age
  end

  def say
    p "#{@name}#{@age}歳です。"
  end

  def set_age(age) # @ageを変更する為に追加
    @age = age
  end
end

tanaka = User.new('田中太郎', 18)
tanaka.say #=> "田中太郎、18歳です。"

tanaka.set_age(33) # インスタンスメソッド経由で@ageを変更する
tanaka.say #=> "田中太郎、33歳です。"

しかし、以下のコードのようにインスタンスメソッドを経由せずに直接tanaka.@age =33で変更しようとするとエラーが発生します。

user.rb | インスタンスメソッドを経由しないでインスタンス変数に変更を加えようとした場合-->
1
2
3
4
5
6
7
8
9
10
class User
  def initialize(name, age)
    @name = name
    @age = age
  end
end

tanaka = User.new('田中太郎', 18)
tanaka.@age = 33
#=> syntax error, unexpected tIVAR, expecting '('tanaka.@age = 33

このようにクラスの外部からインスタンス変数を参照・変更する場合には、インスタンスメソッドを経由する必要があります。

ゲッターメソッドとは?

インスタンス変数の読み取り専用のインスタンスメソッドを「ゲッターメソッド」と呼びます。ゲッターメソッドは、以下のnameメソッドやageメソッドのように、メソッドが呼び出された時に各インスタンス変数を返すように定義します。

user.rb | ゲッターメソッドを経由してインスタンス変数を参照する -->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class User
  def initialize(name, age)
    @name = name
    @age = age
  end

  def name  # ゲッターメソッド
    @name # メソッドが呼び出されたら@nameを返す
  end

  def age # ゲッターメソッド
    @age # メソッドが呼び出されたら@ageを返す
  end
end

tanaka = User.new('田中太郎', 18)
p tanaka.name # nameメソッドを呼び出す
#=> "田中太郎"
p tanaka.age # ageメソッドを呼び出す
#=> 18

上記のtanaka.nameのように、nameというゲッターメソッド(読み取り専用のインスタンスメソッド)を経由することでインスタンス変数(@name)をクラスの外部から参照することが出来ています。

このゲッターメソッドは、参照したいインスタンス変数が増える度に定義する必要があるのでコード量が増えてしまいますが、attr_readerメソッドを使えば、明示的に1つ1つのゲッターメソッドを定義する必要もなく以下の記述だけで済みます。

user.rb | attr_readerメソッドを使用した場合-->
1
2
3
4
5
6
7
8
9
class User
  # この記述だけで各ゲッターメソッドが定義される
  attr_reader :name, :age  

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

attr_readerメソッドを使った場合でも、以下のように各インスタンス変数の参照をすることが出来ています。

user.rb | attr_readerを使った場合でも@name,@ageの参照が可能-->
1
2
3
4
5
tanaka = User.new('田中太郎', 18)
p tanaka.name 
#=> "田中太郎"
p tanaka.age 
#=> 18

これはattr_reader :nameと記述すると、以下のコードのように@nameを参照するためのゲッターメソッドが定義されるためです。

attr_reader :nameを記述した場合-->
1
2
3
4
5
6
attr_reader :name

# 以下のメソッドは、上記の記述で自動で定義されるメソッド
def name
  @name
end

このようにattr_readerメソッドは、ゲッターメソッドを定義することが出来ます。
詳細は「attr_readerメソッドの使い方」を参考にしてください。

ポイント
  1. インスタンス変数の読み取り専用のインスタンスメソッドを「ゲッターメソッド」と呼ぶ
  2. attr_readerメソッドは、ゲッターメソッドを定義してくれるメソッド

セッターメソッドとは?

インスタンス変数の書き込み専用のインスタンスメソッドを「セッターメソッド」と呼びます。セッターメソッドは、以下のname=メソッドage=メソッドのように、メソッドが呼び出された時に各インスタンス変数の値を変更するように定義します。

user.rb | セッターメソッドを定義してインスタンス変数に変更を加えられる様にする-->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class User
  def initialize(name, age)
    @name = name
    @age = age
  end

  def name=(name) # セッターメソッド
    @name = name #メソッドが呼び出されたら@nameを引数の値に変更
  end

  def age=(age) # セッターメソッド
    @age = age #メソッドが呼び出されたら@ageを引数の値に変更
  end
end

tanaka = User.new('田中太郎', 18)
tanaka.age = 33 # age=メソッドを呼び出している

上記のコードのtanaka.age = 33によってage=というセッターメソッド(書き込み専用のインスタンスメソッド)を経由することでインスタンス変数(@age)をクラスの外部から変更することが出来ます。

このセッターメソッドは、変更したいインスタンス変数が増える度に定義する必要があるのでコード量が増えてしまいますが、attr_writerメソッドを使えば、明示的に1つ1つのセッターメソッドを定義する必要もなく以下の記述だけで済みます。

user.rb | attr_writerメソッドを使用した場合-->
1
2
3
4
5
6
7
8
9
class User
  # 以下の記述だけで各セッターメソッドが定義される
  attr_writer :name, :age

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

attr_writerメソッドを使った場合でも、以下のように各インスタンス変数の変更が出来ています。(インスタンス変数の変更を確認する為に、attr_readerメソッドを使って参照できるようにします。)

user.rb | attr_writerメソッドを使用した場合-->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class User
  attr_reader :age # 変更を確認する為に追加
  attr_writer :name, :age

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

tanaka =  User.new('田中太郎', 18)
p tanaka.age # => 18

tanaka.age = 33
p tanaka.age # => 33 変更されている

インスタンス変数の変更ができている理由は、以下のコードのようにattr_writer :ageと記述すると@ageを変更するためのセッターメソッドが定義されるからです。

attr_writer :ageを記述した場合 -->
1
2
3
4
5
6
attr_writer :age

# 以下のメソッドは、上記の記述で自動で定義されるメソッド
def age=(age)
  @age = age
end

このように、attr_writerメソッドはセッターメソッドを定義することが出来ます。
詳細は「attr_writerメソッドの使い方」を参考にしてください。

ポイント
  1. インスタンス変数の書き込み専用のインスタンスメソッドを「セッターメソッド」と呼ぶ
  2. attr_writerメソッドは、セッターメソッドを定義してくれるメソッド

attr_accessorの役割

ここまでの解説でクラス外部からインスタンス変数を参照・変更するには、以下のコードのようにゲッターメソッドとセッターメソッドの定義が必要という事が分かりました。

user.rb | インスタンス変数にアクセスする為にゲッター/セッターメソッドを定義-->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class User
  def initialize(name, age)
    @name = name
    @age = age
  end

  def name # ゲッターメソッド
    @name
  end

  def age # ゲッターメソッド
    @age
  end

  def name=(name) # セッターメソッド
    @name = name
  end

  def age=(age) # セッターメソッド
    @age = age
  end
end

tanaka = User.new('田中太郎', 18)
p tanaka.age #=> 18  @ageを参照する
tanaka.age = 33 # @ageを変更する
p tanaka.age # => 33

そして、このゲッターメソッドとセッターメソッドはattr_readerメソッドattr_writerメソッドを使うと、以下のコードのように記述する事が出来ました。

user.rb | attr_reader,attr_writerメソッドを使用した場合-->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class User
  attr_reader :name, :age # @name,@ageのゲッターメソッドを定義
  attr_writer :name, :age # @name,@ageのセッターメソッドを定義

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

tanaka = User.new('田中太郎', 18)
p tanaka.age #=> 18
tanaka.age = 33
p tanaka.age # => 33

では、@nameや@age以外にも参照と変更の両方したいインスタンス変数が出てきた場合はどうすれば良いでしょうか?

以下のコードのように、対象のインスタンス変数名をattr_readerとattr_writerに追加すれば参照・変更共に可能になりますね。

user.rb | 参照・変更の両方をしたいインスタンス変数が増えた場合-->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class User
  attr_reader :name, :age, :mail, :cell # @mail,@cellをを追加
  attr_writer :name, :age, :mail, :cell # @mail,@cellをを追加

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

tanaka =  User.new('田中太郎', 18, 'hoge@example.com', '070-09xx-xxxx')
p tanaka.mail #=> "hoge@example.com"
tanaka.cell = '090-09xx-xxxx'
p tanaka.cell #=> "090-09xx-xxxx"

しかし、どちらのメソッドにも指定してるインスタンス変数名は一緒なので、参照・変更の両方を一気に定義したいですね。

そんな場合にattr_accessorメソッドを使えば、以下のコードのように記述する事が出来ます。

user.rb | attr_accessorメソッドを使用した場合 -->
1
2
3
4
5
6
7
8
9
10
11
class User
  # 各インスタンス変数のゲッター/セッターメソッドの両方が定義される
  attr_accessor :name, :age, :mail, :cell

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

attr_accessorを使った場合でも、以下のように各インスタンス変数の変更と参照が出来ています。

user.rb | attr_accessorを使った場合でもインスタンス変数の参照と変更は出来ている
1
2
3
4
tanaka =  User.new('田中太郎', 18, 'hoge@example.com', '070-09xx-xxxx')
p tanaka.mail #=> "hoge@example.com"
tanaka.cell = '090-09xx-xxxx'
p tanaka.cell #=> "090-09xx-xxxx"

これはattr_accessor :ageと記述した場合に、以下のコードのように@ageを参照と変更するためのゲッター/セッターの両方のメソッドが定義されるためです。

attr_accessor :ageの記述によって自動で定義されるメソッド-->
1
2
3
4
5
6
7
8
9
# @ageのゲッターメソッド(参照可能になる)
def age
  @age
end

# @ageのセッターメソッド(変更可能になる)
def age=(age)
  @age = age
end

このように、attr_accessorメソッドはゲッター/セッターメソッドの両方を定義することが出来ます。

アクセサメソッドの整理

attr_accessorメソッドの使い所やここまで解説したアクセサメソッド、attr系メソッドについて一度整理します。

attr_accessorメソッドの使い所

attr_accessorメソッドは、指定したインスタンス変数のゲッター/セッターメソッドを定義してくれるメソッドなので、クラス外部からインスタンス変数を参照もしたいし変更も加えたい場合に使用します。

user.rb | attr_accessorメソッドを使う場合-->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class User
  attr_accessor :name, :age

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

tanaka = User.new('田中太郎', 18)
p tanaka.age #=> 18  @ageを参照 

tanaka.age = 33 # @ageを変更
p tanaka.age # => 33  

逆にインスタンス変数を参照・変更のどちらかだけしたい場合は、それぞれattr_readerメソッドやattr_writerメソッドを使います。

例えば、ユーザーの名前や年齢は参照や変更することが出来るが、メールアドレスは参照のみで変更されたくない場合は以下のように記述します。

user.rb | @mailは参照だけで変更しない場合 -->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class User
  attr_accessor :name, :age # @name,@ageは参照・変更どちらも出来る
  attr_reader :mail # @mailは参照だけで変更しない場合

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

tanaka = User.new('田中太郎', 18, 'hoge@example.com')
tanaka.age = 33
tanaka.mail = 'fuga@example.com'
# => undefined method `mail=' for #<User:0x00007fd49f054670> (NoMethodError)

上記のコードは、attr_accessorメソッドによって@nameや@ageの値は参照と変更共に可能にしていますが、@mailは含まれずattr_readerで指定しているので参照のみになります。

attr系メソッドまとめ

アクセサメソッドを定義してくれるメソッドは、attr_writerメソッド、attr_readerメソッド、attr_accessorメソッドがあります。各メソッドの特徴は、以下の表の通りです。

attr系メソッド 定義されるメソッド インスタンス変数の値に対して
attr_writer セッターメソッドを定義 変更のみ
attr_reader ゲッターメソッドを定義 参照のみ
attr_accessor ゲッター/セッター両方を定義 参照と変更の両方

ゲッターメソッドとセッターメソッドの両方を定義し、インスタンス変数の値を参照と変更の両方可能にするのがattr_accessorメソッドです。

ぴっかちゃん

attr系メソッドについてしっかりと学びたい方は、こちらの参考書で理解を深めることができます。

Railsのアクセサメソッド

Railsを触っている人は、「確かアクセサメソッドを定義していなくてもクラスのインスタンス変数に外部からのアクセスが出来たよな?」と感じた方がいると思います。

Railsでは、AcriveRecodeを継承したクラスのインスタンスは、テーブルに定義したカラムに対応するアクセサメソッドが自動的に定義されます。その為、特に意識する事なくインスタンス変数にアクセス出来てしまいます。

しかし、テーブルに定義したカラム以外に値を持たせたい場合にattr_accessorメソッドを使用する事が多いです。

この記事のまとめ

  • attr_accessorメソッドとは、ゲッター/セッターメソッドの両方を定義してくれるメソッド
  • attr_accessorメソッドを使うと、インスタンス変数をクラス外部からアクセスする事が出来る
  • 具体的には、インスタンス変数の参照と変更の両方が出来るようになる