LoginSignup
50
44

More than 3 years have passed since last update.

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

Last updated at Posted at 2018-01-23

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

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

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) 
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

2019/12/07 それなりに需要があるようなのでさらに追記

改めて読んだらループする必要ないコードでした。。。

views/stocks/show.html.haml
  = form_for モデルに基づいたインスタンス変数 do |f|
    = f.label :quantity, "数量"
    = f.select :quantity, stock_array_maker(@stock.current_stock) 
stocks_helper.rb
  def stock_array_maker(current_stock)
    current_stock < 10 ? [*1..current_stock] : [*1..10]
  end
50
44
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
50
44