ActiveRecord でテーブルに定義されていない仮想的な属性を定義する
ActiveRecord を継承したあるモデルに一時的なパラメータを持たせたかったので調べたことを備忘録として残します
結論
- 型やデフォルト値を持たせたいなら
attribute
をつかう - そうでないなら
attr_accessor
をつかう
やりたいこと
あるモデル、Post モデルとします。ID を指定すると指定のモデルを引っ張ってくる action を実装していたとします。
ユーザーが like した投稿 ID は PostLike テーブルに保存されています。
Post
ID | post_name | content | created_at | updated_at |
---|---|---|---|---|
1 | post_1 | Hello | 2024/06/05 12:05 | 2024/06/05 12:05 |
2 | post_2 | arigato | 2024/06/05 12:12 | 2024/06/05 12:12 |
3 | post_3 | Thanks | 2024/06/05 13:45 | 2024/06/05 13:45 |
PostLike
ID | post_id | user_id | liked_at | created_at | updated_at |
---|---|---|---|---|---|
1 | 1 | 1 | 2024/06/05 13:30 | 2024/06/05 13:30 | 2024/06/05 13:30 |
2 | 1 | 2 | 2024/06/05 14:00 | 2024/06/05 14:00 | 2024/06/05 14:00 |
3 | 2 | 1 | 2024/06/06 15:24 | 2024/06/06 15:24 | 2024/06/06 15:24 |
このとき、Post の ID と呼び出し元のユーザーを指定したら、そのユーザーが ID を like したかどうかの情報も返したいです。
liked_at 属性をとるために二つのテーブルを毎回 JOIN するのは WHERE user_id = ? で取得するよりコストが高い気がするのと、serializer にメソッドを作るのもちょっとめんどうでできるのならば Post をそのまま返したいなと考えていました。
とはいえそのために使いもしない liked_at フィールドを Post テーブル上に追加するのはナンセンスだと感じたので、ActiveRecord の一部として使えてなおかつテーブル上に追加されない仮想的な属性を作ることができないか調べました。
attribute を設定する
ActiveRecord を継承したモデルに attribute で属性を設定することができます。
class Post < ActiveRecord::Base
has_many :rpost_likes, dependent: :destroy
attribute :liked_at, :datetime, default: -> { Time.now }
end
ActiveRecord の他のフィールドのように、型や初期値を設定することができるほか、自分でカスタマイズした型を設定することもできます。
ActiveRecord の他のフィールドと同じようにふるまうので、ActiveRecord の機能を活用したい場合によさそうです。
attr_accessor を設定する
単純な getter/setter のみが定義された attr_accessor で仮想的な属性を設定することもできます。
class Post < ActiveRecord::Base
has_many :rpost_likes, dependent: :destroy
attr_accessor :liked_at
end
ActiveRecord とは関係ない Ruby の機能なので、型指定や初期値の指定などは提供されていませんが、単純な値を保持する目的ならこちらで十分かもしれません。
参考
Discussion