更新日:
【Ruby】 attr_accessorメソッドの使い方と必要な理由とは?
attr_accessorとは、インスタンス変数の読み取り専用のメソッドと書き込み専用のメソッド(セッター/ゲッター)の両方を定義することが出来るメソッドのことです。
Rubyでは、以下のコードのように記述してクラス外部からインスタンス変数にアクセスしようとすると、「インスタンス変数にアクセスするためのメソッドが定義されていない」というエラーが発生します。
これにより、クラス外部からのインスタンス変数の参照や変更が出来ません。
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メソッド
を使うと、クラス外部からインスタンス変数へのアクセスが可能になります。
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 'インスタンス変数名'
インスタンス変数名は、以下のように@なしで記述します。
1
2
3
4
5
# シンボルの場合
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の参照と変更が可能になります。
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
を削除して、インスタンス変数を変更・参照しようとすると、それぞれ以下のようなエラーが発生します。
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を参照することが出来ています。
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
で参照しようとするとエラーが発生します。
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を変更することが出来ています。
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
で変更しようとするとエラーが発生します。
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メソッドのように、メソッドが呼び出された時に各インスタンス変数を返すように定義します。
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つのゲッターメソッドを定義する必要もなく以下の記述だけで済みます。
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メソッドを使った場合でも、以下のように各インスタンス変数の参照をすることが出来ています。
1
2
3
4
5
tanaka = User.new('田中太郎', 18)
p tanaka.name
#=> "田中太郎"
p tanaka.age
#=> 18
これはattr_reader :name
と記述すると、以下のコードのように@nameを参照するためのゲッターメソッドが定義されるためです。
1
2
3
4
5
6
attr_reader :name
# 以下のメソッドは、上記の記述で自動で定義されるメソッド
def name
@name
end
このようにattr_readerメソッドは、ゲッターメソッドを定義することが出来ます。
詳細は「attr_readerメソッドの使い方」を参考にしてください。
セッターメソッドとは?
インスタンス変数の書き込み専用のインスタンスメソッドを「セッターメソッド」と呼びます。セッターメソッドは、以下のname=メソッド
やage=メソッド
のように、メソッドが呼び出された時に各インスタンス変数の値を変更するように定義します。
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つのセッターメソッドを定義する必要もなく以下の記述だけで済みます。
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メソッドを使って参照できるようにします。)
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を変更するためのセッターメソッドが定義されるからです。
1
2
3
4
5
6
attr_writer :age
# 以下のメソッドは、上記の記述で自動で定義されるメソッド
def age=(age)
@age = age
end
このように、attr_writerメソッドはセッターメソッドを定義することが出来ます。
詳細は「attr_writerメソッドの使い方」を参考にしてください。
attr_accessorの役割
ここまでの解説でクラス外部からインスタンス変数を参照・変更するには、以下のコードのようにゲッターメソッドとセッターメソッドの定義が必要という事が分かりました。
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メソッド
を使うと、以下のコードのように記述する事が出来ました。
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に追加すれば参照・変更共に可能になりますね。
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メソッド
を使えば、以下のコードのように記述する事が出来ます。
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を使った場合でも、以下のように各インスタンス変数の変更と参照が出来ています。
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を参照と変更するためのゲッター/セッターの両方のメソッドが定義されるためです。
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メソッドは、指定したインスタンス変数のゲッター/セッターメソッドを定義してくれるメソッドなので、クラス外部からインスタンス変数を参照もしたいし変更も加えたい場合に使用します。
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メソッドを使います。
例えば、ユーザーの名前や年齢は参照や変更することが出来るが、メールアドレスは参照のみで変更されたくない場合は以下のように記述します。
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メソッドを使うと、インスタンス変数をクラス外部からアクセスする事が出来る
- 具体的には、インスタンス変数の参照と変更の両方が出来るようになる