おつかれさまです!のぶじゃすです。
最近よくRails7でコードを書いています。modelsを作っていく中でどうしても多対多のモデルになることがあります。
多対多のリレーションをするときには中間モデル(join モデル)を作る必要があるのですが、create時に中間モデルも一緒にcreateする必要があります。自動でcreateされると思っていたのですが、されなくて困りました。色々試して解決できた&あまり明確に書いてある記事が見当たらなかったので備忘録的にBLOG書いておきます。
- 今回使用したバージョン
- うさぎと親が多対多
- has_many :through関連付け
- 元々のうさぎ新規作成機能
- 間違った方法
- 正しい方法
- 最終的な controller は current_user.save にしました
- アクセスありがとうございます🙇♂️
今回使用したバージョン
- Ruby 3.0.3
- Rails 7.0.0
うさぎと親が多対多
今回私が作ったのはこんな感じのモデルです。簡単に説明すると、1人で複数のうさぎを飼育できるし、1匹のうさぎを複数人で飼育することも可能です。
まあそうですよね。1匹飼ったらもう1匹飼いたくなるのが人情ですよね。
models/rabbit.rb
class Rabbit < ApplicationRecord has_many :breeds, dependent: :destroy has_many :users, through: :breeds end
models/breed.rb
class Breed < ApplicationRecord belongs_to :rabbit belongs_to :user end
models/user.rb
class User < ApplicationRecord has_many :breeds, dependent: :destroy has_many :rabbits, through: :breeds end
has_many :through関連付け
今回は has_and_belongs_to_many
ではなくて、 has_many :through
の方法でリレーションシップしています。
元々のうさぎ新規作成機能
controllers/rabbits_controller.rb#create
# POST /rabbits or /rabbits.json def create @rabbit = Rabbit.new(rabbit_params) respond_to do |format| if @rabbit.save format.html { redirect_to rabbit_url(@rabbit), notice: 'Rabbit was successfully created.' } format.json { render :show, status: :created, location: @rabbit } else format.html { render :new, status: :unprocessable_entity } format.json { render json: @rabbit.errors, status: :unprocessable_entity } end end end
scaffoldのままなので説明はあまりいらないですかね。 new して save する。それだけです。
間違った方法
私はこれでうまくいくと思っていました。しかしこれだと中間オブジェクトが自動で保存されず、うまくいきません。
irb(main):005:0> rabbit = current_user.rabbits.new name: 'test', birthday: '2021-01-01' => #<Rabbit:0x00007f1f3cb30120 id: nil, name: "test", birthday: Fri, 01 Jan 2021> irb(main):006:0> rabbit.save TRANSACTION (1.1ms) BEGIN Rabbit Create (0.7ms) INSERT INTO "rabbits" ("name", "birthday") VALUES ($1, $2) RETURNING "id" [["name", "test"], ["birthday", "2021-01-01"]] TRANSACTION (1.4ms) COMMIT => true
うさぎしか作成されないので、 current_user.rabbits
で参照できません。
正しい方法
rabbit.save
ではなく current_user.save
をすると中間オブジェクトも一緒に保存されます。うまくいきました。
irb(main):008:0> rabbit = current_user.rabbits.new name: 'test', birthday: '2021-01-01' => #<Rabbit:0x00007f1f3d06cd28 id: nil, name: "test", birthday: Fri, 01 Jan 2021> irb(main):009:0> current_user.save TRANSACTION (0.9ms) BEGIN Rabbit Create (0.9ms) INSERT INTO "rabbits" ("name", "birthday") VALUES ($1, $2) RETURNING "id" [["name", "test"], ["birthday", "2021-01-01"]] Breed Create (0.9ms) INSERT INTO "breeds" ("rabbit_id", "user_id") VALUES ($1, $2) RETURNING "id" [["rabbit_id", 11], ["user_id", 2]] TRANSACTION (1.4ms) COMMIT => true
これで正しくリレーションされるので current_user.rabbits
で参照できるようになります。
最終的な controller は current_user.save
にしました
controllers/rabbits_controller.rb#create
ここまで読んでくれた貴方はもうわかっていると思いますが、結論はこんな感じになりました
# POST /rabbits or /rabbits.json def create @rabbit = current_user.rabbits.new(rabbit_params) respond_to do |format| if current_user.save format.html { redirect_to rabbit_url(@rabbit), notice: 'Rabbit was successfully created.' } format.json { render :show, status: :created, location: @rabbit } else format.html { render :new, status: :unprocessable_entity } format.json { render json: @rabbit.errors, status: :unprocessable_entity } end end end
しかしまだ、なぜこうなるのかちゃんと説明できてないので、ActiveRecordのコードを読んでおこうと思います。
アクセスありがとうございます🙇♂️
ここまで読んでいただき誠にありがとうございました。もしこの記事が役に立ったらはてブや、Twitterのフォローしていただけると大変喜びます😊