すでにメンバーの場合は

無料会員登録

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

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

Pikawakaにログイン

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

Ruby

【Ruby】 attr_writerメソッド使ってコードの可読性を上げよう

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

attr_writerとは、インスタンス変数の書き込み専用のメソッド(セッターメソッド)を自動で定義することが出来るメソッドのことです。

Rubyではクラスのインスタンス変数を外部から変更する場合は、以下のように書き込み専用メソッドを定義する必要があります。

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
class User
  attr_reader :name, :age # @name,@ageの変更を確認するために記述

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

  def name=(name) # 書き込み専用メソッド
    @name = name
  end

  def age=(age) # 書き込み専用メソッド
    @age = age
  end
end

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

tanaka.name = '田中花子' # @nameを変更する
tanaka.age = 33 # @ageを変更する

p tanaka.name  #=> "田中花子"
p tanaka.age # => 33 

上記のコードだと変更したいインスタンス変数が増えるたびに書き込み専用のメソッドを定義する必要があるので、コード量が増えて可読性が下がります。

しかし、以下のコードのようにattr_writerメソッドを使えば、わざわざ明示的に書き込み専用のメソッドを定義する必要がないのでコード量も少なく済みます。

user.rb | attr_writerメソッドを使った場合-->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class User
  attr_reader :name, :age
  attr_writer :name, :age # これで書き込み専用のメソッドが定義される

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


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

tanaka.name = '田中花子' # @nameを変更する
tanaka.age = 33 # @ageを変更する

p tanaka.name  #=> "田中花子"
p tanaka.age # => 33 

このように、attr_writerメソッドに変更したいインスタンス変数名を指定するだけで、そのインスタンス変数の書き込み専用のメソッドを定義することが出来ます。

attr_writerメソッドの使い方

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

この章では、attr_writerメソッドの使い方だけではなく、大きく関係するsetter(セッター)も一緒に解説するので、attr_writerメソッドの必要性や使用場面を具体的に理解することが出来ます。

基本構文

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

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

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

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

attr_writerメソッドの使用例 -->
1
2
3
4
5
# シンボルの場合
attr_writer :name

# 文字列の場合
attr_writer 'name'

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

attr_writer :nameの記述によって自動で定義されるメソッド -->
1
2
3
def name=(name)
  @name = name
end

また、複数指定する場合は以下のようにカンマで区切ります。

複数指定する場合 -->
1
2
3
4
5
# シンボルの場合
attr_writer :name, :age

# 文字列の場合
attr_writer 'name', 'age'

セッターメソッドの必要性

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

例えば、以下の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
  attr_reader :name, :age # @name, @ageの変更を確認するために記述

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

  def set_age(age)
    @age = age
  end
end

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

tanaka.set_age(33) # インスタンスメソッド経由で@ageを変更する
p tanaka.age
#=> 33

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

user.rb | インスタンスメソッドなしでインスタンス変数に変更を加えようとした場合-->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class User
  attr_reader :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
  #=> 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
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

上記のセッターメソッドのようにメソッド名の末尾に=(イコール)をつけると、代入式のようにメソッドを呼び出すことが出来ます。

例えば、以下のtanaka.age = 33tanaka.age33を代入しているように見えますが、実際には引数に33を渡してage=メソッドを呼び出しているのです。

セッターメソッドの呼び出し例 -->
1
2
3
4
5
6
7
8
9
10
11
class User
  def age=(age) 
    @age = age # ここで@ageの値が33に変更されている
  end  
end

# 代入しているように見えるが。。。
tanaka.age = 33

# 実際には、以下のようにage=メソッドを呼び出している
tanaka.age=(33)

それでは、実際に@age33に変更されてるか確認できるように、以下のようにattr_readerメソッドを追加します。

user.rb | @ageの値が33に変更されているか確認する-->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class User
  attr_reader :age # @ageをクラス外部から参照出来るようにする

  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)
p tanaka.age
# => 18

tanaka.age = 33 # age=メソッドを呼び出して@ageを変更する
p tanaka.age
# => 33 # @ageが33に変更されている!

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

ポイント
  1. インスタンス変数をクラスの外部からアクセスするには、インスタンスメソッドで経由させる必要がある
  2. インスタンス変数の書き込み専用のメソッドを「セッターメソッド」と呼ぶ
  3. メソッド名の末尾に=(イコール)をつけると、代入式のようにメソッドを呼び出せる

attr_writerメソッドの役割とは?

セッターメソッドでクラス外部からインスタンス変数を変更することが出来ましたが、変更したいインスタンス変数が増える度にセッターメソッドを定義しなければなりません。

例えば、@nameや@age以外にも@mailや@cellを変更したい場合は、以下のコードのようにセッターメソッドを定義するので、コードがどんどん増えて可読性が下がります。

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
class User
  def initialize(name, age, mail, cell)
    @name = name
    @age = age
    @mail = mail
    @cell = cell
  end

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

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

  def mail=(mail) # 新たに追加したセッターメソッド
    @mail = mail
  end

  def cell=(cell) # 新たに追加したセッターメソッド
    @cell = cell
  end
end

しかし、attr_writerメソッドを使えば、明示的に1つ1つのセッターメソッドを定義する必要もなく以下の記述だけで済みます。

user.rb | attr_writerを使った場合の記述量-->
1
2
3
4
5
6
7
8
9
10
11
12
13
class User
  attr_reader :name, :age, :mail, :cell # インスタンス変数参照の為 

  # 以下の記述だけで各セッターメソッドが定義される
  attr_writer :name, :age, :mail, :cell 

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

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

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

tanaka.age = 33 # age=メソッドを呼び出して@ageを変更する
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メソッドはセッターメソッドを定義することが出来ます。

ポイント
  1. attr_writerメソッドを使えば、セッターメソッドを明示的に定義する必要がなくなる
  2. コード量が少なくなるので可読性が上がる
  3. attr_writerメソッドはセッターメソッドを定義してくれるメソッド

他のアクセサメソッドとの違い

インスタンス変数の書き込み(変更)専用メソッドの「セッターメソッド」の他にも、インスタンス変数の読み取り(参照)専用メソッドである「ゲッターメソッド」があります。

以下のコードのようにゲッターメソッドを定義すると、クラス外部からインスタンス変数の値を参照することが出来ます。

user.rb | ゲッターメソッドの使用例-->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class User
  def initialize(name, age)
    @name = name
    @age = age
  end

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

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

上記では、ゲッターメソッドを定義した事でインスタンス変数の参照を行うことが出来ました。

しかし、以下のコードのようにセッターメソッドを定義するattr_writerメソッドの記述のみでは、インスタンス変数を変更するだけでゲッターメソッドのような値を参照する機能はないので注意してください。

user.rb | ゲッターメソッドを定義しないでインスタンス変数を参照した場合-->
1
2
3
4
5
6
7
8
9
10
11
12
13
class User
  attr_writer :name, :age

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

end

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

このようにインスタンス変数の値を参照する場合はゲッターメソッドか、ゲッターメソッドを定義するattr_readerメソッドが必要になります。詳細は「attr_readerメソッドの使い方」を参考にしてください。

attr系メソッドまとめ

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

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

ゲッターメソッドとセッターメソッドの両方を定義し、インスタンス変数の値を参照と変更の両方可能にするのがattr_accessorメソッドです。こちらのメソッドもよく使われるので、まだ知らない人は「attr_accessorメソッドの使い方」を参考にしてください。

この記事のまとめ

  • attr_writerメソッドは、セッターメソッドを定義してくれるメソッド
  • セッターメソッドとは、インスタンス変数の値を変更する為のメソッド
  • attr_writerメソッドは、インスタンス変数の値の変更のみで参照することは出来ない