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

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

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

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

Ruby on Rails

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

Ruby on Rails 7

Ruby on Rails 7は、2021年12月に正式リリースされました。Ruby on Railsのバージョン7であり、フロントエンド開発環境を大幅に刷新。Node.jsを用いない構成がデフォルトになっています。

Q&A

解決済

1回答

671閲覧

[Rails] 多対多のテーブルからデータを取得する

mameta00

総合スコア1

Ruby

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

Ruby on Rails

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

Ruby on Rails 7

Ruby on Rails 7は、2021年12月に正式リリースされました。Ruby on Railsのバージョン7であり、フロントエンド開発環境を大幅に刷新。Node.jsを用いない構成がデフォルトになっています。

1グッド

1クリップ

投稿2023/01/15 17:16

編集2023/01/16 12:40

プログラミング初心者です。
アプリを1から作成しているのですが、完全に詰まってしまったので、ご教授いただけると幸いです。

実現したいこと

商品の登録ができるようにしたいです。

以下のテーブルを作成したところ、regular_priceやdiscounted_priceはproductsテーブルに定義されていないとエラーが出てしまいます。
調べても引っ張ってくる方法が分からず、、、どうしたら良いのでしょうか?

productsテーブル

user_id name

product_storesテーブル

product_id store_id regular_price discounted_price

storesテーブル

name

products_controller.rb

ruby

1 def new 2 @product = Product.new 3 end 4 5 def create 6 @product = current_user.products.build(product_params) 7 if @product.save 8 redirect_to products_path, success: t('defaults.message.register', item: Product.model_name.human) 9 else 10 flash.now[:alert] = t('defaults.message.not_register', item: Product.model_name.human) 11 render :new 12 end 13 end 14 15 private 16 17 def product_params 18 params.require(:product).permit(:product_name, :store_name, :regular_price, :discounted_price) 19 end

app/views/products/new.html.erb

ruby

1<% content_for(:title, t('.title')) %> 2<div class="container"> 3 <div class="row"> 4 <div class="col-md-10 offset-md-1 col-lg-8 offset-lg-2"> 5 <h1><%= t('.title') %></h1> 6 <%= form_with model: @product, local: true do |f| %> 7 <%= render 'layouts/error_messages', model: f.object %> 8 <div class="form-group"> 9 <%= f.label :product_name %> 10 <%= f.text_field :product_name, class: 'form-control' %> 11 </div> 12 <div class="form-group"> 13 <%= f.label :store_name %> 14 <%= f.text_field :store_name, class: 'form-control' %> 15 </div> 16 <div class="form-group"> 17 <%= f.label :regular_price %> 18 <%= f.text_field :regular_price, class: 'form-control' %> 19 </div> 20 <div class="form-group"> 21 <%= f.label :discounted_price %> 22 <%= f.text_field :discounted_price, class: 'form-control' %> 23 </div> 24 <%= f.submit t('defaults.signup'), class: 'btn btn-primary' %> 25 <% end %> 26 </div> 27 </div> 28</div>
shinoharat👍を押しています

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

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

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

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

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

maisumakun

2023/01/15 22:32

regular_priceやdiscounted_priceは、なぜproduct_storesにもたせているのでしょうか?
mameta00

2023/01/15 22:49

元々は別でテーブルを作ろうとしていたのですが、店ごとの通常価格・特別価格を保存出来るようにしたかったので、product_storesのカラムとしてregular_priceやdiscounted_priceをもたせています。
shinoharat

2023/01/16 10:26

現在の商品登録画面の view を質問文に追加して欲しいです。
mameta00

2023/01/16 12:40

追加しました。
shinoharat

2023/01/17 00:01

追加ありがとうございます。 この画面に店舗名(store_name)のテキストフィールドがあるのは何故でしょうか? 「店舗一覧からセレクトボックスなどで選ぶ」みたいな UI を想像してたんですが、そうでないということは、この画面から商品(Product)と店舗(Store)を同時に登録するような機能を作ろうとされているのでしょうか?
mameta00

2023/01/17 03:13

おっしゃる通り、一つの画面で商品名・店舗名・価格を入力し、登録できる形にしたいです。
shinoharat

2023/01/17 06:17

> 商品名・店舗名・価格を入力し、登録できる形 で本当に間違いないですか? 「登録済みの店舗をセレクトボックスで選ぶ」とか 「全店舗を each で表示する」とかじゃなくて、 ここで商品名・店舗名を両方入力するんですか? 例えば、最終的に以下のようなデータを作りたいと仮定します。 ------------------------------------------ JR東京駅店 * みかん(通常50円 / 特別40円) * りんご(通常120円 / 特別100円) 神田小川町店 * みかん(通常55円 / 特別45円) ------------------------------------------ この場合、ユーザーはどのように画面操作するのでしょうか? -- 最初は画面上で以下のような入力を行うと思います。 ------------------------------------------ 商品名  [ みかん     ] 店舗名  [ JR東京駅店  ] 通常価格 [  50 ] 円 特別価格 [  40 ] 円 ------------------------------------------ ここまでは良いです。 でも、次に「JR東京駅店」に新たに「りんご」を追加したい場合はどうするのでしょう? ユーザーはもう一度正確に店舗名を入力しなければならないのでしょうか? もしうっかり店舗名を間違えたら、別の店舗として DB に登録されるのでしょうか? ------------------------------------------ 商品名  [ りんご     ] 店舗名  [ JR東京駅店  ] ← もう一度店舗名を入力させる? 通常価格 [  120 ] 円 特別価格 [  100 ] 円 ------------------------------------------ 「みかん」を別の店舗でも取り扱う場合はどうするのでしょう? こちらも、商品名を一字一句間違えずに入力しなければならないのでしょうか? ------------------------------------------ 商品名  [ みかん     ] ← もう一度商品名を入力させる? 店舗名  [ 神田小川町店  ] 通常価格 [  55 ] 円 特別価格 [  45 ] 円 ------------------------------------------
mameta00

2023/01/17 07:02

確かにもう一度正確に店舗名を入力するのは手間ですね。 そこまで考えが及びませんでした。 登録済みの店舗名はセレクトボックスで選べるようにしたいです。 店舗や価格追加については以下のように考えています。 ------------------------------------------------------------------------------ 商品名  [ みかん     ] 店舗名  [ 神田小川町店 ] 通常価格 [  55 ] 円 特別価格 [  45 ] 円 店舗名  [ JR東京駅店 ] ←このように追加できる 通常価格 [  65 ] 円 特別価格 [  60 ] 円 ------------------------------------------------------------------------------ その時に店舗と価格は紐付いているようにしたいのですが、これは可能なのでしょうか? 例えば、詳細画面を表示した際に下記のように表示させたいです。 ------------------------------------------------------------------------------              みかん    店舗名       特別価格     通常価格 [ 神田小川町店 ]   [  45 ] 円    [  55 ] 円 [ JR東京駅店 ]   [  65 ] 円    [  60 ] 円 ------------------------------------------------------------------------------
shinoharat

2023/01/17 07:26

前置きが長くなっちゃってすみません。 入力画面と詳細画面のイメージについて了解しました。 その仕様なら使いやすそうですね。 > その時に店舗と価格は紐付いているようにしたいのですが、これは可能なのでしょうか? > 例えば、詳細画面を表示した際に下記のように表示させたいです。 もちろん実現可能です。 Rails ガイドの「10 複雑なフォームを作成する」に従って作れば、実装はそんなに難しくないと思います。 https://railsguides.jp/form_helpers.html また、動的に入力項目を増やしたいなら gem cocoon が便利です。 https://github.com/nathanvda/cocoon 回答を書こうと思うんですが、解説を含めると結構長くなるので、しばらくお時間をいただくと思います。
mameta00

2023/01/17 07:36

ありがとうございます。 ご回答お待ちしております。
shinoharat

2023/01/22 16:55

回答が遅くなって申し訳ないです。 さきほど投稿したのでご確認ください。
mameta00

2023/01/23 06:10

とても分かりやすく、且つ理想としていた事が出来るようになりました。 本当にありがとうございます。
guest

回答1

0

ベストアンサー

ステップ1・既存の store の一覧から選択できるよう変更

モデルに accepts_nested_attributes_for :一緒に保存したいアソシエーションの名前 を追加します。

diff

1[models] 2 3 class Product < ApplicationRecord 4 belongs_to :user 5 has_many :product_stores 6 has_many :stores, through: :product_stores 7 8+ accepts_nested_attributes_for :product_stores, reject_if: :all_blank 9 end

model に合わせて view を修正します。
先ほどの accepts_nested_attributes_for に応じた fields_for を定義します。

diff

1[views] 2 3 <%= form_with model: @product, local: true do |f| %> 4 <%= render 'layouts/error_messages', model: f.object %> 5 6 <div class="form-group"> 7- <%= f.label :product_name %> 8- <%= f.text_field :product_name, class: 'form-control' %> 9+ <%= f.label :name %> 10+ <%= f.text_field :name, class: 'form-control' %> 11 </div> 12 13+ <%= f.fields_for :product_stores do |product_stores_form| %> 14 <div class="form-group"> 15- <%= f.label :store_name %> 16- <%= f.text_field :store_name, class: 'form-control' %> 17+ <%= product_stores_form.label :store %> 18+ <%= product_stores_form.select :store_id, @store_list, {}, { class: 'form-control' } %> 19+ <%# @store_list は後程コントローラーにて定義 ↑ %> 20 </div> 21 <div class="form-group"> 22- <%= f.label :regular_price %> 23- <%= f.text_field :regular_price, class: 'form-control' %> 24+ <%= product_stores_form.label :regular_price %> 25+ <%= product_stores_form.text_field :regular_price, class: 'form-control' %> 26 </div> 27 <div class="form-group"> 28- <%= f.label :discounted_price %> 29- <%= f.text_field :discounted_price, class: 'form-control' %> 30+ <%= product_stores_form.label :discounted_price %> 31+ <%= product_stores_form.text_field :discounted_price, class: 'form-control' %> 32 </div> 33+ <% end %> 34 <%= f.submit 'submit', class: 'btn btn-primary' %> 35 <% end %>

view に表示するための @store_list を作成します。

diff

1[controllers] 2 3 class ProductsController < ApplicationController 4+ before_action :set_store_list, only: %i[new create edit update] 5 6 private 7 8+ # セレクトボックスの選択肢 9+ def set_store_list 10+ @store_list = [ 11+ ['選択してください', nil], 12+ *Store.all.pluck(:name, :id), 13+ ] 14+ end

view から送られたデータを正しく受け取れるよう、 controller を修正します。

diff

1[controllers] 2 3 def product_params 4- params.require(:product).permit(:product_name, :store_name, :regular_price, :discounted_price) 5+ params.require(:product).permit(:name, 6+ product_stores_attributes: [ 7+ :id, 8+ :store_id, 9+ :regular_price, 10+ :discounted_price, 11+ ]) 12 end

データの新規作成時は product に紐づく product_stores が存在しないため、 fields_for 部分に何も出力されません。それでは困るので、入力フィールドが画面に表示されるように、コントローラで空データを build します。

diff

1[controllers] 2 3 def new 4 @product = Product.new 5 6+ # 表示用に3件ぶんの空データを作成 7+ 3.times do 8+ @product.product_stores.build 9+ end 10 end

ここまでで、おそらく既存の store から選択して登録する処理はうまくいくと思います。

ステップ2・新規 store を登録できるよう機能追加

ここから少しトリッキーになります。
まずはセレクトボックスの選択肢に「(新規 store を登録)」を追加します。

diff

1[controllers] 2 3 # セレクトボックスの選択肢 4 def set_store_list 5 @store_list = [ 6 ['選択してください', nil], 7 *Store.all.pluck(:name, :id), 8+ ['(新規 store を登録)', 'new-store'], 9 ] 10 end

新規 store 名を入力するための attribute を定義します。

diff

1[models] 2 3 class ProductStore < ApplicationRecord 4+ attr_reader :new_store_name 5 6 belongs_to :product 7 belongs_to :store 8 9+ def new_store_name=(name) 10+ build_store(id: nil, name: name) if name.present? 11+ 12+ @new_store_name = name 13+ end 14 end

画面に入力フィールドを追加します。

diff

1[views] 2 3 <%= f.fields_for :product_stores do |product_stores_form| %> 4 <div class="form-group"> 5 <%= product_stores_form.label :store %> 6 <%= product_stores_form.select :store_id, @store_list, {}, { class: 'form-control' } %> 7+ <%= product_stores_form.text_field :new_store_name, 8+ class: 'form-control', 9+ placeholder: '新しい store の名前を入力してください', 10+ disabled: product_stores_form.object.new_store_name.blank?, 11+ hidden: product_stores_form.object.new_store_name.blank? %> 12 </div>

javascript で入力フィールドの表示・非表示を切り替える機能を実装します。

diff

1[javascript] 2 3+ document.querySelectorAll('select').forEach((select) => { 4+ select.addEventListener('change', () => { 5+ if (select.value === 'new-store') { 6+ select.nextElementSibling.disabled = false; 7+ select.nextElementSibling.hidden = false; 8+ } else { 9+ select.nextElementSibling.disabled = true; 10+ select.nextElementSibling.hidden = true; 11+ } 12+ }); 13+ });

画面パラメータを受け取れるよう permit を変更します。

diff

1[controllers] 2 3 def product_params 4 params.require(:product).permit(:name, 5 product_stores_attributes: [ 6 :id, 7 :store_id, 8+ :new_store_name, 9 :regular_price, 10 :discounted_price, 11 ]) 12 end

ここまでで、画面から新たな store が登録できるようになっているはずです。

ステップ3・一覧画面の変更

特に解説することはないです。
html は適当なので table タグなどを使って良い感じに整えてください。

erb

1<%= @product.name %> 2 3   店舗名       特別価格     通常価格 4<% @product.product_stores.each do |product_store| %> 5  <%= product_store.store.name %> 6  <%= product_store.regular_price %>7  <%= product_store.discounted_price %>8<% end %>

ステップ4・登録画面で動的に入力欄を追加する

いまの登録画面では、固定で3件分の store の入力欄が表示され、それ以上増やすことができません。

回答がかなり長くなってしまったので詳細な解説は省きますが、gem cocoon を導入すればその辺りも解決できると思うので、ドキュメントを確認してみてください。
https://github.com/nathanvda/cocoon

投稿2023/01/22 16:54

編集2023/01/22 16:57
shinoharat

総合スコア1676

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問