前提
クリック/キータッチで効果音を鳴らすことができるサンプラーアプリを作っています。
ここで作っているアプリはこれに加えて好きな音源をフォームで読み込んで音量やループ可否なども設定できます。
さらに、ログイン中は変更した設定を保存して、使いたい時に保存した設定を読み込むことができます。
イメージとしては「効果音ラボ」の「効果音ポン出し」に保存機能がついたようなものです。もっと具体的に言うと「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が割り当てられます。
HTML上でのフォームで送る値は以下のような感じになります。
{"authenticity_token"=>"[FILTERED]", "sampler"=> {"seboards_attributes"=> {"1"=>{"position"=>"1", "volume"=>"50", "btncolor"=>"1", "loopable"=>"0", "sefile_attributes"=>{"sedata"=>"click.mp3", "sename"=>""}}, "2"=>{"position"=>"2", "volume"=>"50", "btncolor"=>"1", "loopable"=>"0", "sefile_attributes"=>{"sedata"=>"click2.mp3", "sename"=>""}}}}, "sampler_name"=>"サンプラー名", "commit"=>"保存する"}
新規作成/更新のしくみ
ボタンは一つに統一し、「新規作成/更新」機能は一つに統一します。
具体的に言うと以下の通りです。
「『サンプラーに付ける名前』に前回保存したサンプラーの名前と同じ投稿がある場合、その投稿に今回保存するサンプラーの設定を更新。
それ以外は新規作成とする。」
以上、基本的な保存の仕組みに加えて、もう一つ
「『ボタンごとの設定』で読み込む音源に、
『効果音ライブラリ』の「効果音の名前」「効果音の音源データ」両方の値が同じデータがある場合、そのデータのidを『ボタンごとの設定』の『効果音ライブラリid』に割り当てる。
ない場合は新規データとして『効果音ライブラリ』のデータとして保存。そのidを割り当てる」
が加わります。
詰まっている箇所(更新:210810)
**「unknown attribute 'XXX' for XXX.」**なのですが、存在しない、あるいは存在する必要がない要素を要求されている状態です。
ただ、外部キーなどリレーションに関わる問題であることには間違いないとは思います。
#####(更新: 210810)
maisumakun様、ご回答ありがとうございます。
sefileに、seboard_idを追加いたしました。
しかしその結果、以下のようなエラーが出ています。
「sefile_id」、要するにSefileクラスでは存在するはずのクラスがない、ということになると思われます。
Sefileもクラスである以上、idが存在しないとは考えにくいです。
今の所、こればかりは不可解です。
現在のコード
/app/models/sampler.rb
ruby
1class Sampler < ApplicationRecord 2 # リレーション 3 belongs_to :user, class_name: "User", foreign_key: 'user_id' 4 5 has_many :seboards#, class_name: "Seboard", foreign_key: 'id' 6 accepts_nested_attributes_for :seboards 7 8 has_one :sefile, :through => :seboards#, foreign_key: 'sefile_id' 9 accepts_nested_attributes_for :sefile 10 11 # バリデーション(予定) 12 13 14end
/app/controllers/sampler_controller.rb
ruby
1class SamplerController < ApplicationController 2 3 before_action :authenticate_user! 4 5 # 投稿を一覧形式にする(予定) 6 def seindex 7 @samplers = Sampler.all 8 end 9 10 def save 11 @sampler = Sampler.find_or_initialize_by(sampler_name: params[:sampler_name]) 12 end 13 14 def submit 15 @sampler = Sampler.find_or_initialize_by(sampler_name: params[:sampler_name]) 16 # sampler_sefile = Sampler.seboard.sefile.find_or_initialize_by(sename: params[:sename], sedata: params[:sedata]) 17 unless @sampler.persisted? 18 @sampler = Sampler.new(sampler_params) 19 @sampler.user_id = current_user.id 20 21 unless @sampler.save 22 flash[:danger] = "保存に失敗しました" 23 else 24 flash[:success] = "保存しました" 25 end 26 27 else 28 @sampler = Sampler.update_attrbutes!(update_sampler_params) 29 # @sampler.save 30 31 unless @sampler.save 32 flash[:danger] = "更新に失敗しました" 33 else 34 flash[:success] = "更新しました" 35 end 36 37 end 38 39 redirect_to root_path 40 41 end 42 43 44 def show 45 @sampler = Sampler.find(params[:id]) 46 end 47 48 private 49 50 def sampler_params 51 params.require(:sampler).permit(:sampler_name, seboards_attributes: [:position, :btncolor, :volume, :loopable, sefile_attributes: [:sename, :sedata]]) 52 end 53 54 def update_sampler_params 55 params.require(:sampler).permit(:sampler_name, seboards_attributes: [:position, :btncolor, :volume, :loopable, sefile_attributes: [:sename, :sedata, :id]]) 56 end 57 58 59end
/app/views/sampler/_save.html.erb
html
1 # 字数制限につき一時的に削除 申し訳ありません
/app/models/seboard.rb
ruby
1class Seboard < ApplicationRecord 2 belongs_to :sampler 3 4 has_one :sefile#, class_name: "Sefile", foreign_key: 'id' 5 # ↑ 6 # has_many :sefiles にするとうまくいった。 7 # この調子だと下の式のものやviewとcontrollerに点在するsefileの設定も複数形にするといいのだろうが、こちらを単数形にしたい。 8 accepts_nested_attributes_for :sefile 9 # accepts_nested_attributes_for :sefiles 10 11end
/app/models/sefile.rb(更新:210810)
ruby
1class Sefile < ApplicationRecord 2 belongs_to :user, class_name: "User", foreign_key: 'user_id' 3 belongs_to :seboard, optional: true 4 5 mount_uploader :sedata, SeUploader 6 7 # バリデーションはこれ以降に 8 9end 10
/db/schema.rb(更新:210810)
sefileに、カラム「seboard_id」を追加いたしました。
ruby
1(略) 2 3ActiveRecord::Schema.define(version: 2021_08_09_172009) do 4 5 # These are extensions that must be enabled in order to support this database 6 enable_extension "plpgsql" 7 8 create_table "samplers", force: :cascade do |t| 9 t.bigint "user_id", null: false 10 t.string "sampler_name" 11 t.datetime "created_at", precision: 6, null: false 12 t.datetime "updated_at", precision: 6, null: false 13 t.index ["user_id"], name: "index_samplers_on_user_id" 14 end 15 16 create_table "seboards", force: :cascade do |t| 17 t.bigint "sampler_id", null: false 18 t.bigint "sefile_id" 19 t.integer "position" 20 t.integer "btncolor" 21 t.integer "volume" 22 t.boolean "loopable" 23 t.datetime "created_at", precision: 6, null: false 24 t.datetime "updated_at", precision: 6, null: false 25 t.index ["sampler_id"], name: "index_seboards_on_sampler_id" 26 t.index ["sefile_id"], name: "index_seboards_on_sefile_id" 27 end 28 29 create_table "sefiles", force: :cascade do |t| 30 t.bigint "user_id", null: false 31 t.string "sename" 32 t.string "sedata" 33 t.datetime "created_at", precision: 6, null: false 34 t.datetime "updated_at", precision: 6, null: false 35 t.bigint "seboard_id" 36 t.index ["seboard_id"], name: "index_sefiles_on_seboard_id" 37 t.index ["user_id"], name: "index_sefiles_on_user_id" 38 end 39 40 create_table "users", force: :cascade do |t| 41 t.string "user_name" 42 t.datetime "created_at", precision: 6, null: false 43 t.datetime "updated_at", precision: 6, null: false 44 t.string "password_digest" 45 t.string "email", default: "", null: false 46 t.string "encrypted_password", default: "", null: false 47 t.string "reset_password_token" 48 t.datetime "reset_password_sent_at" 49 t.datetime "remember_created_at" 50 t.index ["email"], name: "index_users_on_email", unique: true 51 t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true 52 end 53 54 add_foreign_key "samplers", "users" 55 add_foreign_key "seboards", "samplers" 56 add_foreign_key "seboards", "sefiles" 57 add_foreign_key "sefiles", "users" 58end 59 60
補足情報(FW/ツールのバージョンなど)
ブラウザ:chrome(最新のバージョン)
テキストエディタ:atom(ver:1.32.2)
rails: Rails 6.1.4
回答2件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/08/09 08:00
2021/08/09 08:04 編集
2021/08/09 08:07
2021/08/09 18:03