Edited at

Rails セレクトボックスの選択肢を動的に表示する

意外に情報がなかったのでメモ。


セレクトボックスのハードコーディングの問題


Viewファイル


views/example/xxxx.html.haml

  = form_for モデルに基づいたインスタンス変数 do |f|

= f.label :quantity, "数量"
= f.select :quantity, [1,2,3,4,5,6,7,8,9,10]
~~省略~~

AmazonのようなECサイトを構築していて遭遇した問題。

selectの選択肢をハードコーディングすると、

在庫が10個未満の商品でもセレクトボックスでは10個まで選択できるようになってしまう。


collection_selectを使えばいいじゃん←この場合うまくいかない

例えば、在庫数をStocksテーブルのcurrent_stockカラムで管理しているとすると、


StocksController.rb

  def show

@stock = Stock.find(params[:id])
@cart = Cart.new
end


views/stocks/show.html.haml

  = form_for @cart do |f|

= f.label :quantity, "数量"
= f.collection_select :quantity, @stock.current_stock, :current_stock, :current_stock #エラー
= f.collection_select :quantity, Stock.all, :current_stock, :current_stock #商品の在庫数のリストになってしまう、欲しいデータではない。

collection_selectの第2引数は、モデルを選択する必要があるので、@stock.current_stockではエラーになるし、Stock.allにするとStocksテーブルの全データになってしまう。


配列を生成して、ビューに渡してあげる

選択できる数量の数だけの配列を作っておくことで解決。

今回の場合、在庫数以下かつ10までのセレクトボックスを生成する。


stocks_controller.rb

  def show

@stock = Stock.find(params[:id])
@cart = Cart.new

@current_stock_array = [] #配列を生成 
@stocks.current_stock.times do |quantity| #@stockの現在の在庫数まで、ループを回す。
if quantity < 10 #quantityが10未満かどうか?
@current_stock_array += quantity + 1
#quantityは0からスタートしているので、1足した数を入れる必要がある。
else
break #ループを抜ける
end
end
end


こうすると、在庫数に応じて、セレクトボックスの選択肢が動的に変わる。


在庫数10以上の場合


stocks_controller.rb

    @current_stock_array = []

@stocks.current_stock.times do |quantity|
if quantity < 10
@current_stock_array += quantity + 1
else
break
end
end
=> @current_stock_array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]


在庫数が5の場合


stocks_controller.rb

    @current_stock_array = []

@stocks.current_stock.times do |quantity|
if quantity < 10
@current_stock_array += quantity + 1
else
break
end
end
=> @current_stock_array = [1, 2, 3, 4, 5]


在庫数が0の場合


stocks_controller.rb

    @current_stock_array = []

@stocks.current_stock.times do |quantity|
if quantity < 10
@current_stock_array += quantity + 1
else
break
end
end
=> @current_stock_array = []


Viewファイル


views/stocks/show.html.haml

  = form_for モデルに基づいたインスタンス変数 do |f|

= f.label :quantity, "数量"
= f.select :quantity, @current_stock_array #Controllerで生成した配列を使うようにする

在庫数が0のときは、選択できないセレクトボックスが表示されるが、View側で在庫0のときにセレクトボックスやカートに入れるボタンを表示させないように制御すればOK。

これでなんとか解決。


2019/1/5 追記

Contoller汚したくないので、Helperに書いて呼び出すほうがいいですね。

Decoratorを導入していれば、それに書くほうがいいと思います。


stocks_controller.rb

    @stock = Stock.find(params[:id])

@cart = Cart.new


views/stocks/show.html.haml

  = form_for モデルに基づいたインスタンス変数 do |f|

= f.label :quantity, "数量"
= f.select :quantity, stock_array_maker(@stock) #Controllerで生成した配列を使うようにする


stocks_helper.rb

  def stock_array_maker(stock)

current_stock_array = [] #配列を生成 
stock.current_stock.times do |quantity| #stockの現在の在庫数まで、ループを回す。
if quantity < 10 #quantityが10未満かどうか?
current_stock_array += quantity + 1
#quantityは0からスタートしているので、1足した数を入れる必要がある。
else
break #ループを抜ける
end
end
current_stock_array # 戻り値
end