ActiveRecord を単体で使う場合と違って、Rails 5 と組み合わせた場合 belongs_to の指すインスタンスが必須になり、この変更によって1対多のリレーションで1の方を保存する前に多の方のインスタンスを作って1の方の保存のタイミングで連動して多の方も保存するみたいな、これまで普通に通っていた処理がひっかかるようになります。
require "active_record"
ActiveRecord::VERSION::STRING # => "5.1.4"
# Rails 5 以上と一緒に使う場合は belongs_to の required: true を最初から有効にされる
ActiveRecord::Base.belongs_to_required_by_default = true
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Migration.verbose = false
ActiveRecord::Schema.define do
create_table :articles do |t|
end
create_table :comments do |t|
t.belongs_to :commentable, polymorphic: true, null: false
end
end
class Article < ActiveRecord::Base
has_many :comments, as: :commentable
end
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
end
article = Article.new
article.comments.build
article.save! rescue $! # => #<ActiveRecord::RecordInvalid: Validation failed: Comments is invalid>
そこで、可能なら Article.create!
して親の方を先に作っておくとか、ActiveRecord::Base.belongs_to_required_by_default = false
して設定を無効にするとか、次のように required: false
をつけてもいいですが、
class Article < ActiveRecord::Base
has_many :comments, as: :commentable
end
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true, required: false
end
article = Article.new
article.comments.build
article.save! # => true
inverse_of オプションを使って Article から見た comments たちに commentable が欲しいときはDBから探すのではなく commentable メソッドを見てくれと伝えます。これで通るようになります。また副作用というか、こっちが本来の目的かもしれませんが無駄なSQLを減らせます。
class Article < ActiveRecord::Base
has_many :comments, as: :commentable, inverse_of: :commentable
end
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
end
article = Article.new
article.comments.build
article.save! # => true
もし混乱してきたら子の belongs_to :xxx の :xxx の部分を親の inverse_of に指定すると覚えておくと楽です。
参考
[rails] accepts_nested_attributes_for の場合は、inverse_of を付けておくと良い気がする
https://qiita.com/ryoff/items/e3ba4b8c8be117c79b73
※こちらの記事では polymorphic では動作しないと言及がありますが今では動くようになっていました