環境
- Rails 8.x
データベースに記述する
そもそもRails側でやらなくても、データベースにデフォルト値をセットする機能が備わっているのでそっちを使う。
マイグレーションで書くとこんな感じ
create_table :posts do |t| t.string :name, default: "nanashi" t.timestamps end
試してみる。しっかりとデフォルト値が設定されているのがわかる。
Post.new.name => "nanashi"
メリット
- データベース側で保証されるため安全で確実
- アプリケーションのロジックに依存しない
- パフォーマンスへの影響が少ない
デメリット
- 動的な値を設定できない
- マイグレーションが必要
- 既存レコードへの影響を考慮する必要がある
attributeを使う
Rails5からActiveRecordにattributes APIというのが追加された。
これを使うとモデル内にデフォルト値をセットできる。
class Post < ApplicationRecord attribute :name, :string, default: 'nanashi' end
試してみる
Post.new.name => "nanashi"
詳しいドキュメントは以下
ちなみにデフォルト値を現在時刻にしたい場合、default: Time.current
としてはいけない。
Time.current(Time.nowも)はオブジェクトが初期化される時点での時刻で固定されてしまうため、attributeのデフォルト値として使用すると意図した動作にならない。 代わりに遅延評価されるラムダ式を使用し、Time.currentを呼び出すことで、オブジェクトが生成される都度、その時点での現在時刻を取得できる。
# NG class Screenshot < ApplicationRecord attribute :ts, default: Time.current end # OK class Screenshot < ApplicationRecord attribute :ts, default: -> { Time.current } end
メリット
- モデル内で完結するためコードの見通しが良い
- 動的な値を設定可能
- 型変換も同時に行える
デメリット
- アプリケーションメモリを消費する
- データベース側では保証されない
- Rails 5以降でしか使用できない
after_initializeを使う
RailsのActiveRecordにはいくつもコールバックが用意されていて、after_initialize()でデフォルト値を入れてしまおうというやり方である。
class Post < ApplicationRecord after_initialize :set_default, if: :new_record? private def set_default self.name ||= "nanashi" end end
なぜ new_record?
が必要かというと、なにもafter_initializeが走るのはモデルで新しいレコードのインスタンスを生成したときだけではなく、Post.all
とかで既存のDBから取得したときも当然インスタンスがレコード分だけ生成されるのでafter_initalizeは走ってしまうからである。
ちなみに他の before_validation
とか before_save
のコールバックでデフォルト値をいれようとするのは禁じ手である
メリット
- 複雑な条件分岐による値設定が可能
- 他のモデルの状態に応じた値設定ができる
- 既存の値を上書きしない制御が容易
デメリット
- コールバックの実行順序を意識する必要がある
- パフォーマンスへの影響が大きい
- コードが複雑になりやすい
バリデーション時にデフォルト値を設定
やってはいけない。