【Rails】accepts_nested_attributes_forを使用しない方が良い理由とその代替方法
「Rails 子モデル 保存」 とかでググるとたくさんのaccepts_nested_attributes_forを使用した記事をお見かけすると思います。
そんなaccepts_nested_attributes_forですが、いま自分が働いている会社では使用することを禁じられており、なるべくクリーンな実装を心がけるということを日々意識しております。
そんなこともあり、改めて使用してはいけないものの正体を調べてみました。
accepts_nested_attributes_forとは何か
一言でいえば、「親モデルに紐づくレコードの保存を簡単にできるようにするためのもの」 です。
具体例で見てみましょう。
下記に2つモデルがあり、親モデルであるAuthorの作成と同時にそれに紐づくBookモデルのレコードも作成するというイメージです。
- Author(著者)
- Book(本)
# 著者モデル
class Author < ActiveRecord::Base
has_many :books
accepts_nested_attributes_for :books
end
# フォームから送信するパラメータ
params = { name: {
name: '礼流図 太郎', books_attributes: [
{ title: 'おいしいジュースのつくりかた' }
]
}}
# コントローラ内で行う処理
def create
Author.create(params_permitted)
end
def params_permitted
params.require(:author).permit(:name, books_attributes: [:title])
end
accepts_nested_attributes_for :books
を定義することで、
Authorモデル内にdef books_attributes=(attributes)
なセッターメソッドが生え、
フォームから送られてくるパラメータのbooks_attributes
によりセッターメソッドが呼ばれることで、Author.create()
でBookモデルのレコードも同時に作成されるというカラクリですね。めちゃ便利!
なぜ使用しない方がよいのか
調べたところ、いくつか理由はありそうですが、主に下記の2つの理由からなのではと思いました。
- ActiveRecordから広げて素のRubyなどでいろいろやりたいってなったときに使用できないため、破綻への道を進む
- accepts_nested_attributes_for を使った入れ子のformが罠だらけなので、formの実装が泥臭くなりがち
実際に破綻への道を辿ったわけではないので、具体的な悪例は見つかりませんでした。他にもこんな理由で使用しないほうが良いなど知っている方いたら教えていただけると嬉しいです。
ちなみにRails生みの親も「killしたいよ」と言っていたりするで使用しないほうがベターなのでしょう。
使用しないための代替案
Formオブジェクトを活用する
ポイントとしてはFormクラス内で、
- booksというattributeを定義すること
- Form内でparamsメソッドを定義して、コントローラではform.paramsでいい感じのパラメータを取得する
form.params = { name: "礼流図 太郎", books: { title: "おいしいジュースのつくりかた"} }
- Authorモデル内でbooksも一緒にビルドしちゃう
# 著者モデル
class Author < ActiveRecord::Base
has_many :books
class << self
def create!(params)
author = new(name: params[:name])
author.books.build(params[:books])
author.save!
author
end
end
end
# フォームから送信するパラメータ
params = { name: {
name: '礼流図 太郎', books: [{ title: 'おいしいジュースのつくりかた' }]
}}
# コントローラ内で行う処理
def create
form = AuthorRegistrationForm.new(params_permitted)
Author.create!(form.params)
end
def params_permitted
params.require(:author).permit(:name, books: [:title])
end
# Form内
class AuthorRegistrationForm
include ActiveModel::Model
include ActiveModel::Attributes
attribute :name, :string
attribute :books
def params
attributes.deep_symbolize_keys
end
end
おわりに
脱accepts_nested_attributes_for!!
ありがとうございました。
Discussion