質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

Ruby on Rails

Ruby on Railsは、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

データベース設計

データベース設計はデータベースの論理的や物理的な部分を特定する工程です。

Q&A

解決済

1回答

3084閲覧

Railsの多対多のモデルにおいて、中間テーブルの追加カラムのデータまで一括保存したいです

motoyu5623

総合スコア1

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

Ruby on Rails

Ruby on Railsは、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

データベース設計

データベース設計はデータベースの論理的や物理的な部分を特定する工程です。

0グッド

0クリップ

投稿2020/07/17 08:38

前提・実現したいこと

個人でRailsのWEBアプリ作成中のプログラミング初学者です。

仕事に関するレビューサイトを作ろうとしていて、下図のようなモデルを組んでいるのですが、データベースへの新規登録が上手くいかないので、ご助言いただけますと幸いです。

イメージ説明

前提

  • カレントユーザーは複数の仕事(Job)を登録できる
  • 各Jobは、仕事に必要なSkillを5個持つ
  • 一度登録されたSkillは、他のJobでも使い回せる(即ちJobとSkillは多対多の関係で、SkillはToxi法におけるタグのようなイメージ)
  • 中間テーブルであるJobSkillには、scoreという追加カラムを用意する(あるJobに対して、そのSkillの重要度を示す)
  • Job登録画面で、Jobの情報、Skillの名前、JobSkillのscoreを同時に登録できるようにしたい(←scoreができなくて詰まっています)

発生している問題・エラーメッセージ

Job登録フォームからJob、Skill、JobSkillの3モデルの情報を入力すると、Jobと5つのskillは登録されるものの、それに紐づくscoreが登録されていない「ように」見えます。

「ように」と表現したのは、エラーこそ発生しないものの、実際には以下の9行目と11行目に示すように、JobSkillテーブルへの不完全な登録が2回行われています。
各INSERTの内容は、

①1回目のINSERTは、scoreは入っているが、3つのモデルの紐付けができていない
②2回目のINSERTは、3つのモデルの紐付けはできているが、scoreが入っていない

となっているので、showページ等で取り出せるのは当然②となり、scoreが登録されていないのと実質的に同じ状況ということです。

1 sidebiz_1 | (0.3ms) BEGIN 2 sidebiz_1 | ↳ app/controllers/jobs_controller.rb:25:in `create' 3 sidebiz_1 | User Load (27.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 5], ["LIMIT", 1]] 4 sidebiz_1 | ↳ app/controllers/jobs_controller.rb:25:in `create' 5 sidebiz_1 | Job Create (6.2ms) INSERT INTO "jobs" ("name", "work_type", "section", "industry", "medium", "occupation", "started_at", "ended_at", "worktime_week", "description", "pulled_skill", "returned_skill", "user_id", "created_at", "updated_at", "company") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) RETURNING "id" [["name", "1357"], ["work_type", ""], ["section", ""], ["industry", ""], ["medium", ""], ["occupation", ""], ["started_at", "2016-01-01"], ["ended_at", "2016-01-01"], ["worktime_week", 1], ["description", "a"], ["pulled_skill", ""], ["returned_skill", ""], ["user_id", 5], ["created_at", "2020-07-17 04:57:42.920242"], ["updated_at", "2020-07-17 04:57:42.920242"], ["company", ""]] 6 sidebiz_1 | ↳ app/controllers/jobs_controller.rb:25:in `create' 7 sidebiz_1 | Skill Create (6.8ms) INSERT INTO "skills" ("name", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["name", "5734"], ["created_at", "2020-07-17 04:57:42.934796"], ["updated_at", "2020-07-17 04:57:42.934796"]] 8 sidebiz_1 | ↳ app/controllers/jobs_controller.rb:25:in `create' 9 sidebiz_1 | JobSkill Create (7.7ms) INSERT INTO "job_skills" ("skill_id", "score", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["skill_id", 51], ["score", 2], ["created_at", "2020-07-17 04:57:42.952086"], ["updated_at", "2020-07-17 04:57:42.952086"]] 10 sidebiz_1 | ↳ app/controllers/jobs_controller.rb:25:in `create' 11 sidebiz_1 | JobSkill Create (13.5ms) INSERT INTO "job_skills" ("job_id", "skill_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["job_id", 56], ["skill_id", 51], ["created_at", "2020-07-17 04:57:42.969098"], ["updated_at", "2020-07-17 04:57:42.969098"]]** 12 sidebiz_1 | ↳ app/controllers/jobs_controller.rb:25:in `create' 13 sidebiz_1 | (5.5ms) COMMIT 14 sidebiz_1 | ↳ app/controllers/jobs_controller.rb:25:in `create' 15 sidebiz_1 | Redirected to http://localhost:3010/jobs/56 16 sidebiz_1 | Completed 302 Found in 177ms (ActiveRecord: 67.9ms | Allocations: 13690)

該当のソースコード

models/job.rb

class Job < ApplicationRecord belongs_to :user has_many :job_skills, dependent: :destroy has_many :skills, through: :job_skills accepts_nested_attributes_for :skills, allow_destroy: true accepts_nested_attributes_for :job_skills, allow_destroy: true

models/skill.rb

class Skill < ApplicationRecord has_many :job_skills, dependent: :destroy has_many :jobs, through: :job_skills accepts_nested_attributes_for :job_skills end

models/job_skills.rb(JobとSkillの中間テーブル)

class JobSkill < ApplicationRecord belongs_to :job, optional: true, autosave: true belongs_to :skill, optional: true, autosave: true end

controllers/jobs_controller.rb

class JobsController < ApplicationController before_action :authenticate_user!, except: %i[index show] def new @job = current_user.jobs.build 5.times {@job.skills.build} end def create @job = current_user.jobs.build(job_params) if @job.save flash[:success] = 'job registered!' redirect_to job_path(@job) else render :new end end private def job_params params.require(:job).permit(:name, :user_id, skills_attributes: [:name, job_skills_attributes: [:score, :job_id]]) end end

views/jobs/new.html.erb

<%= form_with(model: @job, local:true) do |f| %> ・ ・ (Jobの色々な情報の入力フォーム) ・ ・ <div class="field"> <%= f.fields_for :skills do |skill_field| %> <div> <%= skill_field.label :name %> <%= skill_field.text_field :name %> ★→→→ <%= skill_field.fields_for :job_skills, @job.job_skills.build do |score_field|%>   <%= score_field.label :score %>   <%= score_field.number_field :score %> <%= score_field.hidden_field :job_id, :value => @job.id %> <% end %> </div> <% end %> </div> <%= f.submit '登録する' %> <% end %>

試したこと

job入力フォームの(★)の行にて、job_skillsをbuildしているため、SQLの二重発行が起きていると認識しています。一回目のINSERTはこのbuildによるもので、二回目のINSERTはskillがsaveされたときにhas many through効果で自動で関連付けされてできるものだと思います。

そこで、★行の@job.job_skills.buildを消してみたところ、今度はscoreを入力するフォームが消えてしまいます。
jobに紐づくskillがsaveされていない段階では、job_skillsオブジェクトが出来ていないからだと思います。

ユーザビリティの観点から、できれば一つのページで3モデルすべての情報を登録できるようにしたいと考えています。
しかしググって出てくるのは、既に中間テーブルの両親が存在する状態で、中間テーブルの追加カラムを登録するといった方法ばかりで、解決の糸口が全くつかめていないという状況です。(userとpostのデータが既にある状態で、中間テーブルのcommentをcommentコントローラーからcreateする、など)

###質問まとめ
中間テーブルの両親(JobとSkill)がまだ出来ていない段階で、中間テーブルの追加カラム(score)まで一緒に新規保存する方法はありますでしょうか。それがそもそも難しい場合、他にベターなモデル設計はありますでしょうか
個人的には、JobとSkillの一意な組み合わせによってscoreが定まるはずなので、中間テーブルにscoreカラムを追加するのは正規化の観点からも悪くないと思っているのですが、もしそのあたりの認識から間違っていましたら、ご指摘のほどお願いいたします。

お忙しいところ最後までお読みいただきありがとうございました。
何かヒントになりそうな情報がございましたら、コメントいただけますと幸いです。
あるいは不足している情報等あれば、ご教示のほどお願いいたします。

補足情報(FW/ツールのバージョンなど)

ruby 2.7.1 Rails 6.0.3.2 docker-composeでrubyコンテナとpostgresコンテナを立てて開発しています。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

モデルの設計はこれでよいと思いますが

  1. Skillは他のJobでも使い回せる
  2. 一つのページで3モデルすべての情報を登録できる

は矛盾します。使い回しをするなら、Skillは予めとうろくしてあるものから選ぶ、というUIになるのではないでしょうか。
3モデル同時登録ですと、同じnameのSkilが沢山できてしまい、結果 Job:Skillは1:多になります。

追記
まず logをみて paramsの構造を確認することをお薦めします。
jobをつくる、
params の Skill を拾って「なければ作る」
それに対応する job_skils を作る
というような手順でしょうか

投稿2020/07/17 13:50

編集2020/07/18 09:30
winterboum

総合スコア23329

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

motoyu5623

2020/07/17 15:47

ご回答ありがとうございます! 例えば、Job(id:10)を新規登録した時に入力したSkill(name:Excel)が、既にSkillテーブルにid =1, name=Excelとして登録されていた場合、Skillテーブルのレコードは増やさずに、Jobテーブルにid=10のレコードと、JobSkillテーブルにjob_id=10, skill_id=1のレコードが増えるというイメージを持っていました。 「無ければ作る」ということで、find_or_craete_byがそのイメージに近い動きをしてくれるかと想像していました(初めて触るので本当にできるかわかりませんが)。 使い回しという表現が誤解を招いてしまったかもしれず申し訳ございませんが、色んなJobに対するSkillを開発者側の私が予め洗い出してリスト化するのは難しいので、ユーザー側にどんどん登録してもらう想定をしておりました。 その中で、同じ名前のSkillが一つのテーブルに何回も出てくるのは効率的ではないため、Skill名の重複を許さない形(即ちJob:Skillが多対多)にする方向性で考えていました。 もしここまでで違和感、見落としている点などございましたら、ご指摘いただけますと幸いです。 今後の方向性 A. 上の多対多の考えがやはり成り立たない場合→1対多とする B. 上の多対多が一応実現できそうな場合→多対多か1対多のどちらかを選択 Bとなった場合、1対多はシンプルに書けると思うので、自分のスキルでの実現性、時間、DBの応答速度(名前重複の分Skillのレコード数が増える+Skill名からJobのレコードを取ってくる手段がidから名前検索になる)あたりを天秤にかけて、どちらを選択するか決めようと思います。 重ねてですが、貴重なご意見いただき、ありがとうございました。
winterboum

2020/07/18 09:26

「無ければ作る」 という考え方はあります。 その場合は jobs.build(job_params) で一気には作れません。 お書きになっている手順をコツコツ書くことになります。 > 使い回しという表現が誤解を招いてしまったかも いや、コメントに書かれたようなことを考えているのだろうな、ということはしっかりわかってました 笑
motoyu5623

2020/07/22 07:18

追記ありがとうございます! ネストの順番を「Job→Skill→Job_skill」から「Job→Job_skill→Skill」に変えたら3モデル同時登録は一応できましたが、これだと「無ければ作る」が難しいので、記載いただいた手順で考えてみます。 方向性を示していただき、ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問