Tech Racho エンジニアの「?」を「!」に。
  • 開発

after_initializeの使いドコロがわからない

Modelの値に初期値を設定するような用途にafter_initialize を使うのは
やめておいておいたほうがいいのかもしれません。

参考

Railsでモデルの初期化処理を書きたい場合の注意
このページにafter_initialize, after_find を使う際の注意点がまとまっているので参考にしたのですが
今回は検索にwhereメソッドのみを使っていたので問題は無いと思って油断していました。

サンプル事例

プログラムではUserのデータを作成する時に、仮の名前を予めセットしておくような使い方をしていました。

class User < ActiveRecord::Base
  after_initialize do
    self.name ||= "NO NAME"
  end
end

User.new
#→ #<User id: nil, name: "NO NAME", age: nil, job_id: nil, created_at: nil, updated_at: nil>

これだけなら問題はなさそうなのですが、集計関数を利用したクエリを発行した際に問題が起こりました。
職業別の平均年齢を取得するようなクエリを実行して値を取り出そうとすると以下のようなエラーが出ました。

User.select("AVG(age) AS avg_age").group(:job_id)
ActiveModel::MissingAttributeError: missing attribute: name

検索結果のオブジェクトにはname が含まれていないためafter_initialize の処理がエラーになっているようです。
今回の例ではRailsの集計関数メソッドを利用すれば問題ありませんが

User.group(:job_id).average(:age)
#→ #<BigDecimal:7fd75b9e52e8,'0.15E2',9(45)>

selectメソッドで取得するcolumnを指定する際にname を除外することができないというのは色々と不便そうです。

対策

対策としては
・selectメソッドを使う場合は常に初期化処理を行うcolumnも指定する
・集計関数を利用する場合はRailsのメソッドを利用する
・初期値が必要な場合はmigrationで指定する
・initializeメソッドを直接オーバーライドする
・Form Classを作る
・after_initializeを使わずに別のところで処理を行う
などがあると思います。
全てに適応可能な初期値を設定したいだけであれば、migration でdefault を指定するのが一番正しい気がします。
特定のform だけ初期値をセットしたい、それぞれのform で別々の初期値を設定したいというような場合はForm Class を使うといいのでしょうか。
Form Classについては森さんの記事を参考にして下さい。


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。