実現したいこと
下記のような設計で、memoを作成するフォームにsourceとsource_mapに関連づけたデータを保存する機能を実装したいです。
※初学者なので、下記ER図は間違っている可能性がございます。
具体的に言うと、次のような流れの機能を実装したいです。
0. memoを新規作成するフォームに行く
0. word,synonym,content,sourceをフォームに入力する
0. 保存ボタンをクリックする
0. MemosControllerにて入力した値が各モデル(memo,source_map,source)に保存される
0. 作成したmemoが表示されるページにリダイレクト
0. 表示されているmemoをクリックすると詳細ページへリダイレクト
0. 保存した各値が表示される
上記の内2.の「sourceを入力するフォーム」と4.の「入力したsourceの値をmemoと関連付けて(中間テーブルにidペアを保存して)保存する機能」を実装する方法をご教授願いたいです。
実行環境
・Rails 6.0.0
・ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]
・Cloud 9
ソースコード
erb
1<!--フォームパーシャル--> 2 3 <%= form_with model: @memo, local: true do |f| %> 4 <div class="form-group"> 5 <%= f.label :word, "単語" %> 6 <%= f.text_field :word, class: "form-control", 7 placeholder: "登録したい単語を記入して下さい" %> 8 </div> 9 10 ・ 11 ・ 12 ・ 13 14 <div class="form-group"> <!--このdivタグ内の記述方法が分かりません--> 15 <%= f.label :source, "出典(URL)" %> 16 <%= f.url_field :source, class: "form-control", 17 placeholder: "出典元のURLを記入して下さい" %> 18 </div> 19 20 <div class="form-group"> 21 <%= f.submit yield(:btn_message), 22 class: "btn btn-success btn-lg btn-block" %> 23 </div> 24 <% end %>
rb
1#memosコントローラー 2Class MemosController < ApplicationController 3 4 ・ 5 ・ 6 ・ 7 8 def new 9 @memo = Memo.new 10 end 11 12 def create 13 @memo = Memo.new(memo_params) 14 if @memo.save 15 flash[:notice] = "新しいMemoを作成しました!" 16 redirect_to root_url 17 else 18 flash.now[:alert] = "無効な送信です" 19 render :new 20 end 21 end 22 23 ・ 24 ・ 25 ・ 26 27 def update 28 @memo = Memo.find(params[:id]) 29 if @memo.update(memo_params) 30 redirect_to root_url 31 flash[:notice] = "Memoを更新しました!" 32 else 33 flash.now[:alert] = "無効な送信です" 34 render :edit 35 end 36 end 37 38 ・ 39 ・ 40 ・ 41 42 private 43 44 def memo_params #ストロングパラメーターの設定方法も分かりません 45 params.require(:memo).permit(:word, :content, :synonym, :tag_list, :source) 46 end 47 48end
rb
1#ルーティング 2Rails.application.routes.draw do 3 root 'memos#index' 4 resources :memos 5end
rb
1#各モデルの関連付け 2 3#memoモデル 4class Memo < ApplicationRecord 5 has_many :source_maps, dependent: :destroy 6 has_many :sources, through: :source_maps 7 8 ・ 9 ・ 10 ・ 11 12end 13 14#source_mapモデル 15class SourceMap < ApplicationRecord 16 belongs_to :memo 17 belongs_to :source 18end 19 20#sourceモデル 21class Source < ApplicationRecord 22 has_many :source_maps, dependent: :destroy 23 has_many :memos, through: :source_maps 24end 25
試したこと
・上記のようにフォームに:sourceを渡してみました
フォームのビューでは何故か「出典(URL)」の入力ウィンドウに
Source::ActiveRecord_Associations_CollectionProxy:0x00007fc51eb7afd0>
とデフォルトで表示されてしまいます。
上記を消し、URLを入力して保存すると
NoMethodError in MemosController#create
undefined method `each' for "(入力したURL)":String
のエラーが発生します。
何故突然eachが出てくるのか?と戸惑っております。
他にも色々試しましたが、どうも上手くsourceモデルとsource_mapモデルにデータが保存されません。
初歩的な質問ですが、どなたかご回答の程、お願い申し上げます。
追記1:sourceモデルに既出のデータがあってもそれと紐付けられません
次のように実装することで、関連付けたデータの保存はできるようになりました。ありがとうございます。
erb
1<!--フォームパーシャル--> 2<%= form_with model: @memo, local: true do |f| %> 3 4 ・ 5 ・ 6 ・ 7 8 <div class="form-group"> 9 <%= f.label :url, "出典(URL)" %> 10 <%= f.url_field :url, class: "form-control", 11 placeholder: "出典元のURLを記入して下さい" %> 12 </div> 13 <div class="form-group"> 14 <%= f.submit yield(:btn_message), class: "btn btn-success btn-lg btn-block" %> 15 </div> 16<% end %>
rb
1#memosコントローラー 2class MemosController < ApplicationController 3 4 ・ 5 ・ 6 ・ 7 8 def new 9 @memo = Memo.new 10 @source = @memo.sources.build 11 end 12 13 def create 14 @memo = Memo.new(memo_params.slice(:word, :content, :synonym, :tag_list)) 15 if @memo.save 16 @source = @memo.sources.find_or_initialize_by(url: memo_params[:url]) 17 @memo.sources << @source 18 flash[:notice] = "新しいMemoを作成しました!" 19 redirect_to root_url 20 else 21 flash.now[:alert] = "無効な送信です" 22 render :new 23 end 24 end 25 26 ・ 27 ・ 28 ・ 29 30 private 31 32 def memo_params 33 params.require(:memo).permit( 34 :word, 35 :content, 36 :synonym, 37 :tag_list, 38 :url 39 ) 40 end 41 42end
しかし、find_or_initialize_byを使用しても、既出のurlがあってもそれと紐付けられず、同じurlがsourceモデルに新規保存されそちらと紐付けられてしまいます。
console
1以下、フォームから異なるmemoに対し同じurlを登録した場合のモデル内データ 2 3同じurlであってもfind_byされずに新規登録されてしまう 4> Source.all 5 (0.8ms) SELECT sqlite_version(*) 6 Source Load (0.5ms) SELECT "sources".* FROM "sources" LIMIT ? [["LIMIT", 11]] 7 => #<ActiveRecord::Relation [ 8 #<Source id: 1, url: "https://rails.densan-labs.net/form/relation_regist...", created_at: "2020-05-15 22:31:03", updated_at: "2020-05-15 22:31:03">, 9 #<Source id: 2, url: "https://rails.densan-labs.net/form/relation_regist...", created_at: "2020-05-15 22:31:11", updated_at: "2020-05-15 22:31:11">]> 10 11関連付けも、本来なら 12SourceMap id: 1, memo_id: 1, source_id: 1 13SourceMap id: 1, memo_id: 2, source_id: 1 14となって欲しいが、新規登録されたsource_id: 2と関連付けられてしまう 15> SourceMap.all 16 SourceMap Load (0.2ms) SELECT "source_maps".* FROM "source_maps" LIMIT ? [["LIMIT", 11]] 17 => #<ActiveRecord::Relation [ 18 #<SourceMap id: 1, memo_id: 1, source_id: 1, created_at: "2020-05-15 22:31:03", updated_at: "2020-05-15 22:31:03">, 19 #<SourceMap id: 2, memo_id: 2, source_id: 2, created_at: "2020-05-15 22:31:11", updated_at: "2020-05-15 22:31:11">]>
おそらく@memo.sources.find_or_initialize_by
としているので、sourceモデル全体からfind_by
しているのではなく、関連付けられたsourceからfind_by
してしまっているのが原因と思うのですが、解決方法が分かりません。何かアドバイスを頂けないでしょうか?
追記2:解決しました
winterboumさんのアドバイス通り、下記のように実装することで、重複しているurlはそのままに新規のみ保存できるようになりました。
ありがとうございました!
rb
1#memosコントローラー 2class MemosController < ApplicationController 3 4 ・ 5 ・ 6 ・ 7 8 def new 9 @memo = Memo.new 10 @source = @memo.sources.build 11 end 12 13 def create 14 @memo = Memo.new(memo_params.slice(:word, :content, :synonym, :tag_list)) 15 if @memo.save 16 @source = Source.find_or_create_by(url: memo_params[:url]) 17 @memo.sources << @source 18 flash[:notice] = "新しいMemoを作成しました!" 19 redirect_to root_url 20 else 21 flash.now[:alert] = "無効な送信です" 22 render :new 23 end 24 end 25 26 ・ 27 ・ 28 ・ 29 30 private 31 32 def memo_params 33 params.require(:memo).permit( 34 :word, 35 :content, 36 :synonym, 37 :tag_list, 38 :url 39 ) 40 end 41 42end
console
1異なるmemoに同じurlを関連付けて保存した場合 2> SourceMap.all 3 SourceMap Load (0.5ms) SELECT "source_maps".* FROM "source_maps" LIMIT ? [["LIMIT", 11]] 4 => #<ActiveRecord::Relation [ 5 #<SourceMap id: 1, memo_id: 1, source_id: 1, created_at: "2020-05-16 01:35:29", updated_at: "2020-05-16 01:35:29">, 6 #<SourceMap id: 2, memo_id: 2, source_id: 1, created_at: "2020-05-16 01:35:38", updated_at: "2020-05-16 01:35:38">]> 7> Source.all 8 (1.0ms) SELECT sqlite_version(*) 9 Source Load (0.4ms) SELECT "sources".* FROM "sources" LIMIT ? [["LIMIT", 11]] 10 => #<ActiveRecord::Relation [ 11 #<Source id: 1, url: "https://rails.densan-labs.net/form/relation_regist...", created_at: "2020-05-16 01:35:29", updated_at: "2020-05-16 01:35:29">]> 12 13続けて異なるmemoを異なるurlで関連付けて保存した場合 14> SourceMap.all 15 SourceMap Load (0.2ms) SELECT "source_maps".* FROM "source_maps" LIMIT ? [["LIMIT", 11]] 16 => #<ActiveRecord::Relation [ 17 #<SourceMap id: 1, memo_id: 1, source_id: 1, created_at: "2020-05-16 01:35:29", updated_at: "2020-05-16 01:35:29">, 18 #<SourceMap id: 2, memo_id: 2, source_id: 1, created_at: "2020-05-16 01:35:38", updated_at: "2020-05-16 01:35:38">, 19 #<SourceMap id: 3, memo_id: 3, source_id: 2, created_at: "2020-05-16 01:37:04", updated_at: "2020-05-16 01:37:04">]> 20> Source.all 21 Source Load (0.2ms) SELECT "sources".* FROM "sources" LIMIT ? [["LIMIT", 11]] 22 => #<ActiveRecord::Relation [ 23 #<Source id: 1, url: "https://rails.densan-labs.net/form/relation_regist...", created_at: "2020-05-16 01:35:29", updated_at: "2020-05-16 01:35:29">, 24 #<Source id: 2, url: "https://teratail.com/questions/261655#", created_at: "2020-05-16 01:37:04", updated_at: "2020-05-16 01:37:04">]>
回答1件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/05/15 02:58 編集
2020/05/15 03:11
2020/05/15 03:54
2020/05/15 09:40
2020/05/15 23:03
2020/05/16 01:39