【Ruby】attr_accessor、attr_reader、attr_writerの違いを例から理解する

## 目的
Rubyのattr_accessor、attr_reader、attr_writerについて、色々調べたり、手を動かしたりして、理解できたことをまとめておきます。

## attr_accessor、attr_reader、attr_writer の定義

まずは、Rubyリファレンスで定義を見てみましょう。
> attr_readerメソッドは、クラスやモジュールにインスタンス変数を読み出すためのアクセサメソッドを定義します。引数には、インスタンス変数名をシンボルか文字列で指定します（複数指定できます）。戻り値はnilです。  

> attr_writerメソッドは、クラスやモジュールにインスタンス変数を書き込むためのアクセサメソッドを定義します。引数には、インスタンス変数名をシンボルか文字列で指定します（複数指定できます）。戻り値はnilです。

> attr_accessorメソッドは、クラスやモジュールにインスタンス変数を読み書きするためのアクセサメソッドを定義します。引数には、インスタンス変数名をシンボルか文字列で指定します（複数指定できます）。戻り値はnilです。  

定義からは少し分かりづらいかもしれませんが、コードから理解してみましょう。

## 最初からまとめ
簡潔に言うと、
- attr_readerメソッドは、インスタンス変数を呼び出すメソッドを定義する。
- attr_writerメソッドは、インスタンス変数を書き込みメソッドを定義する。
- attr_accessorメソッドは、インスタンス変数を読み書きメソッドを定義する。

これを心に留めておいて、例を見てみましょう。

## それぞれの意味
#### attr_readerメソッド
実は、attr_readerメソッドは以下のインスタンスメソッドと同じ意味です。

```ruby
  def name
    @name
  end
```

例：

```ruby
class Member
  def name
    @name
  end
end
member = Member.new
member.name = "Chimei" # => NoMethodError (undefined method 'name=')
member.name # => nil
```
attr_readerメソッドはクラスからインスタンス変数を読み出すしかできないので、
ここで`member.name = "Chimei"`、値を書き込みしようとすると`NoMethodError (undefined method 'name=')`エラーが出てきます。
値が入ってないから、もちろん`member.name`はnilとなっています。

#### attr_writerメソッド
attr_writerメソッドは以下のインスタンスメソッドと同じ意味です。

```ruby
  def name=(name)
    @name = name
  end
```
例：

```ruby
class Member
  def name
    @name
  end
  def name=(name)
    @name = name
  end
end
member = Member.new
member.name # => nil
member.name = "Chimei" # => "Chimei"
member.name # =>  "Chimei"
```
最初は`member.name`はnilですが、attr_readerメソッドはクラスにインスタンス変数を書き込みできるので、`member.name = "Chimei"`で値を書き込んで、そして`member.name`で値を確認してみると、確かに値が入っていましたね。

#### attr_accessorメソッド
attr_readerメソッドとattr_writerメソッドのフュージョン:point_right::point_left:です。
つまり、クラスにインスタンス変数を読み出しだけではなく、書き込みもできる。
以下の二つ例が同じ意味です。

```ruby
class Member
  attr_reader :name
  attr_writer :name
end

member = Member.new
member.name = "Chimei" # => "Chimei"
member.name # => "Chimei"
```

```ruby
class Member
  attr_accessor :name
end
member = Member.new
member.name = "Chimei"
member.name # => "Chimei"
```

## 三つ一緒に使ってみる
例１：

```ruby
class Profile
  attr_reader :name
  attr_writer :job
  attr_accessor :experience
end

profile = Profile.new

# 現在、値が何も入ってないから、値を書き込んでみよう
profile.name = "Shane" # => NoMethodError (undefined method `name=') 【書き込みできない】
profile.job = "teacher" # => "teacher"
profile.experience = 8 # => 8

# 値を確認
profile.name # => nil【書き込みできないから、値がnil】
profile.job # => NoMethodError (undefined method `job') 【読み出しできない】
profile.experience # => 8
```
例２：

```ruby
class Profile
  attr_reader :name
  attr_writer :job
  attr_accessor :experience

  def initialize(name, job, experience)
    @name = name
    @job = job
    @experience = experience
  end

  def intro
    "Hi, I am #{@name}. I work as a #{@job} for #{@experience} years."
  end
end

profile = Profile.new("Shane", "teacher", 8)
profile.intro # => "Hi, I am Shane. I work as a teacher for 8 years."

# 読み出す場合
profile.name # => "Shane"
profile.job # => NoMethodError (undefined method `age') 【読み出しできない（書き込むだけOK）】
profile.experience # => 8

# 値を変更しようとすると
profile.name = "Sid" # => NoMethodError (undefined method `name=') 【書き込みできない（読み出すだけOK）】
profile.job = "headhunter" # => "headhunter"
profile.experience = 3 # => 3

# 結果を確認
profile.intro # => "Hi, I am Shane. I work as a headhunter for 3 years."
```

initializeメソッドはインスタンスを初期化（ここで`Profile.new`）時、自動で実行します。
そこで、三つの引数を値が入りました。 なので、１番目の`profile.intro`が特に問題ないです。

ですが、`profile.name = "Sid"`で値を変更できないので、
２番目の`profile.intro`は nameの値は元々の`Shane`で出力してしまいました。

## もう一度まとめ
attr_reader：インスタンス変数を呼び出すメソッドだけを定義したので、書き込みできない。
attr_writer：インスタンス変数を書き込みメソッドだけを定義したので、呼び出しできない。
attr_accessor：インスタンス変数を呼び出すと書き込むメソッドを定義しましたので、どちらでもできる。

######ここまでお読みいただきありがとうございました。多分文法上おかしいと思いますが、来日して2年目ですので日本語まだ勉強中です。文法ミスや誤字がありましたらあらかじめご了承ください。:pray_tone1:もちろん編集リクエストでご意見いただければと思います。

## 参考文献
https://www.rubyguides.com/2018/11/attr_accessor/
https://ref.xaio.jp/ruby/classes/module/attr_accessor
https://ref.xaio.jp/ruby/classes/module/attr_reader
https://ref.xaio.jp/ruby/classes/module/attr_writer