LoginSignup
2
1

Rails:外部キーのnilを許可する

Last updated at Posted at 2024-05-22

今回は外部キーの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つあります。

  1. allow_nilオプション
  2. 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

おわり

長くなりましたが、最後までお付き合いいただきありがとうございました。
ご指摘あればお待ちしております。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1