Ruby

【Rails】駆け出しエンジニアが、謎のattr_reader/writer/accessorを、紐解いてみる

バートリーのさいとうです。

今回は

謎のattr_reader/writer/accessorを駆け出しエンジニアが、紐解いてみる

というテーマでお話ししてきます。

正直、このattr_reader/writer/accessorは、至極理解しにくい。

例えば、このように記述されます。

class User < ActiveRecord::Base
  attr_accessor: last_name, first_name, email
end

「ぱっと見何をしているかわからない…」

そんな悩みを、rails実装者であれば、一度は抱えたことがあると思います。

あまりqiitaやzennでも説明されている記事が少ない気がします。

そこで今回は、このattr_reader/writer/accessorを、最も身近な(?)railsにおけるカラムの役割に深く切り込みながら、解説していきます。

この記事を読めば

  • railsでカラムがどんな役割を持っているの分かる
  • カラムが生成されるとモデルに暗黙的に記述されるメソッドがあることが分かる
  • カラムとattr_reader/writer/accessorが、機能的に近いことが分かる

と思いますので、“rail”のその先を見たいという方は、ぜひ最後までお付き合いください。

railsでカラムが定義された時に起こること

本題の、attr_reader/writer/accessorに入る前に、その理解を手助けするrailsのカラムについて見ていきましょう。

まずは、以下のテーブルを用意します。

class User < ActiveRecord::Base
  #  id                             :integer          not null, primary key
  #  user_name               :string
  #  age                          :integer

end

では、このDBを利用する例を見て見ましょう。

カラムの値を取得・編集する

user = User.find(1) #=> {id: 1, user_name: sunny, age: 23}
#取得
p user.user_name #=> sunny
# 編集・更新
user.user_name = thunder 
# 取得(編集後)
p user.user_name #=> thunder

このようにして、rubyではカラムの値を取得したり、編集・更新できたりします。

言い換えると、カラムに定義されている属性は、読み込みと書き込みが可能ということです。

ここで、勘の良い方は

読み込み = reader

書き込み = writer

ということに気づいたかもしれません。

先ほどの、attr_reader と attr_writerと、近いものを感じますね。

もう少し、カラムのことを深掘ってから、attr_〇〇の核心に迫りましょう。

クラスからインスタンスが生成される

一度、基本を振り返りましょう。

rubyは、クラスからインスタンスが生成されます。

先ほどのDBをもう一度見てみましょう。

横の1行1行が、今回インスタンスに相当すると思ってください。(厳密には少し違う気がしますが)

これらのインスタンスは

class User  < ActiveRecord::Base
end

user = User.new(id: 1, user_name: sunny, age: 23)
user.save

のように、保存がなされたと推測できます。

この時に、(明示されてはいませんが)実はUserクラスにこのような定義がなされています。

class User  < ActiveRecord::Base
  #読み込み用
  def user_name
    @user_name
  end

  #書き込み用
  def user_name=(val)
    @user_name = val
  end

  def age
    @age
  end

  def age=(val)
    @age = val
  end
end

user = User.new(id: 1, user_name: sunny, age: 23)
user.save

もう一度言いますが、明示はされていません。

なので、コードのどこを見てもこんなコードは書いていません。

でも、rails師匠が内緒でこっそり

  • 読み込み用のメソッド = DBから値を取得する時に利用
  • 書き込み用のメソッド = DBに値を保存したり、編集更新する時に利用

の二つを定義してくれています。

このおかげで、僕たちはDBに値を保存したり、DBから値を取得できたりします。

ここまでで、カラムのことについて深く理解できたでしょうか?

理解できていると仮定して、では、ついに本題のattr_reader/writer/accessorに入ります。

attr_reader + attr_writer = attr_accessor

まず、前提として、attr_readerとattr_writerの機能を合わせたものがattr_accessorです。

ですので、ここではattr_readerとattr_writerについて説明します。

attr_reader = 読み込み

まずは、attr_readerからです。

readとついているので、何かを読みそうですね。

attr_readerに定義されると、暗黙的にこんなことが起きます。

class User  < ActiveRecord::Base
  #attr_readerにgenderを定義すると...
  attr_reader :gender

  #以下のメソッドが暗黙的に定義されます。
  def gender
    @gender
  end

end

おや、先ほど見た気がしますね。

そう、カラムとして定義されるとこっそりとrails師匠が定義してくれるメソッドです。(以下再掲)

class User  < ActiveRecord::Base
  #読み込み用
  def user_name
    @user_name
  end
.
.
.
end

attr_readerの引数にシンボルで属性を追加することで、カラムの定義がされると暗黙的に作成されていたメソッドと同じ、「読み込み用のメソッド」が定義されたようです。

このまま、attr_writerも見てみましょう。

attr_writer = 書き込み

早速実例を見てみましょう。

class User  < ActiveRecord::Base
  #attr_writerにgenderを定義すると...
  attr_writer :gender

  #以下のメソッドが暗黙的に定義されます。
  def gender=(val)
    @gender = val
  end

end

attr_reader同様、暗黙的にメソッドが定義されました。

これも、どこかで見ましたね…そう、これもカラムの定義と同じです。(以下再掲)

class User  < ActiveRecord::Base
.
.
. 
 #書き込み用
  def user_name=(val)
    @user_name = val
  end
.
.
.
end

attr_writerは、書き込み用のメソッドを定義してくれるみたいです。

一度、ここまでをまとめると

attr_readerとattr_writer
  • attr_readerの引数に指定された属性は、読み込み用のメソッドが定義される。(@属性を持つ)
  • attr_writerの引数に指定された属性は、書き込み用のメソッドが定義される。(@属性の値を編集できる)
  • どちらも、railsの機能によって得られる、カラムが定義されると暗黙的に作成されるメソッドと同じである。

(*@属性 = インスタンス変数)

つまり

attr_readerとattr_writerの引数に定義されると、読み込みと書き込みが可能な属性を作成することができる

ということです。

また、

  • 読み込みだけさせたいな、と思ったらattr_reader
  • 書き込みだけさせたいな、と思ったらattr_writer
  • 両方させたいな、と思ったらattr_accessor

という使い分けができます。

これがわかると、例えばformObjectやサービスクラスなどの、いわゆるモデル層の切り出しの際にかなり色々なことができるようになります。

肌感ですが、コードリーディングしていても、頻繁に出てきます。

今回この記事を書くにあたって参考にさせていただいいたのはこちらの記事です。参考にしてみてくださいね。

おまけ:結局カラムとは?

最後に、結局カラムとは?ということについて喋って終わりにします。

カラムが定義されると、そのカラム名の属性が、定義されたクラスから生成されるインスタンスの属性として定義されます。

この属性のことを、インスタンス変数といいます。

例えば、先ほどのusersテーブルであれば、idが1のuser_nameはsunnyです。

この1とsunnyがそれぞれインスタンス変数の中身になります。

イメージとしては、以下の通りです。

なんだか話しているうちに訳が分からなくなりそうなのでここでやめにしますが

大切なのは、カラムが定義されたら何が起きているかを正しく理解し、その起きていることをカラムはもちろん、カラム以外に適用できたりするということを理解しておくことかなと思います。

まとめ

いかがだったでしょうか。

今回は、駆け出しエンジニアの天敵とも呼べる、attr_シリーズを取り上げました。

このブログは、月に15本を目標に、実務から学んだプログラミングのあれこれを発信しています。

誰かのためになれば、嬉しいです。

最後まで読んでいただき、ありがとうございました。

それでは。

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA