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

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

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


####Viewファイル
```ruby: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カラムで管理しているとすると、

```ruby:StocksController.rb
  def show
    @stock = Stock.find(params[:id])
    @cart = Cart.new
  end
```


```ruby: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までのセレクトボックスを生成する。


```ruby: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以上の場合
```ruby: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の場合
```ruby: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の場合
```ruby: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ファイル

```ruby: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を導入していれば、それに書くほうがいいと思います。

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


```ruby:views/stocks/show.html.haml
  = form_for モデルに基づいたインスタンス変数 do |f|
    = f.label :quantity, "数量"
    = f.select :quantity, stock_array_maker(@stock) 
```


```ruby: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　それなりに需要があるようなのでさらに追記
改めて読んだらループする必要ないコードでした。。。

```ruby:views/stocks/show.html.haml
  = form_for モデルに基づいたインスタンス変数 do |f|
    = f.label :quantity, "数量"
    = f.select :quantity, stock_array_maker(@stock.current_stock) 
```

```ruby:stocks_helper.rb
  def stock_array_maker(current_stock)
    current_stock < 10 ? [*1..current_stock] : [*1..10]
  end
```