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

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

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

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

Ruby on Rails 6

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

データベース設計

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

データ構造

データ構造とは、データの集まりをコンピュータの中で効果的に扱うために、一定の形式に系統立てて格納する形式を指します。(配列/連想配列/木構造など)

保存

保存(save)とは、特定のファイルを、ハードディスク等の外部記憶装置に記録する行為を指します。

Q&A

解決済

1回答

1683閲覧

多対多で関連付けたモデルにデータを保存したい

_Taturon_

総合スコア17

Ruby

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

Ruby on Rails 6

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

データベース設計

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

データ構造

データ構造とは、データの集まりをコンピュータの中で効果的に扱うために、一定の形式に系統立てて格納する形式を指します。(配列/連想配列/木構造など)

保存

保存(save)とは、特定のファイルを、ハードディスク等の外部記憶装置に記録する行為を指します。

0グッド

1クリップ

投稿2020/05/14 13:29

編集2020/05/16 01:49

実現したいこと

下記のような設計で、memoを作成するフォームにsourceとsource_mapに関連づけたデータを保存する機能を実装したいです。
※初学者なので、下記ER図は間違っている可能性がございます。
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">]>

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

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

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

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

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

guest

回答1

0

ベストアンサー

通常の多対多と違うのでちと戸惑っています。
この構成で多対多というと通常は既存のSourceとMemoとを関係つけます。
今回は、Memoと同時にSourceも新規登録ということで、ふぅ~~む。

これですと同じURLからことなるMemoを作ってもSourceが作られることになるのでは?それとも同じURLであったら新規は作らない?

実際のデータをイメージすると、単に Memoにurlというfieldをつけるだけで良いのでは?とおもいますが、多対多にする意図はなんでしょう

追記

if @memo.save @source = Source.find_or_create_by(url: memo_params[:url]) @memo.sources << @source

投稿2020/05/14 23:20

編集2020/05/16 00:53
winterboum

総合スコア23329

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

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

_Taturon_

2020/05/15 02:58 編集

winterboumさん、ご回答ありがとうございます。 多対多にしたい意図は、 1.ひとつのmemoに複数のurlを紐付けたい 2.複数のmemoに同じurlが登録される可能性がある 3.多対多関連付けを学習する ためです。 ご回答の意味は、通常の多対多では、 「複数のモデルに対して一つのフォームから同時にデータを保存するのは一般的ではない」 ということでしょうか? その場合、新たにSourcesControllerとビューを追加し、urlを別途保存するフォームを作ると実装出来そうと感じました。 しかし、出来れば一つのフォーム、一回の保存で登録したいのですが、やはり難しいでしょうか...
_Taturon_

2020/05/15 03:11

ご回答を読んでいると、実装したい機能がより明確になりました。 まずフォームを複数追加し、 可能であれば、 1.入力したurlがsourceモデルにあった場合はそのidと紐付ける 2.入力したurlがsourceモデルになかった場合は新規登録して紐付ける みたいな機能が欲しいですが、難しいでしょうか。 if文を使って、入力された内容をfindで検索し、あった場合はその内容を削除かつ見つかったidで紐付け、なかった場合はそのまま保存 みたいなスキームが見えましたが、実装方法がいまいち分かりません...
winterboum

2020/05/15 03:54

Railsが用意しているスマートな方法はつかえないですが、泥臭くやれば可能です。 find_or_initialize_by とか find_or_create_by を調べてみてください それで手に入れた sourcesのインスタンスを memo.sources << インスタンス で登録できます。
_Taturon_

2020/05/15 09:40

winterboumさん、ご返信ありがとうございます。 早速調べてみます。
_Taturon_

2020/05/15 23:03

winterboumさんのアドバイスを基に、追記1のように実装してみたのですが、既出のurlと関連付ける方法がいまいち分かりません。 恐縮ですが、もう少しアドバイスして頂くことは可能でしょうか?
_Taturon_

2020/05/16 01:39

winterboumさんの追記情報通りに実装することで、機能を実現できました。 ご回答ありがとうございました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問