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

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

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

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

Ruby on Rails

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

データベース

データベースとは、データの集合体を指します。また、そのデータの集合体の共用を可能にするシステムの意味を含めます

Q&A

解決済

2回答

756閲覧

【rails】一対多対一のリレーションを使うフォームで、バリデーションに失敗する(仮)(最終更新:210826)

kaoru-drosera

総合スコア23

Ruby

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

Ruby on Rails

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

データベース

データベースとは、データの集合体を指します。また、そのデータの集合体の共用を可能にするシステムの意味を含めます

0グッド

0クリップ

投稿2021/08/12 09:46

編集2021/08/26 06:13

おことわり

unknown attribute 'XXX' for XXX. で必要ないはず/存在するはずのカラムが要求される」の続きになります。
そのため、「詰まっている箇所」まで同じ文面が続きます。

前提

クリック/キータッチで効果音を鳴らすことができるサンプラーアプリを作っています。

ここで作っているアプリはこれに加えて好きな音源をフォームで読み込んで音量やループ可否なども設定できます。
さらに、ログイン中は変更した設定を保存して、使いたい時に保存した設定を読み込むことができます。

イメージとしては「効果音ラボ」の「効果音ポン出し」に保存機能がついたようなものです。もっと具体的に言うと「KLANG」に当たると思われます。

効果音ラボ 効果音ポン出し
https://soundeffect-lab.info/pon/app.html

KLANG
http://m-tank.info/common/klang

実現したいこと

マウスクリック/キータッチで効果音を鳴らせるボタンが12あり、
12のボタンごとに「使用する効果音の音源(とその名前)」と「ボタンごとの設定(どのキーに配置するか、音量、ループ可能かどうか、ボタンの色)」を読み込みます。
それら12ボタンごとの設定と読み込む音源を、「サンプラー」の一つの投稿として保存(作成)する仕組みです。

##使用するテーブル
使用するテーブルは以下の通りです。
ユーザー(User)もありますが今回は省略いたします。
ちなみに簡単に言うとログインはdeviseで、メールアドレスではなくユーザーネーム(半角英数字)とパスワードでログインする仕組みにしています。

サンプラー(Sampler)
|保存するもの|カラム名|データ型|必須?|
|:--|:--|:--|
|id|id|-|
|ユーザーid|user_id|bigint?|
|サンプラー名|sampler_name|string|

ボタンごとの設定(Seboard)
|保存するもの|カラム名|データ型|必須?|
|:--|:--|:--|
|id|id|-|
|サンプラーid|sampler_id|bigint|true|
|効果音ライブラリid|sefile_id|bigint?|
|キー配置|position|integer|
|ボタン色|btncoler|integer|
|音量|volume|integer|
|ループ可否|loopable|loopable|

効果音ライブラリ(Sefile)
|保存するもの|カラム名|データ型|必須?|
|:--|:--|:--|
|id|id|-|
|ユーザーid|user_id|bigint|
|ボタンごとの設定のid|seboard_id|bigint|
|効果音(をDBに保存する際)の名前|sename|string|
|効果音の音源データ|sedata|(binary)string|

「効果音の音源」を「ボタンごとの設定」と分けているのは、「音源」は「サンプラー」の一つの投稿として投稿する以外にも直接「音源」を投稿できる仕組みにしたいためです。
証拠に、user_idのカラムがあり、idを取得してリレーションできるようにしています。

投稿(保存)のしくみ

投稿のしくみ

一度の投稿で更新されるsampler一つにつき12のseboardがあり、それぞれに一つずつ(空白可)sefileが割り当てられます。

新規作成/更新のしくみ

ボタンは一つに統一し、「新規作成/更新」機能は一つに統一します。
具体的に言うと以下の通りです。

「『サンプラーに付ける名前』に前回保存したサンプラーの名前と同じ投稿がある場合、その投稿に今回保存するサンプラーの設定を更新。
それ以外は新規作成とする。」

以上、基本的な保存の仕組みに加えて、もう一つ

「『ボタンごとの設定』で読み込む音源に、
『効果音ライブラリ』の「効果音の名前」「効果音の音源データ」両方の値が同じデータがある場合、そのデータのidを『ボタンごとの設定』の『効果音ライブラリid』に割り当てる。
ない場合は新規データとして『効果音ライブラリ』のデータとして保存。そのidを割り当てる」

が加わります。

詰まっている箇所(更新:210810)

エラー画面はこのような感じです。

イメージ説明

ただし、どこで詰まっているかはターミナルを見た方が詳しい気がするのでそちらを説明します。

現在の状況(ターミナル)

コードを貼ると字数を超えてしまうため、画像2つで表示しています。
申し訳ありません。

バリデーション失敗_ターミナル-1

バリデーション失敗_ターミナル-2

このコードを見る限り、リレーションの最上位であるsamplerテーブルの値は満足に送れている気がします。
それが、その下層であるseboardテーブルの値から何らかのエラーが起きて送信が停止しているように見えます。

現在のコード

/app/controllers/sampler_controller.rb

ruby

1class SamplerController < ApplicationController 2 3 before_action :authenticate_user! 4 5 # 投稿を一覧形式にする(予定) 6 def seindex 7 @samplers = current_user.samplers.all 8 end 9 10 def save 11 @sampler = current_user.samplers.find_or_initialize_by(sampler_name: params[:sampler_name]) 12 @sampler.seboards.build 13 @sampler.sefile.find_or_initialize_by(sename: params[:sename], sedata: params[:sedata]) 14 end 15 16 def submit 17 @sampler = current_user.sampler.find_or_initialize_by(sampler_name: params[:sampler_name]) 18 unless @sampler.persisted? 19 @sampler = current_user.sampler.new(sampler_params) 20 21 unless @sampler.save! 22 flash[:danger] = "保存に失敗しました" 23 else 24 flash[:success] = "保存しました" 25 end 26 27 else 28 @sampler = current_user.sampler.update(update_sampler_params) 29 30 unless @sampler.save! 31 flash[:danger] = "更新に失敗しました" 32 else 33 flash[:success] = "更新しました" 34 end 35 36 end 37 38 redirect_to root_path 39 40 end 41 42 43 def show 44 @sampler = Sampler.find(params[:id]) 45 end 46 47 private 48 49 def sampler_params 50 params.require(:sampler).permit(:sampler_name, seboards_attributes: [:position, :btncolor, :volume, :loopable, sefile_attributes: [:sename, :sedata]]) 51 end 52 53 def update_sampler_params 54 params.require(:sampler).permit(:sampler_name, seboards_attributes: [:position, :btncolor, :volume, :loopable, sefile_attribute: [:sename, :sedata]]) 55 end 56end 57
/app/models/seboard.rb

Ruby

1class Seboard < ApplicationRecord 2 belongs_to :sampler,inverse_of: :seboards#, class_name: "Sampler", foreign_key: 'sampler_id' 3 4 has_one :sefile,inverse_of: :seboard#, class_name: "Sefile", foreign_key: 'sefile_id' 5 accepts_nested_attributes_for :sefile 6 7 8end
/app/models/sefile.rb

Ruby

1class Sefile < ApplicationRecord 2 belongs_to :user, class_name: "User", foreign_key: 'user_id' 3 4 belongs_to :seboard, class_name: "Seboard", foreign_key: 'seboard_id' ,inverse_of: :sefile, optional: true 5 6 mount_uploader :sedata, SeUploader 7 8end 9
/app/views/sampler/_save.html.erb

html

1 <%= form_with model: @sampler do |sp| %> 2 <%= sp.fields_for :seboards do |sb| %> 3 <%= sb.fields_for :sefile do |sf| %> 4 5 <!-- 上段 --> 6 <div id="bg-q" class="case-btn-q case-btn" onclick="play(soundQ, loopQ, loopValue_q); btnColorInit(btnColorQ, bg_Q)"> 7 <p>Q</p> 8 <%= sb.hidden_field :position,:name=>"sampler[seboards_attributes][1][position]", :value => "1" %> 9 <%= sb.range_field :volume, :name=>"sampler[seboards_attributes][1][volume]", :onchange=>"volumeInit(soundQ, volumeQ, volumeValue_q)", :id=>"vol-q", :value=>50, :min=>1, :max=>99 %> 10 <div class="radio-field_q"> 11 <%= sb.radio_button :btncoler, "1", :name=>"sampler[seboards_attributes][1][btncolor]", checked: true, :class=>"red" %> 12 <%= sb.radio_button :btncoler, "2", :name=>"sampler[seboards_attributes][1][btncolor]", :class=>"blue" %> 13 <%= sb.radio_button :btncoler, "3", :name=>"sampler[seboards_attributes][1][btncolor]", :class=>"white" %> 14 </div><!-- .radio-field_q --> 15 <%= sb.check_box "loopable", :name=>"sampler[seboards_attributes][1][loopable]", :onclick=>"loopInit(soundQ, loopQ, loopValue_q)", :id=>"loopable_q", :checked_value =>true, :unchecked_value=>false %> 16 <%= sf.file_field :sedata, :name=>"sampler[seboards_attributes][1][sefile_attributes][sedata]", :id=>"selectFile_q", :onclick=>"readFile(soundQ, form_fileQ)" %> 17 <%= sf.text_field :sename, :name=>"sampler[seboards_attributes][1][sefile_attributes][sename]" %> 18 19 </div><!-- .case-btn-d --> 20 <div id="bg-w" class="case-btn-w case-btn" onclick="play(soundW, loopW, loopValue_w); btnColorInit(btnColorW, bg_W)"> 21 <p>W</p> 22 <%= sb.hidden_field :position,:name=>"sampler[seboards_attributes][2][position]", :value => "2" %> 23 <%= sb.range_field :volume, :name=>"sampler[seboards_attributes][2][volume]", :onchange=>"volumeInit(soundW, volumeW, volumeValue_w)", :id=>"vol-w", :value=>50, :min=>1, :max=>99 %> 24 <div class="radio-field_w"> 25 <%= sb.radio_button :btncoler, "1", :name=>"sampler[seboards_attributes][2][btncolor]", checked: true, :class=>"red" %> 26 <%= sb.radio_button :btncoler, "2", :name=>"sampler[seboards_attributes][2][btncolor]", :class=>"blue" %> 27 <%= sb.radio_button :btncoler, "3", :name=>"sampler[seboards_attributes][2][btncolor]", :class=>"white" %> 28 </div><!-- .radio-field_q --> 29 <%= sb.check_box "loopable", :name=>"sampler[seboards_attributes][2][loopable]", :onclick=>"loopInit(soundW, loopW, loopValue_w)", :id=>"loopable_w", :checked_value =>true, :unchecked_value=>false %> 30 <%= sf.file_field :sedata, :name=>"sampler[seboards_attributes][2][sefile_attributes][sedata]", :id=>"selectFile_w", :onclick=>"readFile(soundW, form_fileW)" %> 31 <%= sf.text_field :sename, :name=>"sampler[seboards_attributes][2][sefile_attributes][sename]" %> 32 </div><!-- .case-btn-d --> 33 34 <% end %> 35 <% end %> 36 37 <%= sp.text_field :sampler_name %> 38 <%= sp.submit "保存する" %> 39 <% end %> 40 41
/db/schema.rb

リレーション、特に外部キーの設定で何度もつまづいているため、念のためschema.rbから外部キーの設定も貼っておきます。

ruby

1() 2 add_foreign_key "samplers", "users" 3 add_foreign_key "seboards", "samplers" 4 add_foreign_key "seboards", "sefiles" 5 add_foreign_key "sefiles", "seboards" 6 add_foreign_key "sefiles", "users" 7end

試したこと(更新:210814)

sefileにuser_idがない、とのご指摘をいただいたので解法を調べていたところ、

  1. deep_merge
  2. to_h.deep_merge(配列に変換した後deep_merge)
  3. reverse_merge

の3つがありました。

deep_merge

コードは以下の通りに修正しました。

/app/controllers/sampler_controller.rb

ruby

1 def sampler_params 2 params.require(:sampler).permit(:sampler_name, seboards_attributes: [:position, :btncolor, :volume, :loopable, sefile_attributes: [:sename, :sedata]]).deep_merge(seboards_attributes:[sefile_attributes:[user_id: current_user.id]]) 3 end 4 5 def update_sampler_params 6 params.require(:sampler).permit(:sampler_name, seboards_attributes: [:position, :btncolor, :volume, :loopable, sefile_attributes: [:sename, :sedata]]).deep_merge(seboards_attributes:[sefile_attribute:[user_id: current_user.id]]) 7 end 8

その結果以下のようなエラーが出ました。
undifined_merge

to_h.deep_merge

コードは以下の通りに修正しました。

/app/controllers/sampler_controller.rb

ruby

1 def sampler_params 2 params.require(:sampler).permit(:sampler_name, seboards_attributes: [:position, :btncolor, :volume, :loopable, sefile_attributes: [:sename, :sedata]]).to_h.deep_merge(seboards_attributes:[sefile_attributes:[user_id: current_user.id]]) 3 end 4 5 def update_sampler_params 6 params.require(:sampler).permit(:sampler_name, seboards_attributes: [:position, :btncolor, :volume, :loopable, sefile_attributes: [:sename, :sedata]]).to_h.deep_merge(seboards_attributes:[sefile_attribute:[user_id: current_user.id]]) 7 end 8

その結果以下のようなエラーが出ました。
イメージ説明

reverse_merge

最後はreverse_mergeですが、結果は変わりませんでした。

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

ブラウザ:chrome(最新のバージョン)
テキストエディタ:atom(ver:1.32.2)
rails: Rails 6.1.4

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

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

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

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

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

mather

2021/08/12 10:12

バリデーションエラーなので、 Seboards のモデル定義が影響していると思います。
kaoru-drosera

2021/08/12 13:49

ご指摘のありましたseboardのモデルのコードを追記いたしました。
guest

回答2

0

ベストアンサー

sefile のパラメーターに user_id が無いからです。

投稿2021/08/12 22:30

winterboum

総合スコア23567

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

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

0

大変遅くなりました。
解決いたしましたので簡単に解決方法を示してから質問を閉めさせていただきます。

ただし、まだこのアプリに関した質問はまだもう一つ行うことになってしまいます。申し訳ありません。

#解決方法

結論から言うと、winterboum様が回答した通り「sefileのパラメータにuser_idが渡ってきていない」ことが原因です。

sefile側の「belongs_to」に「optional:true」を加え、空白可にすることで正常に投稿ができるようになりました。

/app/models/sefile.rb

ruby

1 belongs_to :user, class_name: "User", foreign_key: 'user_id', optional: true 2 3 has_many :samplers, through: :seboards, source: :sampler, foreign_key: 'sampler_id' 4 accepts_nested_attributes_for :samplers 5 6 7 belongs_to :seboard, class_name: "Seboard", foreign_key: 'seboard_id' ,inverse_of: :sefile, optional: true 8 9 10 mount_uploader :sedata, SeUploader 11 12 # バリデーションはこれ以降に 13 14end 15

本当ならサンプラー経由で投稿してもuser_idが入ることでユーザーが自由に管理できるようにしたかったのですがここは妥協です。

最後に

別解としては、seboard側にもuser_idのカラムを外部キー付きで入れることも挙げられると考えています。ただし、他に実装すべき最低限の機能を優先的に実装させたいので試すのはだいぶ後になると思います。

長い間、そして今一度先のお目汚し申し訳ありません。
乱文乱筆、ご拝読重ねてありがとうございます。
自己解決で締めさせていただきますが、回答を下さったwinterboum様をベストアンサーといたします。

投稿2021/08/26 06:11

編集2021/08/26 06:15
kaoru-drosera

総合スコア23

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問