

# 環境

  * macOS 10.15.6
  * Ruby 2.5.7
  * Rails 5.2.3

# 参考にしたURL

[【Rails】enumチュートリアル](https://pikawaka.com/rails/enum)  
[【Rails】enumとは? enumを用いてselectボックスを作ってみた -
Qiita](https://qiita.com/clbcl226/items/3e832603060282ddb4fd)  
[【Rails】enumを使用したセレクトボックスの実装とDBへの保存 -
Qiita](https://qiita.com/y-suna/items/1f5f34c6289fd88199a5)  
[【Rails】Enumってどんな子？使えるの？ -
Qiita](https://qiita.com/ozackiee/items/17b91e26fad58e147f2e)  
[f.selectで生成されるoptionタグを改変する -
Qiita](https://qiita.com/ATORA1992/items/fc6217412c5bc004ac6c)

# 実現したいこと

  1. 投稿時の:expense カラムのセレクトボックスの選択肢をenum で登録した値(type1, type2, type3)で表示したい

[code]

    # 実現したいセレクトボックスの形
    <select>
      <option value="1">type1</option>
      <option value="2">type2</option>
      <option value="3">type3</option>
    </select>
    
[/code]

  2. DB に保存する値はセレクトボックスのvalue の値(1, 2, 3)をInteger 型で保存したい

[code]

    # 実現したいDB 保存の形
    {
        :name => "name",
        :expense => 1 # enum で登録したvalue値のインデックス番号のInteger 型
    }
    
[/code]

# 問題

DB 保存時に`'1' is not a valid expense` エラーで保存ができません(はじめの数字はenum
で定義したvalue値に対応するインデックス番号)

# 状態

[code]

    # schema.rb
      create_table "destinations", force: :cascade do |t|
        t.string "name"
        t.integer "expense", default: 0
      end
    
[/code]

[code]

    # controller
      def create
        @destination = current_user.destinations.build(destination_params)
        if @destination.save
          flash[:success] = "Destination added!"
          redirect_to destination_path(@destination)
        else
          render 'destinations/new'
        end
      end
    
      private
    
      def destination_params
        params.require(:destination).permit(:name, :expense)
      end
    
[/code]

[code]

    # model
    class Destination < ApplicationRecord
    
      enum expense: {
        "---": 0,
        type1: 1,
        type2: 2,
        type3: 3,
      }
    end
    
[/code]

[code]

    # view
    <%= form_with model: @destination |f| %>
      <%= f.label :expense %>
      <%= f.select :expense, options_for_select(Destination.expenses), {} %>
      <%= f.submit%>
    <% end %>
    
[/code]

[code]

    # 出力されたHTML
    <select name="destination[expense]">
      <option value="0">---</option>
      <option value="1">type1</option>
      <option value="2">type2</option>
      <option value="3">type3</option>
    </select>
    
[/code]

# 原因の予想

`'1' is not a valid expense` で引数に関するエラーという事なのでbinding.pry で確認したところInteger
型で保存するカラムにString 型で値が送られる事によるエラーでないかと予想しました

[code]

    # expense の型を調べる
    [1] pry > destination_params[:expense]
    "3"
    [2] pry > destination_params[:expense].class
    String < Object
    
[/code]

# 試した事

下記を参考にstrong_parameters で保存される値を強制的にInteger 型に変換する方法を試しました

[【Rails】enumを使用したセレクトボックスの実装とDBへの保存 -
Qiita](https://qiita.com/y-suna/items/1f5f34c6289fd88199a5)

[code]

    # controller
    # strong_parameters で:expense をInteger 型に変換
    # 修正前
      def destination_params
        params.require(:destination).permit(:name, :expense)
      end
    
    # 修正後
      def destination_params
        params.require(:destination).permit(:name).merge(expense: params[:destination][:expense].to_i)
      end
    
[/code]

[code]

    # :expense がInteger 型になっているか確認
    [1] pry > destination_params[:expense]
    Unpermitted parameter: :expense
    3
    [2] pry > destination_params[:expense].class
    Unpermitted parameter: :expense
    Integer < Numeric
    
[/code]

## 試した事で起こった新たな問題

  * セレクトボックスのvalue値(1, 2, 3)を保存したいのですが、保存される値がセレクトボックスの選択肢の値(type1, type2, type3)になってしまいます
  * schema.rb で:expense の型をInteger 型に指定しているはずなのにDB に保存できてしまう

[code]

    # :expense に保存されている値を確認
    [1] pry > destination = Destination.first
    {
       :name => "name",
    :expense => "type1", # ここにセレクトボックスのvalue値(1, 2, 3)を保存したい
    }
    [2] pry > destination.expense
    "type1"
    [3] pry > destination.expense.class
    String < Object
    
[/code]

[code]

    # コントローラの@destination に送られる:expense の値を確認
    [1] pry > @destination.expense
    "type1"
    [2] pry > @destination.expense.class
    String < Object
    
[/code]

## 試した事で分からないこと

  * enum で定義した(名前定義: 対応する数値) にどのタイミングで変換されて保存されるのか？
  * なぜschema.rb で指定した型以外でもDB に保存できるのか？

# 教えて頂きたいこと

長々と書いてしまったのでまとめます。

  * enum で定義した値でセレクトボックスを表示したい
  * セレクトボックス選択で保存される値はenum で定義した値のインデックス番号をInteger 型で保存したい

この2点を実現したいです。よろしくお願いします。


CC BY-SA 4.0
2020-12-09T23:56:56.720
72551
2020-12-10T15:21:38.713
2020-12-10T00:51:35.667
3060
25084
question
ruby-on-rails ruby database
enum でセレクトボックスを表示させてインデックス番号をDB に保存したい


Railsのenumはコードの表記上、文字列(またはシンボル）で透過的に扱えるように実装されています

SQLを直接叩いたり、DBのGUIクライアントを使って保存されたレコード情報を見るとわかりますが実際は文字列ではなく数値が保存されています。あくまでRailsの上ではDBに保存された`1`の値が`type1`と表示されているだけです

Railsで実際に保存された値を確認したい場合はenumを定義した際に利用できる`attribute_before_type_cast`を呼び出すことで確認できます

[code]

    > destination.expense_before_type_cast
    => 1
    
[/code]

ドキュメント:
<https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/BeforeTypeCast.html>

以下のコードは同じ意味となり、enumの値`type1`が代入されます。saveすれば数値の`1`がDBに保存されます

[code]

    > destination.expense = 1
    > destination.expense = "type1"
    > destination.expense = :type1
    
[/code]

セレクトボックスのインデックスを保存したい、というのが目的であればenumにしないという選択もあります。現状のままインデックスとしての値が欲しいのであれば
`attribute_before_type_cast`を検討してみてください


CC BY-SA 4.0
2020-12-10T15:21:38.713
72576
2020-12-10T15:21:38.713
298
72551
answer