今回は外部キーのnilを許可する設定にする際、「モデルファイルも設定つけて」というレビューをもらったので、調べたことを深堀りしてみました。
作成したマイグレーションファイルはこちら
class CreatePortfolios < ActiveRecord::Migration[7.1]
def change
create_table :portfolios do |t|
t.references :user, null: false, foreign_key: true
t.references :organization, foreign_key: true
t.string :name, limit: 50, null: false
t.string :url, limit: 255, null: false
t.text :introduction
t.integer :unhealthy_cnt, default: 0, limit: 1
t.datetime :latest_health_check_time
t.timestamps
end
add_index :portfolios, :url, unique: true
end
end
モデルファイル
class Portfolio < ApplicationRecord
belongs_to :user
belongs_to :organization
validates :name, presence: true, length: { maximum: 50 }
validates :url, presence: true, length: { maximum: 255 }, uniqueness: true
validates :unhealthy_cnt, numericality: { only_integer: true, less_than_or_equal_to: 4 }
before_destroy :check_user_dependency
private
def check_user_dependency
throw(:abort) if organization.present?
end
end
今回の指摘はbelongs_to :organization
この部分です。
環境
Next.js 14.1.4
Rails 7.1.3
Ruby 3.2.3
Docker
構成
ユーザーの作成したPortfolio(アプリ)にはoganization(組織)をもたせることができる。
アプリは個人開発したものもあるので、必ずしもoganizationに紐付くわけではない。
この場合、nilを許可する制約を付ける方法は2つあります。
- allow_nilオプション
- optional: trueオプション
allow_nilとは
allow_nil
オプションは、バリデーションの一部として使用され、特定のフィールドがnilであることを許可する場合に用います。例えば、以下のように使用します
class Article < ApplicationRecord
belongs_to :author
validates :title, presence: true, allow_nil: true
end
この例では、titleフィールドに対してpresenceバリデーションが適用されていますが、nilの値は許可されています。つまり、titleがnilの場合はバリデーションがスキップされ、それ以外の場合はpresenceバリデーションが適用されます。
optional: trueとは
optional: trueオプションは、Active Recordの関連(association)で使用され、関連付けられたオブジェクトが存在しないことを許可します。特に、belongs_to関連に対して使用されます。
class Article < ApplicationRecord
belongs_to :author, optional: true
end
この例では、authorとの関連付けがオプションであり、articleレコードがauthor_idを持たない(nilである)ことを許可します。これは、ArticleがAuthorに必ずしも属する必要がないことを意味します。
違いのまとめ
- 1.用途
- •allow_nil: バリデーションの一部として使用され、特定のフィールドがnilであることを許可します。
- •optional: true: Active Recordの関連に対して使用され、関連付けられたオブジェクトが存在しないことを許可します。
- 2.対象
- •allow_nil: モデルの特定の属性(フィールド)。
- •optional: true: belongs_to関連の外部キー。
- 3.適用される場面
- •allow_nil: 属性に対するバリデーションの際に使用。
- •optional: true: 関連付けが必須でないことを示すために使用。
どちらがより適切か
外部キーがnilでも良い場合にはoptional: true
を使用し、特定の条件で外部キーのバリデーションを柔軟に制御したい場合allow_nil
オプションを使用するのがいいみたい。
今回の例ではoptional: true
を使用することにしました。
修正したコード
belongs_to :organization, optional: true
追加で調べたこと
optional: true
オプションについて、Railsガイドではこの様になっています。
4.1.2.11 :optional
:optionalオプションにtrueを指定すると、関連付けられるオブジェクトが存在することが保証されなくなります。このオプションは、デフォルトではfalseです。
どうやらこのオプションは、nilを許可するのではなく、関連づいていない状態を許容するというニュアンスらしい。
このニュアンスについて面白い記事を見つけました。
Hogeテーブルのモデルを作ると下記のようになります。
nilを許可するためにoptional: trueをつけています。class Hoge < ApplicationRecord belongs_to :parent, class_name: 'Hoge', optional: true end
ただ、optionalの場合はHogesテーブルに存在しないidをparent_idに入れた場合もバリデーションはOKになってしまいます。
記事によると、:optionalオプションは「関連付けがなくても構いませんよ」というだけで、idが存在するかどうかはチェックしてくれないので存在しない外部キーのidが入力された場合も登録できてしまうということのようです。
それを防ぐために、記事では下記のような実装が紹介されています。
class Hoge < ApplicationRecord
belongs_to :parent, class_name: 'Hoge', optional: true
validates :parent, presence: true, if: :parent_id?
end
これだと、allow_nil:オプションを使用したときと変わらないような気がするので、最初から validates :parent, presence: true, allow_nil: true
だけで良いような…
おそらくこれは、整合性を保つために行った実装かな?と思いますが、通常はoptional: trueだけで十分みたいです。
通常はoptional: trueだけで十分です。allow_nilを使用する場合は、特定のバリデーションロジックが必要な場合に限られます。 GPT
おわり
長くなりましたが、最後までお付き合いいただきありがとうございました。
ご指摘あればお待ちしております。