📘
ActiveRecordを単独で使うとbelongs_toが参照先の存在を検証しない
tl;dr
ActiveRecordを単体で(Railsなしで)使うときは、belongs_toはデフォルトでoptional: true相当の挙動をします。
Rails同様にoptional: falseをデフォルトにするにはActiveRecord::Base.belongs_to_required_by_default = trueの設定が必要です。
Railsにおけるbelongs_to
RailsでActiveRecordを使う際、belongs_toは参照先のレコードが存在することを検証します。[Railsガイド]
例えば次のようなテーブルが存在するとします。
ActiveRecord::Schema.define do
create_table :shops do |t|
end
create_table :items do |t|
t.references :shop, null: false, foreign_key: true
end
end
class Shop < ActiveRecord::Base
end
class Item < ActiveRecord::Base
belongs_to :shop
end
このとき、Item#shopがnilであるならばItemはinvalidになります。
p Item.new.valid? #=> false
この検証をやめるには、belongs_toにoptional: trueというオプションを設定します。
ActiveRecord単体におけるbelongs_to
ところが、ActiveRecordをRailsなしで使うと、この検証は行われません。
require "bundler/inline"
gemfile do
source "https://rubygems.org"
gem "activerecord"
gem "sqlite3"
end
require "active_record"
require "sqlite3"
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Schema.define do
create_table :shops do |t|
end
create_table :items do |t|
t.references :shop, null: false, foreign_key: true
end
end
class Shop < ActiveRecord::Base
end
class Item < ActiveRecord::Base
belongs_to :shop
end
p Item.new.valid? #=> true
上のコードのように、Item#shopがnilであってもItemはvalidになります。
原因と対策
これは、belongs_toでoptional: falseがデフォルトとなっているのは、ActiveRecordのデフォルトではなくRailsが設定している動作のためです。
従って、ActiveRecordを単体で使う際にoptional: falseをデフォルトとするには、ActiveRecord::Base.belongs_to_required_by_default = trueを書いておく必要があります。
require "bundler/inline"
gemfile do
source "https://rubygems.org"
gem "activerecord"
gem "sqlite3"
end
require "active_record"
require "sqlite3"
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Schema.define do
create_table :shops do |t|
end
create_table :items do |t|
t.references :shop, null: false, foreign_key: true
end
end
###################
# ↓これを追加
###################
ActiveRecord::Base.belongs_to_required_by_default = true
class Shop < ActiveRecord::Base
end
class Item < ActiveRecord::Base
belongs_to :shop
end
p Item.new.valid? #=> false
Discussion