Ruby
haml
二次元配列
Rails5
form_for

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

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

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

Viewファイル

views/example/xxxx.html.haml
  = form_for モデルに基づいたインスタンス変数 do |f|
    = f.label :quantity, "数量"
    = f.select :quantity, [[1,1],[2,2],[3,3],[4,4],[5,5],[6,6],[7,7],[8,8],[9,9],[10,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テーブルの全データになってしまう。

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

選択できる数量の数だけの2次元配列を作っておくことで解決。
今回の場合、在庫数以下かつ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 + 1]
        #quantityは0からスタートしているので、1足した数を入れる必要がある。
        #配列の左側がsubmitが押されたときに渡される値、右側が表示する値となる。
      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, quantity + 1]
      else
        break
      end
    end
   => @current_stock_array = [[1,1],[2,2],[3,3],[4,4],[5,5],[6,6],[7,7],[8,8],[9,9],[10,10]]

在庫数が5の場合

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

在庫数が0の場合

stocks_controller.rb
    @current_stock_array = []
    @stocks.current_stock.times do |quantity|
      if quantity < 10
        @current_stock_array << [quantity + 1, 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。
これでなんとか解決。