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

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

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

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

Ruby on Rails 6

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

HTML5

HTML5 (Hyper Text Markup Language、バージョン 5)は、マークアップ言語であるHTMLの第5版です。

Q&A

1回答

859閲覧

Railsのフォームの要素が増殖してしまう問題を解決したい

MH00214

総合スコア53

Ruby

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

Ruby on Rails 6

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

HTML5

HTML5 (Hyper Text Markup Language、バージョン 5)は、マークアップ言語であるHTMLの第5版です。

1グッド

1クリップ

投稿2022/08/12 01:38

編集2022/08/12 02:21

前提

  • 親モデルを登録した際に子モデルを一括登録するフォームを作っています
  • slimを使っています
  • Rails6系を使っています

現在Railsで親モデルのデータを1つ登録した際に、子モデルのデータを複数一括登録する機能を作っています。
子モデルの一括登録そのものはできたのですが、登録に失敗した時に新規登録ページを再描画する場面で不具合が起きています。

具体的には

  • 新規登録の際(/logs/newへのアクセス)の際には正しく選択肢が表示される

イメージ説明

  • ↑の状態で何も選択しなかったり、日付や質問への答えを入力せずに登録するボタンを押すとエラーの処理が走るが、その際に再度/logs/newをレンダリングする動きがある。おそらくそこの動作のせいで、質問の後ろにあるはい、いいえの選択肢が増殖してしまう。(繰り返し増殖できてしまう)

イメージ説明

実現したいこと

  • 何も選択しなかったり、日付や質問への答えを入力せずに登録するボタンを押しても要素が増殖しないようにしたい

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

エラーメッセージは特にないですが、おそらく、下記のコードの @log.answers.build のbuildが複数回走ってしまっているのが原因かと思います...

該当のソースコード

ruby

1 def new 2 @user = current_user 3 @log = Log.new 4 @questions = Question.order(:id).where(is_active: true) 5 @log.answers.build 6 end 7 8 def create 9 @user = current_user 10 @log = Log.new(log_params) 11 @questions = Question.order(:id).where(is_active: true) 12 13 begin 14 @log.save! if @log.present? 15 flash[:success] = '作成しました' 16 redirect_to logs_path 17 rescue StandardError => e 18 logger.error(e) 19 flash[:danger] = @log.errors.full_messages 20 render :new 21 end 22 end

slim

1= form_with(model: log, url: url, local: true) do |f| 2 = f.hidden_field :user_id, value: user.id 3 / とりあえず固定値を渡す 4 = f.hidden_field :score, value: 40 5 .form-item 6 = f.label :registered_on, '日付' 7 = f.date_field :registered_on 8 .form-item 9 - questions.each do |question| 10 = "#{question.content}" 11 = f.fields_for :answers do |answer| 12 = answer.hidden_field :question_id, value: question.id 13 = answer.label :is_good_habit, 'はい' 14 = answer.radio_button :is_good_habit, true 15 = answer.label :is_good_habit, 'いいえ' 16 = answer.radio_button :is_good_habit, false 17 = f.submit '登録する'

試したこと

  • view側でparamsなどを見て選択肢部分の表示をしないようにした(しかし、newページにアクセスした時も消えてしまうのでダメ)
  • controller側で @log.answers.build の実行を paramsなどを見て行わないようにした(しかし上と同じ問題が起きてダメ)
shinoharat👍を押しています

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

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

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

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

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

shinoharat

2022/08/18 00:56

下記5点を質問文に追加していただけないでしょうか? ・app/models/log.rb ・app/models/question.rb ・app/models/answer.rb ・db/schema.rb の logs, questions, answers 部分 ・「log_params」の実装内容
guest

回答1

0

バグの原因について

エラーメッセージは特にないですが、おそらく、下記のコードの @log.answers.build のbuildが複数回走ってしまっているのが原因かと思います...

いえ、原因はそこではありません。
まずは、この現象が起きている理由について解説させてください。

create アクションで保存に失敗した場合、以下のコードが実行されます。

rb

1 rescue StandardError => e 2 ... 3 render :new 4 end

「render :new」は、現在のインスタンス変数の状態のままビューを描画します。
(new アクションを再度実行したりはしません)
ですので、create 失敗時に「@log.answers.build」が実行されることはありません。

--

では、なぜフォームが増殖してしまうのでしょうか?

原因は view の構造にあります。

slim

1= form_with(model: log, url: url, local: true) do |f| 2 ... 3 .form-item 4 - questions.each do |question| <----------------------- ここと 5 = "#{question.content}" 6 = f.fields_for :answers do |answer| <---------------- ここに注目 7 ...

f.fields_for :answers@log.answers の要素の数だけフォームを生成します。
それを questions.each で囲っていますので、この書き方だと 『一つの質問につき、すべての @log.answers の数だけフォームを生成』 という動作になります。

初期表示では、まず new アクションの

rb

1 def new 2 ... 3 @log.answers.build 4 end

が実行されるため、 @log.answers.count は1です。
そのため、『一つの質問につき、一つの回答フォーム』となり、一見うまく動作しているように見えます。

しかし、create に失敗したときはどうでしょうか?
登録ボタン押下時、フォームからは「質問1」と「質問2」の2つの回答情報が送信されます。
それを保存しようとするので、当然 @log.answers.count は2となります。(保存に失敗しても、DBに反映されないだけで、データは変数にセットされます)
そのまま view を描画すれば、『一つの質問につき、二つの回答フォーム』となり、今回のような現象が発生してしまいます。

修正方法

長くなってすみません。
ここからは修正方法について解説します。

まず、view から questions.each は取り除いてしまいます。
そして、質問文は「answer に紐づく question」から表示するようにします。
(後述しますが、 controller 側で answer と question の紐づけを行っています)

diff

1### view ### 2 3 .form-item 4- - questions.each do |question| 5- = "#{question.content}" 6 7 = f.fields_for :answers do |answer| 8+ / ここでの answer は「フォーム変数」であり、Answer モデルのインスタンスではないので注意。 9+ / 「フォーム変数.object」で紐づくモデルインスタンスを取得できる。 10+ - answer_model = answer.object 11 12+ / answer(モデル) に紐づく question から質問文を表示 13+ = "#{answer_model.question.content}" 14 15+ / question_id は controller で設定済み(後述)のため、value の記述が不要となる 16- = answer.hidden_field :question_id, value: question.id 17+ = answer.hidden_field :question_id 18 19 = answer.label :is_good_habit, 'はい' 20 = answer.radio_button :is_good_habit, true 21 = answer.label :is_good_habit, 'いいえ' 22 = answer.radio_button :is_good_habit, false 23 = f.submit '登録する'

次に、controller の new アクションを変更します。
answer を一つだけビルドするのではなく、question の数だけビルドするようにします。
view で質問文が表示できるよう、 question_id も設定します。

diff

1### controller ### 2 3 def new 4 @user = current_user 5 @log = Log.new 6- @questions = Question.order(:id).where(is_active: true) 7- @log.answers.build 8+ # 質問の数だけ answer をビルドする 9+ questions = Question.order(:id).where(is_active: true) 10+ questions.each do |question| 11+ @log.answers.build(question_id: question.id) 12+ end 13 end

最後に、「answer に紐づく question」にアクセスするためのアソシエーションを追加します。
(既に定義済みならこの修正は不要です。)

diff

1### model ### 2 3 class Answer < ApplicationRecord 4+ belongs_to :question # 無いなら追加 5 ... 6 end

以上です。
何かわからないことがあれば、またコメントください🙏

投稿2022/08/24 08:21

shinoharat

総合スコア1685

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.30%

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

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

質問する

関連した質問