Rails でDB に保存されたenum の値をattribute_before_type_cast で取得する

# はじめに

Rails でDB に保存されたenum の値を`attribute_before_type_cast` で取得する方法の備忘録です

# 環境

* macOS 10.15.6
* Ruby 2.5.7
* Rails 5.2.3

# 参考URL

<https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/BeforeTypeCast.html>

<https://railsdoc.com/page/options_for_select>

<https://310nae.com/rails-selectbox/>

<https://qiita.com/ozackiee/items/17b91e26fad58e147f2e>

<https://ja.stackoverflow.com/questions/72551/enum-%e3%81%a7%e3%82%bb%e3%83%ac%e3%82%af%e3%83%88%e3%83%9c%e3%83%83%e3%82%af%e3%82%b9%e3%82%92%e8%a1%a8%e7%a4%ba%e3%81%95%e3%81%9b%e3%81%a6%e3%82%a4%e3%83%b3%e3%83%87%e3%83%83%e3%82%af%e3%82%b9%e7%95%aa%e5%8f%b7%e3%82%92db-%e3%81%ab%e4%bf%9d%e5%ad%98%e3%81%97%e3%81%9f%e3%81%84>

# attribute_before_type_cast とは？

* enum の文字列ではなくDB に保存された実態値を取得できる
* enum 定義した際に利用できるインスタンスメソッド

# enum の名前定義の値と実態値について

enum 使用時は値の見え方がRails 上とDB に保存される値が異なるので注意が必要です

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

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

参考URL: <https://ja.stackoverflow.com/questions/72551/enum-%e3%81%a7%e3%82%bb%e3%83%ac%e3%82%af%e3%83%88%e3%83%9c%e3%83%83%e3%82%af%e3%82%b9%e3%82%92%e8%a1%a8%e7%a4%ba%e3%81%95%e3%81%9b%e3%81%a6%e3%82%a4%e3%83%b3%e3%83%87%e3%83%83%e3%82%af%e3%82%b9%e7%95%aa%e5%8f%b7%e3%82%92db-%e3%81%ab%e4%bf%9d%e5%ad%98%e3%81%97%e3%81%9f%e3%81%84>

enum で定義した実態値はRails 上では名前定義として振る舞います。

```Ruby:sample.rb
# 血液型(blood_type) をenum 定義する

  enum blood_type: {
    A型: 0,
    B型: 1,
    O型: 2,
    AB型: 3,
    # Rails 上での名前定義: DB に保存される実態値
  }
```

# 動作確認

下記の想定で動作確認を行います

* Person モデルにname カラム, blood_type カラムを作成
* Person モデルでblood_type をenum で定義

```Ruby:schema.rb
# Parson モデルにname カラム, blood_type カラム を作成
  create_table "persons", force: :cascade do |t|
    t.string "name"
    t.integer "blood_type"
  end
  ```

```Ruby:parson.rb
# model にblood_type をenum で定義
class Person < ApplicationRecord

  enum blood_type: {
    A型: 0,
    B型: 1,
    O型: 2,
    AB型: 3,
  }
end
```

## attribute_before_type_cast メソッド

`rails console` を使って`attribite_before_type_cast` の動作確認

```Shell
$ rails console
> person = Person.create(name: "Alice", blood_type: 0) # person オブジェクトを作成, DB 保存
:name => "Alice"
:blood_type => "A型"
> person.blood_type # rails 上の値を取得
"A型"
> person.blood_type # rails 上の値のクラスを取得
String < Object
> person.blood_type_before_type_cast # DB の値を取得
0
> person.blood_type_before_type_cast # DB の値のクラスを取得
Integer < Numeric
```

## attributes, attributes_before_type_cast メソッド

* `attributes`: インスタンスに対して`属性名 => 値` が取得できるインスタンスメソッド

* `attributes_before_type_cast`: attributes のbefore 版。DB 上の実態値を`属性名 => 値` の形で取得できるインスタンスメソッド

`rails console` を使って`attributes`, `attributes_before_type_cast` の動作の違いを確認します

```Shell
$ rails console
> person = Person.create(name: "Alice", blood_type: 0) # person オブジェクトを作成, DB 保存
:name => "Alice"
:blood_type => "A型"
> person.attributes # rails 上の 名前定義を表示
"name" => "Alice"
"blood_type" => "A型"
> person.attributes_type_before_type_cast # DB の実態値を表示
"name" => "Alice"
"blood_type" => 0
```

# 使用例

私の場合はenum で定義した値から新規投稿フォームを生成するのに使用しました

```HTML
# 出力したいHTML
<select>
  <option value="0">A型</option>
  <option value="1">B型</option>
  <option value="2">O型</option>
  <option value="3">AB型</option>
</select>
```

```Ruby:person.rb
# blood_type をenum で定義
class Person < ApplicationRecord

  enum blood_type: {
    A型: 0,
    B型: 1,
    O型: 2,
    AB型: 3,
  }
end
```

```Ruby:persons_controller.rb
# person オブジェクトを作成
def new
  @person = Person.new
end
```

デフォルト値(selected: value) の設定に`attirbute_before_type_cast` で取得した値を使用しています

```Ruby:new.html.erb
# enum の値からフォーム生成
<%= form_with model: @person local: true do |f| %>
  <%= f.select :blood_type, options_for_select(Person.blood_types, @person.expense_before_type_cast), {}  %>
  <%= f.submit %>
<% end %>
```

```Ruby
# フォーム生成時の各値
<%= form_with model: @モデル名 do |f| %>
  <%= f.select :カラム名, options_for_select(モデル名.カラム名複数形, @モデル名.カラム名_before_type_cast), {}  %>
  <%= f.submit %>
<% end %>
```

**select タグの空カッコ({}) には注意点があります**

> 必ず通常のオプションを設定しない場合でも空のカッコ{},をオプション部分に設定してください。
> この空のカッコがないと、HTMLオプションが無視されてしまうので、id/classを設定したのに効いていない！ということになってしまいます。

参考URL: https://310nae.com/rails-selectbox/

```Ruby
# options_for_select の文法
options_for_select(要素の配列 or ハッシュ, デフォルト値, {オプション}, {htmlオプション})
```

* オプション例
  * `include_blank: true` (先頭行を空にする)
  * `selected: default_value` (デフォルト値)
* HTMLオプション例
  * `id: id_name` (id を付与)
  * `class: class_name` (class を付与)

# まとめ

* enum 使用時にDB に保存された実態値の取得には`attribute_before_type_cast`か`attributes_before_type_cast` を使用する
* enum の名前定義の値と実態値は取得方法が異なる

enum を使った事が無くポートフォリオ作成時に見事にハマってしまったので良い勉強になりました！
間違っている場合はコメント頂けると助かります！