はじめに
こんにちは! @Yu_yukk_Y です。
突然ですが、ActiveRecordモデルにDBカラムと同じ名前のattr_readerを設定すると何が起きるか知っていますか?
なんと、そのカラムは確定でnilが返されるようになるのです!
具体的には、下記のようなモデルがあった時textカラムが確定でnilになります。
class Memo < ApplicationRecord
attr_reader :text
end
memo = Memo.first
memo.text # => nil(DBに値があっても nil が返る)
この記事では、初心者の自分がびっくりした上記の挙動について、内部では何が起きていたのかを探っていきます。
Rubyのattr_readerの挙動
Rubyのattr_readerでは、指定された名前に対して下記のようなメソッドを生成します
def text
@text
end
つまり、attr_readerを指定してカラムにアクセスする場合、内部的にはそのカラム名のメソッド経由でインスタンス変数にアクセスしているのです。
ActiveRecordのカラムの挙動
まず、ActiveRecordでアクセスするDBカラムはそのカラム名(@カラム名)のインスタンス変数には保存されません。
実際には内部の@attributes変数(ActiveModel::AttributeSet)に保存されます。
そして、下記のような処理で属性名のメソッドを作成し、@attributes変数から指定された値を読み取っているのです。
なぜattr_readerで指定したカラムはnilになったのか
Rubyのメソッドの探索順序の優先度が
attr_reader > includeされたメソッド
だからです。
また、ActiveRecordでは@カラム名のインスタンス変数に値を保存しないため、常にnilが返されていたのでした。
それぞれのカラム読み込みの仕組みさえわかればあとはシンプルですね!
ちなみにどうしてもDBカラムと同名のattr_readerを設定する必要がある場合、DBカラムはread_attributeというメソッドを用いることで読み取ることができます。
余談
ActiveRecordモデルにおいて、DBカラムと同名のattr_readerが設定されているとattr_readerによって生成されたメソッドが優先されることがわかりました。
では、そもそもActiveRecordによるメソッド生成は行われているのでしょうか?
実は、attr_readerが設定されていてもメソッドの生成は行われます。
attr_readerによって作成されたメソッドを動的に削除することで確かめることができます。
adcale2025(dev)> Memo.first.text
Memo Load (0.3ms) SELECT "memos".* FROM "memos" ORDER BY "memos"."id" ASC LIMIT 1 /*application='Adcale2025'*/
=> nil
adcale2025(dev)> Memo.class_eval { remove_method :text }
=> Memo(id: integer, text: string, created_at: datetime, updated_at: datetime)
adcale2025(dev)> Memo.method_defined? :text
=> true
adcale2025(dev)> Memo.first.text
Memo Load (0.3ms) SELECT "memos".* FROM "memos" ORDER BY "memos"."id" ASC LIMIT 1 /*application='Adcale2025'*/
=> "from ActiveRecord"
まとめ
今回の記事ではActiveRecordモデルにDBカラムと同じ名前のattr_readerを設定したときの挙動について深掘りしました。
アドカレという機会を借りて以前から気になっていたことを調査できてよかったです!
また、今回の調査ではDeepWikiをフル活用しました!AIの力で大規模なソースコードを効率的に探索できるので大変便利でありがたいです🙌