【Rails】enumチュートリアル

Rails

enumとは、1つのカラムに指定した複数個の定数を保存できる様にする為のモノです。

enumとは、1つのカラムに指定した複数個の定数を保存できる様にする為のモノです。

モデルファイル| enumの定義
1
2
# 定義
enum blood_type: { A: 0, B: 1, O: 2, AB: 3 }
コンソール | enumの使い方
1
2
3
4
5
6
7
# 複数個の定数リスト
User.blood_types
=> {"A"=>0, "B"=>1, "O"=>2, "AB"=>3}

# Userインスタンスのenumカラムの定数を表示
User.find_by(blood_type: 'A').blood_type
=> "A"

このenumを使うと指定した複数個の定数以外の値は保存できない様にしたり、カラムに指定した定数が入っているレコードを取り出すのが容易になったりと多くのエンジニアが頻繁に使用する大変便利なメソッドです。

enumの定義方法

enumの使い方や仕組みについては、定義のされ方とセットで覚えるとenumを簡単に理解できるので
早速enumを使う準備を整えて行きましょう。

enumを使うためには

  1. テーブルにenum用のカラムを用意する
  2. モデルにenumの定義をする

この2つの準備が必要になります。

enumは具体例を持って説明するほうが分かり易いので、プログラマンファミリーを例にenumを説明します。

以下はプログラマンファミリーのそれぞれのメンバーが載っているusersテーブルの情報です。

usersテーブル

id name age
1 programan 25
2 programan_father 58
3 programan_mother 58
4 programan_bigsister 30
5 programan_sister 20
6 programan_bigbrother 28
7 programan_brother 22

このテーブルにenum用のカラムを追加してenumを説明していきます。

tableにenum用のカラムを用意
リンクをコピーしました

enumは、指定した複数個の定数を保存できると言いましたが、その型についてはInteger型かboolean型になります。
複数個の定数を保存できると言ったのになぜデータの型はInteger型かboolean型になるかは後述するので、ここではenumは整数(0, 1, 2等)真偽値(true, false)でDBに保存されるということだけ覚えておきましょう。

integerの場合
リンクをコピーしました

integer型でenumを定義する場合を説明していきます。今tableはこの様なカラムの設計になっております。

db/migrate/create_users.rb | 現在のusersテーブルのカラム設計
1
2
3
4
5
6
7
8
9
10
class CreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string     :name,           null: false, default: ""
      t.integer   :age,              null: false
      t.timestamps
      t.timestamps
    end
  end
end

ここにenum用のカラムblood_typeを追加します。

db/migrate/create_users.rb | 現在のusersテーブルのカラム設計(integer型のカラムを追加)
1
2
3
4
5
6
7
8
9
10
11
12
class CreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string     :name,           null: false, default: ""
      t.integer   :age,              null: false
      # 新しくenum用のinteger型のblood_typeカラムを追加
      t.integer   :blood_type, null: false, default: 0
      t.timestamps
      t.timestamps
    end
  end
end

今回追加したblood_typeカラム(血液型)がないデータは存在しない前提なので、null: falseにしdefault: 0(初期値には0)を指定します。またinteger型は整数値を保存する型で2個以上の定数と紐づけることができます。理由は後述するので、2個以上の定数を紐付けたい場合は型はinteger型で定義すると覚えておきましょう。

booleanの場合
リンクをコピーしました

boolean型でenumを定義する場合を説明していきます。boolean型のenum用のis_marriedカラムを追加します。

db/migrate/create_users.rb | 現在のusersテーブルのカラム設計(boolean型のカラムを追加)
1
2
3
4
5
6
7
8
9
10
11
12
13
class CreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string     :name,           null: false, default: ""
      t.integer   :age,              null: false
      t.integer   :blood_type, null: false, default: 0
      # 新しくenum用のboolean型のis_marriedカラムを追加
      t.boolean      :is_married,   null: false, default: false
      t.timestamps
      t.timestamps
    end
  end
end

今回追加したis_marriedカラム(既婚者かどうかを表すカラム)でも必ずデータは入る前提なので、null: falseにしdefault: false(初期値にはfalse)を指定します。is_marriedカラムがfalseであれば独身を表し、trueであれば既婚者を表すという意味のカラムです。

またboolean型は真偽値を保存する型で2個の定数しか紐づけることができません。理由は後述するので、2個の定数を紐付けたい場合かつ真偽値を保存したい時はboolean型で定義すると覚えておきましょう。

これでテーブルの準備は出来ました。続いてモデルの定義に移っていきます。

Pikawakaマークテーブルでenumカラムを定義するPoint

  1. 2個以上の定数を紐付けたい場合は型はinteger型で定義する。
  2. 2個の定数を紐付けたい場合かつ真偽値を保存したい時はboolean型で定義する。
  3. null: falseの場合は初期値を付ける。

モデルの定義
リンクをコピーしました

テーブルにenumのカラムを用意したら、次はenumのカラムに複数個の定数を紐付けていきます。定義の仕方と一緒に定数の紐付け方とデータ保存のされ方を見ていきましょう。

integerの場合
リンクをコピーしました

enumは1つのカラムに指定した複数個の定数を保存できる様にする為のモノと説明しました。

それは簡単にいうとenumはハッシュで複数個の定数を定義するので、定数の数だけhashの要素を増やすことでリストに登録してる定数を保存できるということです。どういうことか実際に定義と保存をして、データ保存のされ方を見ていきましょう。

app/models/user.rb | enumの定義方法(integerの場合)
1
2
3
class User < ApplicationRecord
  enum blood_type: { A: 0, B: 1, O: 2, AB: 3 }
end

上記の様に定義します。

enum blood_type: { A: 0, B: 1, O: 2, AB: 3 }がどういう意味かというと、blood_typeカラムに0が保存されていればAという定数として扱うということです。その他の値も同様で1が保存されていればB、2が保存されていればO、3が保存されていればABとハッシュの様にそれぞれの整数と定数を紐付けています。

上記の様に定義するとデータはどの様に保存されるか確かめるために、実際にO型のユーザーを作成してみます。

コンソール | O型のユーザーを作成
1
2
3
4
5
6
7
8
9
10
11
12
User.create(
  name: 'programan_mother',
  age: 58,
  # blood_typeに'O'を指定
  blood_type: 'O'
)

INSERT INTO `users` (
  `name`, `age`, `blood_type`, `created_at`, `updated_at`)
VALUES (
  'programan_mother', 58, 2, '2020-01-25 07:01:24', '2020-01-25 07:01:24'
)

これでユーザーを作成できました!O型のユーザーが作成されているか確認してみましょう。

コンソール | O型のユーザーが作成されているか確認
1
2
3
4
5
6
7
8
9
10
11
12
13
user = User.find_by(name: "programan_mother")

=> #<User:0x007fa7232290f8
  id: 1,
  name: "programan_mother",
  age: 58,
  blood_type: "O",
  is_married: false,
  created_at: Sat, 25 Jan 2020 07:01:24 UTC +00:00,
  updated_at: Sat, 25 Jan 2020 07:01:24 UTC +00:00>

user.blood_type
=> "O"

上記の結果を見てみると、実際にO型のユーザーが作成されていることが確認できます。保存されたインスタンスを見てみるとblood_typeにはOと表示されていますね。ただテーブルの型定義では、blood_typeはinteger型で定義されているのを思い出して下さい。

integer型で定義されているのでOという文字は保存できないはずです。実際にSequel Proではどの様にデータが保存されているのでしょうか?

Sequel ProにてO型のユーザーが数字の2で保存されている画像

Sequel Proで確認してみると、数字の2が保存されています。つまりモデルのenumの定義部分でenum blood_type: { A: 0, B: 1, O: 2, AB: 3 }とし、hashのkey部分の定数を保存すると、そのkeyに対応するvalueの整数が保存されるということです!

  • User.create(blood_type: 'A') → DBに0が保存される
  • User.create(blood_type: 'B') → DBに1が保存される
  • User.create(blood_type: 'AB') → DBに3が保存される

このrailsのインスタンス、モデル、DBの関係を表した画像が下記になります。

インスタンス、モデル、DBの関係図

この様にDBのカラムの型をintegerに定義して、モデルに定数と整数をhashの形で紐付けることによって1つのカラムと複数個の定数を紐づけることができたのです。では、もし紐付けた定数以外で保存しようとすれば下記の様なエラーが出ます。

コンソール | モデルで定義した定数以外で保存しようとした時
1
2
User.create(name: 'unknown_blood_type_programan',age: 40, blood_type: '血液型不明')
ArgumentError: '血液型不明' is not a valid blood_type

このエラーはblood_typeに血液型不明なんて登録していないよ!というエラー内容です。つまり冒頭で説明した通り、1つのカラムに指定した複数個の定数以外は保存できないということですね。

この様に指定した複数個の定数を1つのカラムで管理でき、もし指定していない定数を保存しようとすれば弾くことができるenumは非常に便利でよく利用されるメソッドです。

今までの説明でenumの仕組みは分かったと思うので、次は配列でenumを定義する方法を簡単に説明していきます。

配列での定義方法

コンソール | 配列でのenumの定義方法
1
2
3
4
5
6
7
class User < ApplicationRecord
  # シンボルで定義する場合
  enum blood_type: [ :A, :B, :O, :AB ]

  # 文字列で定義する場合
  enum blood_type: [ "A", "B", "O", "AB" ]
end

配列定義のPointは2つあります。

  1. シンボルか文字列を定義する。
  2. 配列の添字と要素の定数が紐付きます。

配列のインデックスは先頭から順に0から数字が入って定数と紐づくので下記の様にDBに保存されます。

  • User.create(blood_type: 'A') → DBに0が保存される
  • User.create(blood_type: 'B') → DBに1が保存される
  • User.create(blood_type: 'O') → DBに2が保存される
  • User.create(blood_type: 'AB') → DBに3が保存される

配列定義でAB型のユーザーを作成

今回はAB型のユーザーを作成しました。配列で定義した通り3が保存されているか確認してみましょう。

Sequel ProにてAB型のユーザーが3で保存されているか確認する画像

上記の画像の通り、3が保存されていることが確認できました。

配列の定義方法もenumの仕組みを理解しているとシンプルですね。保存のされ方を見ると、先ほどのhashの定義と同義の意味であったと理解できたと思います。

app/models/user.rb | enumの定義方法(hashの場合)
1
2
3
class User < ApplicationRecord
  enum blood_type: { A: 0, B: 1, O: 2, AB: 3 }
end

上記のコードは下記のコードと実質同義で、整数と紐づいている定数が同じ・保存のされ方も同じです。

コンソール | 配列でのenumの定義方法
1
2
3
4
5
6
class User < ApplicationRecord
  # シンボルで定義する場合
  enum blood_type: [ :A, :B, :O, :AB ]
  # 文字列で定義する場合
  enum blood_type: [ "A", "B", "O", "AB" ]
end

ですから、基本的に配列で定義してもhashで定義してもどちらでも大丈夫です。特に違いはありません。強いて挙げるならば下記の2つが違いになります。

  1. 配列で定義する方がインデックスが自動で定数と紐づいてくれるので、記述量が少なくすっきりしたコードになる
  2. hashの定数と整数の紐付けは0から始めなくても良い

1は記述の通り配列で定義した方が少なく記述できるので、登録する定数が多くなってくるほどメリットを感じます。

2はどういうことかというと、配列はインデックスが定数と紐づくので必ず0から順に紐付きますが、hashの場合は整数値を指定できるので、enum blood_type: { A: 0, B: 1, O: 2, AB: 3 }としなくてもenum blood_type: { A: 0, B: 10, O: 20, AB: 30 }の様なことができます。hashなら保存される整数値を指定できるということですね。

Pikawakaマークenumのモデルでのinteger定義のPoint

  1. hashで書く場合は、enum blood_type: { A: 0, B: 1, O: 2, AB: 3 }の様に{定数: 整数値}で定義する
  2. 配列で書く場合は、enum blood_type: [ :A, :B, :O, :AB ]の様に配列の要素に定数をいれる
  3. 配列で定義した方が、記述量が少なくて済むので定数を多く保存する場合は配列で書いた方がすっきりする。

booleanの場合
リンクをコピーしました

boolean型で定義するenumの使い方を説明します。boolean型の場合も基本的にenumの仕組みは同じになります。

app/models/user.rb | boolean型でのenum定義
1
2
3
4
class User < ApplicationRecord
  enum blood_type: { A: 0, B: 1, O: 2, AB: 3 }
  enum is_married: { single: false, married: true } # 追加
end

boolean型のenumを使う場合はこの様に定義します。どの様に保存するか見てみましょう。

コンソール | boolean型で既婚のユーザーを作成
1
2
3
4
5
6
7
8
9
10
User.create(
  name: 'programan_bigsister',
  age: 22, 
  blood_type: 'B', 
  # enumで設定した定数を指定
  is_married: 'married'
)

INSERT INTO `users` (`name`, `age`, `blood_type`, `is_married`, `created_at`, `updated_at`) 
  VALUES ('programan_bigsister', 22, 1, TRUE, '2020-01-28 06:32:16', '2020-01-28 06:32:16')

これでユーザーを作成できました!既婚しているユーザーが作成されているか確認してみましょう。

コンソール | 既婚のユーザーが作成されているか確認
1
2
3
4
5
6
7
8
9
10
User.find_by(name: 'programan_bigsister')

=> #<User:0x007f82b384ec38
  id: 3,
  name: "programan_bigsister",
  age: 22,
  blood_type: "B",
  is_married: true,
  created_at: Tue, 28 Jan 2020 06:32:16 UTC +00:00,
  updated_at: Tue, 28 Jan 2020 06:32:16 UTC +00:00>

booleanのenumはintegerで定義した様に定数とそれに紐づく真偽値(false or true)を設定して、定数を保存したらそれに紐づく真偽値を保存します。ではDBにはどの様に保存されているか見てみましょう。

booleanのカラムがDBではどの様に保存されているか表示する画像

booleanの場合、DBではfalseであれば0、trueの場合は1と保存されます。今回作成したprograman_bigsisterは既婚者として保存したので1が保存されています。

この様にbooleanでenumを設定できますが、booleanの場合にenumを利用することはあまりオススメしません。

理由としては、2点あります。

  1. Rails 5.2系でfalseにupdateする際にnullに更新しようとするバグが生じている。
  2. enumを設定しなくてもbooleanだけで充分機能は実装できるのでenumを設定する必要が特にない。

1についてですが現在このprogramanアプリがRails 5.2.1.1のバージョンで動いてます。このバージョンでenumに設定したbooleanのデータをfalseにupdateしようとするとnilでupdateされるバグが確認されています。

コンソール | 既婚のユーザーが作成されているか確認
1
2
3
User.find_by(name: 'programan_bigsister').update(is_married: false)

ActiveRecord::NotNullViolation: Mysql2::Error: Column 'is_married' cannot be null: UPDATE `users` SET `is_married` = NULL,

is_married: 'single'なら正常にupdateされるのですが、falseでupdateすると上記の様なエラーが出ます。また一部enumのメソッドも正常に働かないことがあるので、使用しない方が良さそうですね。

そこで2に話が移るのですが、そもそもenumは指定した複数の定数だけしか保存できないことが魅力なので、booleanの真偽値を入れる場合は、enumを設定しなくても充分にbooleanの役割は果たせます。

私も機能を実装するときは基本的にbooleanのカラムにはenumを設定しません。

ユーザー作成フォームから作成するユーザーのステータスを、独身か既婚者どちらかに設定したい場合があるとします。そのとき、checkboxで既婚か独身かを選ぶのもtrueかfalseの真偽値をformから送信すれば良いだけなので、特に指定した定数は必要ありません。

boolean型のカラムを作成したときは、is_marriedカラムの様に二択しか入らない状態の様なカラムにすることが重要です。その様な設計になっていればboolean型でenumを使用する必要は特にないので、モデルでのboolean型のenumの指定は削除しましょう。

boolean型の記述を削除するgif

以上のことからenumを使うときはinteger型で定義するのをおすすめします。

それでは、次はenumを定義する上でのよくある注意点を説明します。

enumを定義する上での注意点
リンクをコピーしました

enumのカラムを設定するときに気をつけないといけないことは、カラムに予約語を設定することです。

予約語とは、言語側(今回で言うとruby)で既にこの単語は言語で使用しているから使わないでね!という単語のことです。例えばrubyではnilとは何もないという意味があります。これを変数で定義すると、どうなるか見てみましょう。

irb | nilを変数として定義した場合
1
2
nil = '何もない'
SyntaxError: (irb):1: Can't assign to nil

上記の様に、nilは既にruby側で定義しているので変数として定義できないというエラーが出ています。これはつまり、ruby側でnilという単語は何もないという意味で既に予約している(定義している)ので、変数としては使わないでね!ということですね。この様に言語側で既に使うと予約してる単語を予約語と言います。

enumは複数の定数を登録するので、カラム名をActiveRecordで使用している予約語のtypeにしてしまいがちです。予約語はカラム名として使えないためエラーになってしまいますので注意しましょう。

試しに先ほどのblood_typeカラムを予約語のtypeカラムにしてみます。

db/migrate/create_users.rb | 現在のusersテーブルのカラム設計(boolean型のカラムを追加)
1
2
3
4
5
6
7
8
9
10
11
12
13
class CreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string     :name,           null: false, default: ""
      t.integer   :age,              null: false
      # blood_typeカラムを予約語のtypeカラムに変更
      t.integer   :type, null: false, default: 0
      t.boolean  :is_married,   null: false, default: false
      t.timestamps
      t.timestamps
    end
  end
end

↑に変更して、ユーザーを作成してみます。

コンソール | 予約語で登録してみた結果エラーになる。
1
2
3
User.create(name: '予約語type登録', age: 0, type: 'A')

ActiveRecord::SubclassNotFound: The single-table inheritance mechanism failed to locate the subclass: 'A'. This error is raised because the column 'type' is reserved for storing the class in case of inheritance. Please rename this column if you didn't intend it to be used for storing the inheritance class or overwrite User.inheritance_column to use another column for that information.

↑の様なエラーが出ました。いろいろ書かれていますが、注目すべきなのは、This error is raised because the column 'type' is reserved for storing the class in case of inheritance. Please rename this columの部分で、「typeは予約語だからエラーが起こりました、カラム名を変えてください。」という内容です。

これをtypeからblood_typeに書き換えるとうまくいきます。

db/migrate/create_users.rb | typeカラムからblood_typeカラムに変更
1
2
3
4
5
6
7
8
9
10
11
12
13
class CreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string     :name,           null: false, default: ""
      t.integer   :age,              null: false
      # 予約語のtypeからblood_typeに変更
      t.integer   :blood_type, null: false, default: 0
      t.boolean  :is_married,   null: false, default: false
      t.timestamps
      t.timestamps
    end
  end
end

blood_typeカラムに変更しました。登録できるか試してみます。

typeカラムからblood_typeカラムに変更して登録できるか試している動画

登録できましたね!

enumは複数の定数を登録できるのでカラム名をtypeにしたくなりますが、typeは予約語でカラム名に設定するとエラーが出るので注意しましょう。

Pikawakaマークポイント

  1. 予約語は既に言語側で使用すると予約しているので、カラム名などに設定できない
  2. enumは複数の定数を登録できるので、予約語のtypeカラムを使用しがちなので注意しよう

enumの便利なメソッド
リンクをコピーしました

1つのカラムに指定した複数の定数を入れられて、それ以外の定数の場合には弾くことができる便利なモノがenumと伝えてきました。今回は、そんなenumで利用できるさらに便利なメソッドを紹介していきます。この便利なメソッドを使いこなせる様になったら簡潔で読み易いコードになるので、しっかり理解していきましょう!

これまでusersテーブルのレコードがない状態でenumの仕組みを説明してきましたが、これから先はテーブルにレコードがある方が便利なメソッドや使い方について理解し易くなるので、下記のusersテーブルのレコードがある状態でenumについて説明していきます。

usersテーブル

id name age blood_type is_married
1 programan 25 A false
2 programan_father 58 A true
3 programan_mother 58 O true
4 programan_bigsister 30 B true
5 programan_sister 20 AB false
6 programan_bigbrother 28 A true
7 programan_brother 22 O true

確認メソッド
リンクをコピーしました

enumには便利な確認メソッドがあります。確認メソッドとは何かというと、今enumカラム(blood_type)に入っている定数が何なのか確認するメソッドのことです。例題をみていきましょう。

コンソール | 確認メソッド
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
user = User.find_by(name: 'programan')
=> #<User:0x007f82b7b6fe40
  id: 1,
  name: "programan",
  age: 25,
  blood_type: "A",
  is_married: false,
  created_at: Wed, 29 Jan 2020 01:44:45 UTC +00:00,
  updated_at: Wed, 29 Jan 2020 01:44:45 UTC +00:00>

user.blood_type
=> "A"

user.A?
=> true
user.B?
=> false
user.O?
=> false
user.AB?
=> false

user.C?
NoMethodError: undefined method `C?' for #<User:0x007f82b7b6fe40>

確認メソッドは上記の様に使用します。

モデルのインスタンスに対してインスタンス.定数名?の形でenumカラムであるblood_typeに指定した定数が入っていればtrueが返ってきて、指定した定数が入っていなければfalseが返ってきます。

コンソールを見てみるとprogramanの血液型はA型ですね。ですからuser.A?trueが返ってきます。それ以外のB?O?AB?に関しては、指定した定数が該当のインスタンスのblood_typeに入ってないのでfalseが返ってきます。

そして登録していない定数であるuser.C?に関しては、エラーが出ます。

このインスタンス.定数名?の確認メソッドを使うと、簡潔で分かり易いコードになります。
実際の使われ方に関してはenumの確認メソッドを使って条件分岐してデータを表示しようで詳しく説明します。

更新メソッド
リンクをコピーしました

更新メソッドも確認メソッドと同様に、使い方は非常にシンプルです。
更新メソッドとは、今enumカラム(blood_type)に入ってある定数を別の定数に更新するメソッドのことです。
モデルのインスタンスに対してインスタンス.定数名!の形で使います。

コンソール | 更新メソッド
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
user = User.find_by(name: "programan_father")
=> #<User:0x007f82b384d838
  id: 2,
  name: "programan_father",
  age: 58,
  blood_type: "A",
  is_married: true,
  created_at: Wed, 29 Jan 2020 01:44:45 UTC +00:00,
  updated_at: Wed, 29 Jan 2020 01:44:45 UTC +00:00>

user.blood_type
=> "A"

user.B!
UPDATE `users` SET `blood_type` = 1, `updated_at` = '2020-01-29 03:41:33' WHERE `users`.`id` = 2
=> true

user.blood_type
=> "B"

user.A!
UPDATE `users` SET `blood_type` = 0, `updated_at` = '2020-01-29 03:42:40' WHERE `users`.`id` = 2
=> true

user.blood_type
=> "A"

user.C!
NoMethodError: undefined method `C?' for #<User:0x007f82b7b6fe40>

UPDATE users SET blood_type = 1, updated_at = '2020-01-29 03:41:33' WHERE users.id = 2はSQLでidが2のユーザー(programan_father)のblood_typeを1(B)に更新するという意味ですね。実際にuser.blood_typeBと表示されているので、更新されているのが分かると思います。

元々はA型のユーザーなので、またUser.A!でA型のユーザーに戻しています。

もし指定した定数を定義していなければエラーが出ます。

詳しい使い方についてはこちらのajax(非同期通信)を使ってenumの更新メソッドを使ってデータを更新しようで説明します。

検索メソッド
リンクをコピーしました

次はenumカラム(今回であればblood_type)のデータを検索する検索メソッドの使い方について説明します。
この検索メソッドの使い方も非常にシンプルです。
モデルクラスに対してモデルクラス.定数名の形で使用します。

コンソール | 検索メソッド
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
a_type_users = User.A

SELECT `users`.* FROM `users` WHERE `users`.`blood_type` = 0

=> [#<User:0x007f82b50f12e0 
  id: 1, 
  name: "programan", 
  age: 25, 
  blood_type: "A", 
  is_married: false, 
  created_at: Wed, 29 Jan 2020 01:44:45 UTC +00:00, 
  updated_at: Wed, 29 Jan 2020 01:44:45 UTC +00:00>,

 #<User:0x007f82b50f11a0 
  id: 2,
  name: "programan_father",
  age: 58,
  blood_type: "A",
  is_married: true,
  created_at: Wed, 29 Jan 2020 01:44:45 UTC +00:00,
  updated_at: Wed, 29 Jan 2020 03:42:40 UTC +00:00>,

 #<User:0x007f82b50f1060 
  id: 6,
  name: "programan_bigbrother",
  age: 28, 
  blood_type: "A",
  is_married: true,
  created_at: Wed, 29 Jan 2020 01:44:45 UTC +00:00,
  updated_at: Wed, 29 Jan 2020 01:44:45 UTC +00:00>
]

検索メソッドは上記の様にモデルクラスに対してモデルクラス.定数名の形で使用するとenumカラム(blood_type)で指定した定数が保存されている全てのデータを取得するメソッドです。今回の場合だとUser.AとするとA型のユーザーを全て取得しました。

SQLのSELECT users.* FROM users WHERE users.blood_type = 0を実行していて、enumのAという定数は実質DBでは0なので、blood_typeカラムが0のユーザーを全て取得するという意味になってます。
User.AUser.where(blood_type: 0)は同義になります。

Sequel Proにてblood_typeが0のユーザーを取得していることを確認してる画像

上記の画像を見てみると、コンソールの結果と同じでidが1, 2, 6のデータが取得されてますね。この様な仕組みでデータを取得できています。
他の定数も同じ様に取得できます。もちろん定義してない定数を指定するとエラーになります。

コンソール | 検索メソッド
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
b_type_users = User.B
=> [#<#<User:0x007f82b6217010
  id: 4, 
  name: "programan_bigsister", 
  age: 30, 
  blood_type: "B", 
  is_married: true, 
  created_at: Wed, 29 Jan 2020 01:44:45 UTC +00:00, 
  updated_at: Wed, 29 Jan 2020 01:44:45 UTC +00:00>
]

o_type_users = User.O
=> [#<User:0x007f82b3964de8
  id: 3,
  name: "programan_mother",
  age: 58,
  blood_type: "O",
  is_married: true,
  created_at: Wed, 29 Jan 2020 01:44:45 UTC +00:00,
  updated_at: Wed, 29 Jan 2020 01:44:45 UTC +00:00>,
 #<User:0x007f82b39640a0
  id: 7,
  name: "programan_brother",
  age: 22,
  blood_type: "O",
  is_married: true,
  created_at: Wed, 29 Jan 2020 01:44:45 UTC +00:00,
  updated_at: Wed, 29 Jan 2020 01:44:45 UTC +00:00>
]

ab_type_users = User.AB
=> [#<User:0x007f82b5120248
  id: 5,
  name: "programan_sister",
  age: 20,
  blood_type: "AB",
  is_married: false,
  created_at: Wed, 29 Jan 2020 01:44:45 UTC +00:00,
  updated_at: Wed, 29 Jan 2020 01:44:45 UTC +00:00>
]

User.C
NoMethodError: undefined method `C' for #<Class:0x007f82bb9eabc0>

検索メソッドの詳しい使い方については、検索メソッドを使ってenumの属性ごとにデータを取得しようで詳しく説明します。

enumカラム_before_type_cast - 整数値取得メソッド
リンクをコピーしました

整数値取得メソッドとはDBに保存されている整数値を取得するメソッドです。enumカラム名_before_type_castとして使用します。

公式 | before_type_castメソッドの使い方
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
インスタンス.enumカラム名_before_type_cast # 公式

user = User.find_by(blood_type: 'A')
=> [#<User:0x007fc8203c2518
  id: 1, 
  name: "programan", 
  age: 25, 
  blood_type: "A", 
  is_married: false, 
  created_at: Wed, 29 Jan 2020 01:44:45 UTC +00:00, 
  updated_at: Wed, 29 Jan 2020 01:44:45 UTC +00:00>
]

user.blood_type_before_type_cast # 例: enumカラムがblood_typeの時
=> 0

例えばA型ユーザーのDBに保存されているblood_typeの整数値はenum blood_type: { A: 0, B: 1, O: 2, AB: 3 }なので0になりますね。その整数値の0を取得するメソッドが整数値取得メソッドであるenumカラム_before_type_castになります。例題を見てみましょう。

コンソール | enumカラムの形
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
a_type_user = User.find_by(blood_type: 'A')
a_type_user.blood_type_before_type_cast
=> 0

b_type_user = User.find_by(blood_type: 'B')
b_type_user.blood_type_before_type_cast
=> 1

o_type_user = User.find_by(blood_type: 'O')
o_type_user.blood_type_before_type_cast
=> 2

ab_type_user = User.find_by(blood_type: 'AB')
ab_type_user.blood_type_before_type_cast
=> 3

上の例だとa_type_user.blood_type_before_type_castのようにすることで整数値を取得できます。DBの整数値を取得したい時に使用しましょう!

定数リスト取得メソッド
リンクをコピーしました

定数リスト取得メソッドとは、モデルファイルのenumに記載している複数個の定数リストを取得するメソッドです。

user.rb | enumを使って複数個の定数リストを登録している
1
2
3
class User < ApplicationRecord
  enum blood_type: { A: 0, B: 1, O: 2, AB: 3 }
end

この様にモデルファイルに複数個の定数リストをenumでhash形式で定義してます。
このhashを取得するメソッドが定数リスト取得メソッドになります。
モデルクラス.enumカラム名の複数形という形で使用します。

コンソール | 定数リスト取得メソッド
1
2
User.blood_types
=> {"A"=>0, "B"=>1, "O"=>2, "AB"=>3}

上記のようにモデルクラス.enumカラム名の複数形という形で、登録した複数個の定数リストを取得できます。
今回の場合で言うとモデルクラスがUser、enumカラム名がblood_type、その複数形なのでUser.blood_typesとなります。

取得したハッシュである{"A"=>0, "B"=>1, "O"=>2, "AB"=>3}の中で、keyだけ欲しい場合・valueだけ欲しい場合は下記の様に書くと取得できます。

コンソール | 定数リスト取得メソッド
1
2
3
4
5
6
7
#keyだけ欲しい場合
User.blood_types.keys
=> ["A", "B", "O", "AB"]

#valueだけ欲しい場合
User.blood_types.values
=> [0, 1, 2, 3]

この定数リスト取得メソッドは、次の章から始まる実際にenumを使っていこうで何度も活躍するので、実践での使い方は次の章でマスターしてもらえればと思います。

それでは、これまで学んできたenumをrailsアプリでどの様に使用できるか?
次の章で実際に自分の手でenumのソースコードを書いて理解することで、enumを自分が使ってるrailsアプリに組み込みましょう!

実際にenumを使っていこう

今までenumの仕組みや便利なメソッドを解説してきましたが、これからは実際にenumをどういう場面で利用できるのか手を動かして実装していきましょう。enumを理解するためのアプリを作成したので、git cloneして環境を整えていきましょう。

git cloneしてenumのアプリを実行できる環境を作ろう
リンクをコピーしました

enumのrailsアプリケーションの環境を整えるために、下記のコマンドを一つずつ実行していきましょう。

ターミナル | enumアプリをgit clone
1
2
3
4
5
6
$ git clone -b enum https://github.com/miyagit/programan_dojo.git
$ cd programan_dojo

# programan_dojoのディレクトリにいるか確認
$ pwd
/Users/pikawaka/programan_dojo

これでenumアプリケーションのprograman_dojoまで移動できました。次はprograman_dojoの環境を構築していきます。下記の様にbundle installを実行してください。

ターミナル | bundle install
1
2
3
$ bundle install
Bundle complete! 22 Gemfile dependencies, 87 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

bundle installに成功した人は下記リンクをクリックしてDBの作成・データの挿入まで移動しましょう。
DB作成と初期データ投入

bundle installに失敗してrbenv: version '2.4.1' is not installedと表示された場合

下記のコマンドを実行してください。

ターミナル | 2.4.1のrubyのバージョンをinstall
1
2
# bundle installに失敗した人のみ実行してください。うまくいった人はこの部分は無視して進んでください。
$ rbenv install 2.4.1

rbenv installの処理が無事終わった場合は、インストールできているか確認してみましょう。

shell
1
2
$ ls ~/.rbenv/versions | grep 2.4.1
2.4.1

2.4.1と表示されればインストールできています。

rubyのインストール方法について詳しく知りたい方は下記の記事を参考にしてください。
rbenvを使ってrubyをインストールする方法

rubyのバージョンが2.4.1か確認しましょう。

ターミナル | rubyのバージョン確認
1
2
3
4
5
6
7
# programan_dojoのディレクトリにいるか確認
$ pwd
/Users/pikawaka/programan_dojo

# rubyのバージョンが2.4.1であるか確認
$ ruby -v
ruby 2.4.1p111 (2017-03-22 revision 58053) [x86_64-darwin16]

2.4.1と表示されていればrubyのバージョンをprograman_dojoに合わせることができました。
先ほど失敗したbundle installを実行しましょう。

ターミナル | bundle install
1
2
3
$ bundle install
Bundle complete! 22 Gemfile dependencies, 87 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

成功すればenumアプリのDB作成と初期データを入れていきましょう。

DB作成と初期データ投入

データベース作成と初期データを投入するために下記のコマンドを実行します。

ターミナル | DBの作成・データの挿入
1
2
$ rails db:create && rails db:migrate && rails db:seed
環境構築が完了しました。

「環境構築が完了しました。」と表示されると、準備完了です。rails applicationが動作するか確認しましょう。

rails sを実行してください。

ターミナル | railsサーバーの起動
1
2
3
4
5
6
7
8
9
10
$ rails s
=> Booting Puma
=> Rails 5.2.1.1 application starting in development
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.12.0 (ruby 2.4.1-p111), codename: Llamas in Pajamas
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop

ブラウザでlocalhost: 3000と入力して下記のような画面が出てくれば環境構築完了です!
localhost:3000にアクセス

programan_dojoの最初の画面

上記の画像を見てみるとenumをまだ定義していないので、血液型の部分は数字が入っているのが確認出来ます。
それでは、次の章から実際に手を動かしてenumについて理解していきましょう。

formからenumのデータを送信してレコードを作成しよう
リンクをコピーしました

まず今git cloneした状態では、enumを定義していないのでapp/models/user.rbに定義していきます。

app/models/user.rb | enumを定義
1
2
3
4
class User < ApplicationRecord
  # enumを定義
  enum blood_type: { A: 0, B: 1, O: 2, AB: 3 }
end

enumを定義すれば、http://localhost:3000/をリロードしましょう。するとユーザーの血液型の表示変わったと思います。

ユーザー一覧の血液型の表示を変更した動画

これはなぜ変わったかというと、cloneした当初はenumの定義をしていなかったからです。ユーザーの血液型の表示はuser.blood_typeで表示していたので、enumの定義をしていない状態では、DBに保存している数字が出てきました。

enumの定義をしたことでDBの数字とenumが紐づいたので、血液型がしっかり表示される様になりました。

次はユーザー登録ページへ移動しましょう。すると下記の画像が出てきたと思います。

ユーザーの登録フォームの画像

血液型のセレクトボックスに注目してください。こちらはenumの定数リストのセレクトボックスになっております。どうやってこのenumのセレクトボックスを作成しているか解説していきます。

app/views/users/new.html.erbを開いて12行目に注目してみてください。

app/views/users/new.html.erb:12 | enumのセレクトボックス作成の解説
1
<%= f.select :blood_type, User.blood_types.keys.map {|k| [k, k]}, {}, { class: 'form-control', style: 'margin-bottom: 15px;', data: {} } %>

12行目に書かれている記述でenumの定数リストが載っているセレクトボックスを作成しているのですが、注目して欲しいのがUser.blood_types.keys.map {|k| [k, k]}の部分です。この記述の返り値が[["A", "A"], ["B", "B"], ["O", "O"], ["AB", "AB"]]で二次元配列になります。

rails cをしてコンソールで動きを確認してみましょう。

コンソール | User.blood_types.keys.map {|k| [k, k]}の返り値
1
2
3
4
5
6
7
# enumで登録してる血液型の配列を作成
User.blood_types.keys
=> ["A", "B", "O", "AB"]

# ↑の配列からmapメソッドで二次元配列を作成
User.blood_types.keys.map {|k| [k, k]}
=> [["A", "A"], ["B", "B"], ["O", "O"], ["AB", "AB"]]

上記のように二次元配列を取得できたことが分かりますね。mapメソッドの動作が分からない方はmapメソッドの使い方に詳しい使い方が載っているので、参照してみてください。

この二次元配列 [["A", "A"], ["B", "B"], ["O", "O"], ["AB", "AB"]] を下記のセレクトボックスで使う場合、どの様な動作になるか見ていきます。

app/views/uses/new.html.erb:11 | 二次元配列をselectboxで渡したときの動作
1
<%= f.select :blood_type, User.blood_types.keys.map {|k| [k, k]}, {}, { class: 'form-control', style: 'margin-bottom: 15px;', data: {} } %>

二次元配列の1つ目の要素の["A", "A"]の部分に注目してください。["A", "A"]の配列の1つ目の要素がoptionタグの表示部分になり、2つ目の要素にvalueが入ります。

selectboxに二次元配列を使用した場合の解説画像

これが["B", "B"]["C", "C"]["D", "D"]それぞれ展開され、セレクトボックスになります。

app/views/users/new.html.erb | enumのセレクトボックス作成の解説
1
2
3
4
<select class="form-control" style="margin-bottom: 15px;" name="user[blood_type]" id="user_blood_type"><option selected="selected" value="A">A</option>
<option value="B">B</option>
<option value="O">O</option>
<option value="AB">AB</option></select>

投稿フォームの血液型セレクトボックスの画像

enumでセレクトボックスに表示する方法は分かったと思うので、今度はこのenumの値をDBに保存します。
B型のユーザーを一人作成してみましょう。まずフォームに内容を入力します。

血液型B型一郎を保存する動画

app/controllers/users_controller.rbでbinding.pryを加えてみて、送られてきたパラメーターを調べてみましょう。

app/controllers/users_controller.rb:6 | binding.pryの説明
1
2
3
4
5
6
7
8
9
10
def create
  # ↓を追加
  binding.pry
  @user = User.new(user_params)
  if @user.save
    redirect_to root_path, notice: 'userを作成できました。'
  else
    render :new
  end
end

上記のコードの様にbinding.pryを入力してみてください。binding.pryについて知らない方はbinding.pryを徹底解説に詳しく載っているので、参照してみてください。

binding.pryをコントローラーに追加すれば、先ほど入力したフォームでCreate User を押してコンソールを確認しましょう。コンソール画面でparamsと打つと下記の動画の様に送られてきたデータを確認することができます。

pryで止めて保存してる動画

{"blood_type"=>"B"}が送られてきてるのが確認できたので、Bという定数がDBには1という数字で保存されます。

DBに1が保存されている画像

一覧画面で血液型B型一郎が表示されていることを確認できれば、無事保存されています。

血液型B型一郎が表示されている画像

ここまでの説明で投稿フォームの入力からenumがどの様にDBに保存されるかなど、enumの仕組みが分かりましたね。これからはenumの便利なメソッドで説明したenumのメソッドを使って、アプリケーションの中のどの様な場面でenumが活躍するか実際に手を動かして理解していきましょう。


enumの確認メソッドを使って条件分岐してデータを表示しよう
リンクをコピーしました

それでは、enumの確認メソッドを使って条件分岐してデータを表示してみましょう。

例えば、本日の血液型占いの結果が下記だとします。

血液型 順位
B型 1位
O型 2位
AB型 3位
A型 4位

この血液型の占い結果をテーブルで表示します。表示する項目を追加しましょう。
app/views/main/top.html.erbの8行目と16行目を追加しています。

app/views/main/top.html.erb | 血液型占いの項目追加
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div class='p-top'>
  <table border="1" class= 'p-top__introduce'>
    <tr>
    <th>名前</th>
    <th>年齢</th>
    <th>血液型</th>
    <th>婚姻関係?</th>
    <th>血液型占い結果</th> <%# このコードを追加する%>
    </tr>
    <% @users.each do |user| %>
      <tr>
      <td><%= user.name %></td>
      <td><%= "#{user.age}歳" %></td>
      <td><%= "#{user.blood_type}型" %></td>
      <td><%= user.is_married? ? '既婚' : '未婚' %></td>
      <td></td> <%# このコードを追加する%>
      </tr>
    <% end %>
  </table>
</div>

そうすると下記のような表示になったと思います。

テーブルに血液型カラムを追加した画像

まだ血液型占い結果の順位が出ていません。ここから血液型占いの確認メソッドを使って順位を表示します。
下記の様にapp/views/main/top.html.erbのコードを修正してください。

app/views/main/top.html.erb | 占い結果表示(18 ~ 26行目を追加)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<div class='p-top'>
  <table border="1" class= 'p-top__introduce'>
    <tr>
    <th>名前</th>
    <th>年齢</th>
    <th>血液型</th>
    <th>婚姻関係?</th>
    <th>血液型占い結果</th>
    </tr>
    <% @users.each do |user| %>
      <tr>
      <td><%= user.name %></td>
      <td><%= "#{user.age}歳" %></td>
      <td><%= "#{user.blood_type}型" %></td>
      <td><%= user.is_married? ? '既婚' : '未婚' %></td>
      <td>
      <%# 18~26行目を追加%>
       <% if user.A? %>
         ビリ
       <% elsif user.B? %>
         一位
       <% elsif user.O? %>
         二位
       <% elsif user.AB? %>
         三位
       <% end %>
      <%# 18~26行目を追加%>
     </td>
     </tr>
   <% end %>
  </table>
</div>

上のようにソースを変更すると占い結果が表示されました。

テーブルに占い結果が追加された画像

上記のソースの様にif User.A?の様な形でifと一緒に確認メソッドを使うことがよくあります。
今回はビューで使いましたが、コントローラーでもモデルでもifと確認メソッドを一緒に使う場面がよくあるので、確認メソッドはifと一緒に使うことが多いと覚えておきましょう。

ajax(非同期通信)とenumの更新メソッドを使ってデータを更新しよう
リンクをコピーしました

非同期通信とenumの更新メソッドを使って一覧画面から血液型を更新する機能を作っていきます。アプリを作るいろいろな場面で応用できる機能なので、この機会にぜひ使い方を理解してください。

下記の動画のようにセレクトボックスで血液型を変えただけで血液型を更新できる機能を作っていきます。リロードしてもセレクトボックスの値が変更した血液型になってるんで、更新されているのが分かると思います。

更新メソッドを使った場合の完成動画

この機能を作るには大きく分けて4つの実装が必要になります。

  1. 血液型の一覧画面の表示をセレクトボックスにする。
  2. 血液型更新用のルーティングを作成する。
  3. 非同期通信の処理をJSで作成する。
  4. controllerでenumの更新用メソッドを使って血液型を更新する。

1つ1つみていきましょう。

血液型の一覧画面の表示をセレクトボックスにする。
リンクをコピーしました

一覧画面から血液型を更新するには血液型の表示をセレクトボックスに修正しなければいけません。ファイルの内容を下記のように更新しましょう。

app/views/main/top.html.erb:14 | セレクトボックスの表示を修正
1
2
3
4
5
# 変更前
<td><%= "#{user.blood_type}型" %></td>

# 変更後
<td><%= select :blood_type, :name, options_for_select(User.blood_types.keys.map{|k| [k, k]}, selected: user.blood_type), {}, { class: 'js-blood_type form-control', data: { id: user.id } } %></td>

form_forの記述を使えなくなったので、ユーザー登録画面で記述されていたnew.html.erbの血液型セレクトボックスと少し書き方が変わっています。簡単に解説するとselected: user.blood_typeでユーザーの血液型を初期値にしています。第四引数の部分でクラス属性とdata属性を指定していますが、この2つの属性は非同期通信で使用します。

変更すると下記の画像のような表示になったと思います。

一覧画面に血液型占い結果を追加した画像

ちゃんと血液型の部分がセレクトボックスになっていれば無事動作しています。

血液型更新用のルーティングを作成する。
リンクをコピーしました

一覧画面から血液型を変更できるルーティングを作成していきましょう。

config/routes.rb | ルーティングの記述
1
2
3
4
5
6
7
8
9
10
11
12
# 変更前Rails.application.routes.draw do
  root 'main#top'
  resources :users
end

# 変更後
Rails.application.routes.draw do
  root 'main#top'
  resources :users do
    resource :blood_type, only: :update, controller: 'users/blood_type'
  end
end

上記のスニペットを見るとresourcesでupdateアクションを定義しています。

上記の様に記述を変更してrake routesすると、/users/:user_id/blood_typeのルーティングが追加されたと思います。

routingの画像

このルーティングはupdateアクションになっていて、血液型を更新する処理を書いていきます。

/users/:user_id/blood_typeのルーティングが追加されていれば、ルーティングの設定は完了です。

非同期通信の処理をJSで作成する。
リンクをコピーしました

先ほど作成したルーティングに向けてjsファイルからajaxメソッドで処理を飛ばします。
まず下記のJSファイルを作成していきましょう。

app/assets/javascripts/blood_type_update.js | 非同期通信ファイル
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$(document).ready(function() {
  $('.js-blood_type').change(function() {
    var user_id = $(this).data('id');
    var blood_type = $(this).val();

    $.ajax({
      type: 'PATCH',
      url: '/users/' + user_id + '/blood_type',
      data: {
        user: {
          id: user_id,
          blood_type: blood_type
        }
      },
      dataType: 'json',
    })
    .done(function(data) {
      alert(data.user_name + 'さんの血液型を' + data.blood_type + '型に更新しました。');
    })
  });
});

作成したファイルの内容を解説していくと、血液型の一覧画面の表示をセレクトボックスにする。で作成したセレクトボックスのクラス名でjs-blood_typeという名前のセレクトボックスがあったことを思い出してください。

そのセレクトボックスを変えたら処理を実行するようにしています。

3行目のuser_idにはセレクトボックスのdata属性で指定した該当のuserのidを入れ、blood_typeには変更したセレクトボックスのvalueを代入しています。

ここからajaxメソッドを使って非同期通信をしています。

それでは、簡単にajaxメソッドの書き方について説明しますね。指定するのは下記の4つです。
1. type
2. url
3. data
4. done


type

typeにはHTTPメソッドで何を使うか指定します。今回の場合だと血液型の更新なので、PATCHを指定しています。


url
urlには先ほど血液型更新用のルーティングを作成する。で作成したルーティングの'/users/' + user_id + '/blood_type'を指定します。これでセレクトボックスを変えただけで先ほど作成したルーティングに処理が実行されます。


data
dataでは、paramsに何を送るか指定できます。ここでは更新するためのuserのidとどの血液型にするかのblood_typeを指定してます。

app/assets/javascripts/blood_type_update.js:9 | data属性の指定の仕方
1
2
3
4
5
6
data: {
  user: {
    id: user_id,
    blood_type: blood_type
  }
}

上記の様にdataを指定することにより、paramsで下記の様に受け取ることができます。

コンソール | dataを指定した場合のコントローラ側でのparamsの値
1
2
params
=> {"user"=><ActionController::Parameters {"id"=>"2", "blood_type"=>"B"}


done
最後のdoneでは実際にDB側で処理が実行されて血液型が更新された後に、ビュー側でどの様な処理をするかJSで書けます。今回はどのユーザーがどの血液型に更新したかalertで通知する様にしてます。またcontroller側の変数などをdoneの引数のdataに渡すことができます。詳しくは次の章で後述します。


これで非同期通信を使ってセレクトボックスを変えただけで、'/users/' + user_id + '/blood_type'のルーティングで処理が実行される様になりました。非同期通信でセレクトボックスを変えた後にcontrollerの処理が動く様になったので、controllerに更新の処理を書いていきます。

controllerでenumの更新用メソッドを使って血液型を更新する
リンクをコピーしました

'/users/' + user_id + '/blood_type'からリクエストが来たときのcontrollerの処理を書いていきます。

下記にcontrollerファイルを作ってください。

app/controllers/users/blood_type_controller.rb | blood_type_controller.rbを作成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Users::BloodTypeController < UsersController
  def update
    binding.pry
    @user = User.find(params[:user][:id])

    case params[:user][:blood_type]
    when 'A'
      @user.A!
    when 'B'
      @user.B!
    when 'O'
      @user.O!
    when 'AB'
      @user.AB!
    end

    respond_to do |format|
      format.json { render json: {user_name: @user.name, blood_type: @user.blood_type} }
    end
  end
end

この処理についてはbinding.pryを使ってデータがどの様に送られているか見ていきます。

まずセレクトボックスで血液型を変更します。
非同期通信の処理の確認でセレクトボックスを変えたときの動画

血液型を変更し'/users/' + user_id + '/blood_type'に処理が送られたので、binding.pryで処理を見ていきます。

コンソールでformから送られてきた値を確認。blood_types_controller.rbの処理を確認する動画

上の動画を見てみるとparams[:user][:id]にuserのidの2が送られていて、params[:user][:blood_type]には変更した血液型のBが送られているのが分かります。

ここの処理で@user = User.find(params[:user][:id])としてインスタンスを作成し、送られてきた値がBならば@user.B!でB型に更新してます。

respond_toに書いてあるのは、js側に処理を返すフォーマットを指定してます。format.jsonの引数でjson形式で{user_name: @user.name, blood_type: @user.blood_type}とすることで

非同期通信のdoneの部分で引数のdata{user_name: @user.name, blood_type: @user.blood_type}を受け取っています。

doneの処理
1
2
3
4
5
.done(function(data) {
  // data.usernameに@user.nameが入っており、blood_typeに@user.blood_typeが入ってます。
  // data == {user_name: "programan_father", blood_type: "B"}
  alert(data.user_name + 'さんの血液型を' + data.blood_type + '型に更新しました。');
})

この様な原理で、alertにどのユーザーがどの血液型に更新できたか表示されてます。
下記の画像の様にalertが表示されていれば、処理がうまくいってます。


ユーザーの血液型を更新したことをalertで表示している画像

最後にbeofe_actionで処理をまとめたりメソッドに分割して見やすいコードになる様にリファクタリングしましょう。下記の様にファイルを更新してください。

app/controllers/users/blood_type_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Users::BloodTypeController < UsersController
  before_action :set_user, :update_blood_type, only: :update

  def update
    respond_to do |format|
      format.json { render json: {user_name: @user.name, blood_type: @user.blood_type} }
    end
  end

  private

  def set_user
    @user = User.find(params[:user][:id])
  end

  def update_blood_type
    case params[:user][:blood_type]
    when 'A'
      @user.A!
    when 'B'
      @user.B!
    when 'O'
      @user.O!
    when 'AB'
      @user.AB!
    end
  end
end

これで見やすいコードになりました。

before_actionでfindメソッドを使ってインスタンス変数をセットしたり、処理をメソッドに分割することによりupdateアクションの記述が簡潔になりました。

beofore_actionで複数のメソッドを指定する方法について知らない方は、こちらで詳しく解説していますのでご参照ください。

beofore_actionで複数のメソッドを指定する方法について解説

findメソッドを使ってインスタンスを作成する方法について知らない方はこちらをご参照ください。
findについて徹底解説

今回説明した様な一覧画面のセレクトボックスを変えることで、リアルタイムでそのデータを更新する場面(enumの更新メソッドと非同期を使う場面)はよくあるので、ぜひその様な実装があるときは参考にしてもらえればと思います。

検索メソッドを使ってenumの属性ごとにデータを取得しよう
リンクをコピーしました

次はenumの検索メソッドを使って、血液型ごとにユーザーテーブルを作って表示します。
下記の画像の様なページを作成していきます。

検索メソッドを使った場合の完成画像

上記の様な血液型一覧画面を作成するには、まず血液型ごとのユーザーを取得する必要があります。
そしてその後に血液型ごとに取得したユーザーをビューで表示する必要があります。
その手順をこの章では下記の様な順番で進めていきます。

  1. 検索メソッドを使って血液型ごとのユーザーをcontrollerで取得する。
  2. ユーザーテーブルで表示している部分を部分テンプレート化する。
  3. 血液型ごとのユーザーテーブルを表示する。
  4. メソッドチェーンを使って血液型ユーザー一覧表示に条件を追加しよう!

それでは、まずcontrollerで血液型ごとにユーザーを取得しましょう。

検索メソッドを使って血液型ごとのユーザーをcontrollerで取得する。
リンクをコピーしました

まずcontrollerで血液型ごとのユーザー取得メソッドに記述してある様に、User.Aの様に記述することで、血液型ごとのユーザーを取得する必要があります。

app/controllers/main_controller.rb | 血液型ごとのユーザーを取得
1
2
3
4
5
6
7
8
9
10
class MainController < ApplicationController
  def top
    @users = User.all
    # 5 〜 8行目を追加しましょう。
    @a_type_users = User.A
    @b_type_users = User.B
    @o_type_users = User.O
    @ab_type_users = User.AB
  end
end

上記のスニペットの様に変更して、血液型ごとのユーザーを取得できているか確認してください。取得できていれば、次はビュー表示の部分を記述していきましょう。

ユーザーテーブルで表示している部分を部分テンプレート化する。
リンクをコピーしました

完成系の画像を確認してみると、ユーザー一覧のテーブルと血液型ごとのユーザーのテーブルは同じ見た目になっていますね。

ですから、ユーザー一覧のテーブル表示している部分を血液型ごとのユーザーのテーブルの部分でも、共通化して使える様に部分テンプレート化していきます。

app/views/main/top.html.erb | 占い結果表示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<div class='p-top'>
  <p class='p-top__label'>ユーザー一覧</p>
  <table border="1" class= 'p-top__introduce'>
    <tr>
    <th>名前</th>
    <th>年齢</th>
    <th>血液型</th>
    <th>婚姻関係?</th>
    <th>血液型占い結果</th>
    </tr>
    <% @users.each do |user| %>
      <tr>
      <td><%= user.name %></td>
      <td><%= "#{user.age}歳" %></td>
      <td><%= select :blood_type, :name, options_for_select(User.blood_types.keys.map{|k| [k, k]}, selected: user.blood_type), {}, { class: 'js-blood_type form-control', data: { id: user.id } } %></td>
      <td><%= user.is_married? ? '既婚' : '未婚' %></td>
      <td>
       <% if user.A? %>
         ビリ
       <% elsif user.B? %>
         一位
       <% elsif user.O? %>
         二位
       <% elsif user.AB? %>
         三位
       <% end %>
     </td>
     </tr>
   <% end %>
  </table>
</div>

部分テンプレート化する前に↑の記述を↓の記述に変更して、まず占い結果の表示を消しましょう。(4つのテーブルが並んだ場合の幅の問題で小さく表示されるカラムがあるため。)

app/views/main/top.html.erb | 占い結果表示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div class='p-top'>
  <p class='p-top__label'>ユーザー一覧</p>
  <table border="1" class= 'p-top__introduce'>
    <tr>
    <th>名前</th>
    <th>年齢</th>
    <th>血液型</th>
    <th>婚姻関係?</th>
    </tr>
    <% @users.each do |user| %>
      <tr>
      <td><%= user.name %></td>
      <td><%= "#{user.age}歳" %></td>
      <td><%= select :blood_type, :name, options_for_select(User.blood_types.keys.map{|k| [k, k]}, selected: user.blood_type), {}, { class: 'js-blood_type form-control', data: { id: user.id } } %></td>
      <td><%= user.is_married? ? '既婚' : '未婚' %></td>
     </tr>
   <% end %>
  </table>
</div>

すると下記の画像の様に表示されたと思います。
占い結果を消した後のユーザー一覧画像

占い結果の表示を消えていることが確認できれば、このユーザー一覧のテーブルが他の血液型のユーザー一覧テーブルと共通化できる様にまず部分テンプレート化します。

10 ~ 17行目の部分がユーザーテーブルの一覧を表示してる部分なので、その部分を部分テンプレートにします。

app/views/main/top.html.erb | 現状
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div class='p-top'>
  <p class='p-top__label'>ユーザー一覧</p>
  <table border="1" class= 'p-top__introduce'>
    <tr>
    <th>名前</th>
    <th>年齢</th>
    <th>血液型</th>
    <th>婚姻関係?</th>
    </tr>
    <% @users.each do |user| %> # each 〜 endまでを切り出す。
      <tr>
      <td><%= user.name %></td>
      <td><%= "#{user.age}歳" %></td>
      <td><%= select :blood_type, :name, options_for_select(User.blood_types.keys.map{|k| [k, k]}, selected: user.blood_type), {}, { class: 'js-blood_type form-control', data: { id: user.id } } %></td>
      <td><%= user.is_married? ? '既婚' : '未婚' %></td>
     </tr>
   <% end %>
  </table>
</div>

↑上のコメントに書かれている部分を切り出して、下記のファイルを作成してください。

app/views/main/_table_user.html.erb | 部分テンプレートファイルを作成
1
2
3
4
5
6
<tr>
<td><%= user.name %></td>
<td><%= "#{user.age}歳" %></td>
<td><%= select :blood_type, :name, options_for_select(User.blood_types.keys.map{|k| [k, k]}, selected: user.blood_type), {}, { class: 'js-blood_type form-control', data: { id: user.id } } %></td>
<td><%= user.is_married? ? '既婚' : '未婚' %></td>
</tr>

部分テンプレートファイルが作成されたので、先ほど「# each 〜 endまでを切り出す。」とコメントしていた部分をpartialを使って記述を変更します。下記の様にファイルを修正してください。

app/views/main/top.html.erb | eachの部分を部分テンプレート化する。
1
2
3
4
5
6
7
8
9
10
11
<div class='p-top'>
  <table border="1" class= 'p-top__introduce'>
    <tr>
    <th>名前</th>
    <th>年齢</th>
    <th>血液型</th>
    <th>婚姻関係?</th>
    </tr>
    <%= render partial: 'table_user', collection: @users, as: 'user' %>
  </table>
</div>

部分テンプレートのcollectionオプションを使うと、アプリのパフォーマンスがよくなります。
部分テンプレートのcollectionオプションについて知らない方は、こちらでcollectionオプションについて詳しく解説しているので参考にしてください。

部分テンプレートのcollectionオプションを徹底解説

見た目は先ほどと変わりませんが、部分テンプレートを使ってエラーを出さずに先ほどと同じ表示になっているかここで確認してください。

ユーザー一覧がしエラーが出ずに表示されている確認する画像

それでは、ユーザー一覧のテーブルの共通化の準備はできたので、次は血液型ごとのユーザーを表示する準備をしていきます。

血液型ごとのユーザーテーブルを表示する。
リンクをコピーしました

血液型ごとのユーザーを表示するためにファイルを下記の様に修正してください。

app/views/main/top.html.erb:10| 血液型ごとのユーザーを追加
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div class='p-top'>
  <p class='p-top__label'>ユーザー一覧</p>
  <table border="1" class= 'p-top__introduce'>
    <tr>
    <th>名前</th>
    <th>年齢</th>
    <th>血液型</th>
    <th>婚姻関係?</th>
    </tr>
    <%= render partial: 'table_user', collection: @users, as: 'user' %>
  </table>

  <div class='p-top__section'>
    <% User.blood_types.keys.each do |blood_type| %>
      <%= render 'blood_type_box', blood_type: blood_type %>
    <% end %>
  </div>
</div>

14行目の記述User.blood_types.keysの返り値は、血液型一覧が配列でかえって来ます。

コンソール | 血液型一覧を配列で取得
1
2
User.blood_types.keys
=> ["A", "B", "O", "AB"]

完成系の画像を見てみると血液型の数の分だけ、合計4つのテーブルを作る必要があります。

そのテーブルを作るのが4つ必要になるので、4回分blood_type_boxという血液型のテーブルを作る部分テンプレートにblood_typeという変数を渡してレンダリングしています。

それでは_blood_type_box.html.erbに血液型のテーブルを作る部分テンプレートを作成しましょう。
下記の様に部分テンプレートファイルを作成してください。

app/views/main/_blood_type_box.html.erb | 血液型のテーブルを作成
1
2
3
4
5
6
7
8
9
10
11
12
<div class='p-top__section__box'>
  <p class='p-top__section__box__label'><%= "#{blood_type}型一覧" %></p>
  <table border="1" class= 'p-top__section__box__table'>
    <tr>
    <th>名前</th>
    <th>年齢</th>
    <th>血液型</th>
    <th>婚姻関係?</th>
    </tr>
    <%= render partial: 'table_user', collection: blood_type_users(blood_type), as: 'user' %>
  </table>
</div>

2行目の「どの血液型テーブルを表示してるかのラベル」には、渡してきた変数のblood_typeを表示します。
10行目では先ほどの章で作成したユーザーテーブル一覧で、共通の見た目のtable_userを部分テンプレートで表示してます。

collectionに指定しているblood_type_usersは一体何でしょうか?
blood_type_usersは実はまだ定義していません。ただこのcollectionに渡す部分は、血液型ごとのユーザーを渡す必要があります。血液型ごとのユーザーを渡すためにblood_type_usersメソッドを定義しましょう。

application_helper.rbに下記のメソッドを追加してください。

app/helpers/application_helper.rb | blood_type_usersメソッドの処理を追加
1
2
3
4
5
6
7
8
9
10
11
12
13
14
module ApplicationHelper
  def blood_type_users(blood_type)
    case blood_type
    when 'A'
      @a_type_users
    when 'B'
      @b_type_users
    when 'O'
      @o_type_users
    when 'AB'
      @ab_type_users
    end
  end
end

このコードを解説すると、送られてきたblood_typeがAであればA型のユーザー一覧、BであればB型のユーザー一覧、OであればO型のユーザー一覧、ABであればAB型のユーザー一覧を取得してます。

app/views/main/top.html.erb:13 | topのhtmlを一部抜粋
1
2
3
4
5
<div class='p-top__section'>
  <% User.blood_types.keys.each do |blood_type| %>
    <%= render 'blood_type_box', blood_type: blood_type %>
  <% end %>
</div>

上記のコードをもう一度見返してみるとUser.blood_types.keysで血液型一覧を取得しているので、blood_type_boxにはA, B, O, ABの順番で変数が渡されています。

この渡された変数をblood_type_users(blood_type)の引数に設定して、先ほどコントローラーで定義したそれぞれの血液型ユーザー一覧を返り値(例: @a_type_users)に指定することで

  1. blood_typeがAであれば@a_type_users
  2. blood_typeがBであれば@b_type_users
  3. blood_typeがOであれば@o_type_users
  4. blood_typeがABであれば@ab_type_users

と返り値としてcollectionのblood_type_users(blood_type)に返すことができます。

<%= render partial: 'table_user', collection: blood_type_users(blood_type), as: 'user' %>の記述のcollectionに血液型ごとのユーザー一覧を指定することができました。

ここまでで来れば、localhost:3000を表示してください。
下記の完成系の画像になっていれば、うまくいってます。

検索メソッドを使用した場合の完成系画像

メソッドチェーンを使って血液型ユーザー一覧表示に条件を追加しよう!
リンクをコピーしました

先ほどcontrollerで定義した@a_type_usersの様な血液型ごとのユーザーに対してメソッドチェーンで条件を追加できます。条件を追加できる理由はUser.Aの返り値がUser::ActiveRecord_Relationだからです。

メソッドチェーンについて知らない方は、下記のサイトを参考にしてください。

メソッドチェーンについて解説

それでは、メソッドチェーンで下記のスニペットの様に条件を追加してみましょう。

app/controllers/main_controller.rb | 血液型ごとのユーザーを取得
1
2
3
4
5
6
7
8
9
10
11
12
13
class MainController < ApplicationController
  def top
    @users = User.all
    # 20歳~40歳のA型ユーザー一覧
    @a_type_users = User.A.where(age: 20..40)
    # 結婚しているB型ユーザー一覧
    @b_type_users = User.B.where(is_married: true)
    # 結婚していて50代のO型ユーザー一覧
    @o_type_users = User.O.where(age: 50..59).where(is_married: true)
    # 未婚で20代のAB型のユーザー一覧
    @ab_type_users = User.AB.where(age: 21..29).where(is_married: false)
  end
end

上の様な条件を追加してどんな表示になるか見てみましょう。

メソッドチェーンを使った場合の血液型ごとのユーザー一覧の画像

上記の様な表示になればうまくいってます。開発をしている時はenumの検索メソッドを単体で使うというよりかは、メソッドチェーンと併用して他の条件と合わせてデータを取得するケースが多いです。

メソッドチェーンを使うパターンもしっかり理解しておきましょう。

enumとi18nの使い方
リンクをコピーしました

enumとi18nを併用した方法について解説していきます。

i18nとは何かと簡単にいうと、翻訳ファイルと言われています。外国語を日本語に翻訳するファイルとして使われることがあります。

例えば、英語のエラーメッセージを日本語に翻訳したり、韓国人が多くアクセスするページであれば、英語を韓国語に翻訳して表示することもできます。この様な翻訳という意味合いで使われることが多いです。

そこで今回英語から日本語に変換する用途でi18nとenumを併用する方法を説明します。

ユーザー登録ページに移動してください。

下記の画像が今表示されていると思いますが、ここのAが分かりづらいのでAからA型への表示の変更がi18nを使うとできます。

英語の血液型一覧が載っているselectboxの画像

i18nとenumを使うと↑の血液型がAの表示を↓のA型などに変更できます。

i18nで翻訳済みの血液型のセレクトボックス一覧の画像

そして、validationのエラーメッセージを日本語で表示できたりします。

エラーの英語のメッセージの画像

上記↑の様な英語の表示を下記↓の日本語の表示にすることができます。

日本語のエラーメッセージの画像

上記の画像で見せたセレクトボックスのAの表示をA型に変更したり、エラーメッセージを日本語化するといったことがi18nとenumを併用するとできます。

それでは、どうすれば上記の様な日本語翻訳設定ができるのか?その方法を説明していきます。

まず日本語の表示をするには、3つのファイルの変更が必要になります。

  1. config/application.rbにi18nのデフォルトの言語を日本語に設定
  2. config/locales/ja.ymlに英語から日本語への翻訳内容を追加
  3. app/views/users/new.html.erbの記述を変更して日本語表示のselectboxを作成

それでは、1から作業をおこなっていきましょう。

i18nのデフォルトの言語を日本語に設定
リンクをコピーしました

application.rbに下記の様にconfig.i18n.default_locale = :jaを追加することによって、設定を日本語にすることが出来ます。

config/application.rb | application.rbの処理を変更
1
2
3
4
5
6
7
8
9
10
11
12
require_relative 'boot'
require 'rails/all'

Bundler.require(*Rails.groups)

module ProgramanDojo
  class Application < Rails::Application
    config.load_defaults 5.2
    # 下記を追加
    config.i18n.default_locale = :ja
  end
end

これを追加することによって、config/locales/以下にあるja.ymlファイルを読み込みます。
これでi18nの日本語に翻訳するという設定ができました。

application.rbを操作した場合は一度control cでサーバーを停止した後に、rails sをして再起動する様にしてください。再起動しなければapplication.rbに追加したことが反映されないためです。

config/locales/ja.ymlに英語から日本語への翻訳内容を追加
リンクをコピーしました

英語から日本語への翻訳を可能にするためにconfig/locales/ja.ymlを作成します。

config/locales/ja.yml | 英語から日本語への翻訳ファイルを作成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 下記の4~16行目の記述をja.ymlの既に書かれているactiverecordとerrosの間に入れくてください。
ja:
  activerecord:
    models:
      user: ユーザー
    attributes:
      user:
        name: 名前
        age: 年齢
        blood_type: 血液型
        blood_types:
          A: A型
          B: B型
          O: O型
          AB: AB型
        is_married: 婚姻関係
    errors:

この様に書くと日本語化されます。

実際にvalidationのメッセージで日本語化されているか見てみましょう。

user.rbにvalidationの設定をしましょう。

app/models/user.rb | validationの設定を追加
1
2
3
4
class User < ApplicationRecord
  enum blood_type: { A: 0, B: 1, O: 2, AB: 3 }
  validates :name, :age, :blood_type, presence: true
end

validationのpresenceオプションについて知らない方はこちらの記事を参考にしてください。
validationのpresenceオプションについて解説

エラーメッセージの一部が日本語の画像

エラーメッセージが日本語で表示されましたね。ただ今血液型はセレクトボックスの初期値にA型が設定されているので、血液型のエラーメッセージを表示できていません。血液型のエラーメッセージを日本語で表示する方法については後述します。

app/views/users/new.html.erbの記述を変更して日本語表示のselectboxを作成
リンクをコピーしました

それでは日本語表示のセレクトボックスを作って、日本語を選択して送信しても英字で送られるようにしていきます。app/views/users/new.html.erbを開いて、セレクトボックスの記述を修正していきましょう。

app/views/users/new.html.erb:12 | 記述変更
1
2
3
4
5
# 変更前
<%= f.select :blood_type, User.blood_types.keys.map {|k| [k, k]}, {}, { class: 'form-control', style: 'margin-bottom: 15px;', data: {} } %>

# 変更後
<%= f.select :blood_type, options_for_select(User.blood_types.map { |k, _v| [t("activerecord.attributes.user.blood_types.#{k}"), k] }), {include_blank: true}, { class: 'form-control', style: 'margin-bottom: 15px;', data: {} } %>

変更した点は2点です。

  1. 第一引数のUser.blood_types.keys.map {|k| [k, k]}User.blood_types.map { |k, _v| [t("activerecord.attributes.user.blood_types.#{k}"), k] })に変更
  2. 第三引数にinclude_blank: trueを追加

1のUser.blood_types.map { |k, _v| [t("activerecord.attributes.user.blood_types.#{k}"), k] })に変更したことにより、日本語の表示ができる様になりました。

config/locales/ja.yml | 解説
1
2
3
4
5
6
blood_types:
  # keyの部分がt("activerecord.attributes.user.blood_types.#{k}")のkに当たり、A型などに翻訳される。
  A: A型
  B: B型
  O: O型
  AB: AB型

この様な理屈でi18nを使って日本語で表示ができました。
このkに入っているものはmapの一番最初の繰り返しだとkにAが入り、k(A): A型という理屈でA型が表示されているということですね。

そして2の第三引数にinclude_blank: trueを追加することによって、空欄を先頭に追加できます。先ほどはセレクトボックスに空欄がなくてバリデーションのエラーメッセージを表示できなかったんですが、これを追加することによって空で送ってバリデーションのエラーメッセージを表示できる様になります。

まず見た目を確認してみましょう。

血液型を日本語で表示してる画像

上記の様にセレクトボックスに空欄、A型の様にしっかり日本語に変換されていれば実装できています。
では実際に登録できるか確かめてみるのと、エラーを出した時に日本語で表示されるか確認してみましょう。

AB型二郎を追加するgif

上記の様にAB型二郎さんが追加されていればしっかり実装できています。

次はエラーの表示を見てみましょう。

血液型のエラーメッセージを表示するgif

上記の様にエラーメッセージが出ていれば完了です。

i18nとenumを使えば、日本語の表示をしながら実際に保存する値は英語の定数ということができるので、非常に便利です。実際にenumを使って実装するときは必須の機能になると思うので、ぜひ自分の実装に役立ててください。

enum_help・rails6で追加されたenumメソッド

前回までの章で実践で使うenumについての説明は終わりです。
この章からはenumをさらに便利にするgemについての説明やrails6で新しく追加されたenumの新たなメソッドについて解説します。

enum_help
リンクをコピーしました

enum_helpとは、簡単にenumとi18nを結び付けられるgemです。
enumとi18nの使い方では、enumとi18nを使って日本語化する方法について説明しました。mapメソッドとの併用もしており応用的な使い方をしていたと思います。gemを使えばもっと簡単にenumとi18nを結び付けられるので早速enum_helpを使っていきましょう。

enum_helpの使い方について、下記の順番で説明していきます。

  1. enum_helpのインストール
  2. enum_helpの定義
  3. enum_helpの便利なメソッド

enum_helpのインストール
リンクをコピーしました

enum_helpを早速インストールしましょう。

Gemfile | Gemfileにenum_helpの追加
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '2.4.1'

gem 'rails', '~> 5.2.0'
gem 'mysql2', '>= 0.3.18', '< 0.5'
gem 'puma', '~> 3.11'
gem 'sass-rails', '~> 5.0'
gem 'uglifier', '>= 1.3.0'
gem 'pry-rails'
gem 'coffee-rails', '~> 4.2'
gem 'turbolinks', '~> 5'
gem 'jbuilder', '~> 2.5'
gem 'bootsnap', '>= 1.1.0', require: false
gem 'bootstrap', '~> 4.1.1'
gem 'jquery-rails'
gem 'jquery-turbolinks'
# enum_helpを追加
gem 'enum_help'
...

bundle installを実行しましょう。
bundle installした結果にUsing enum_help バージョン名と表示されていれば、インストールできています。

enum_helpの定義
リンクをコピーしました

enum_helpを使用するためにはja.ymlにenum_help専用の記述を追加しなければいけません。

config/locales/ja.yml | enum_helpの定義
1
2
3
4
5
6
7
8
9
ja:
  # 3行目から9行目を追加しましょう。
  enums:
    user:
      blood_type:
        A: A型
        B: B型
        O: O型
        AB: AB型

jaの直下に上記の様な形で追加しましょう。追加すればenum_helpの便利なメソッドを使用できるようになります。早速どんな便利なメソッドを使えるか見ていきましょう。

enum_helpの便利なメソッド
リンクをコピーしました

enum_helpを使うと簡単にenumの定数を日本語に翻訳できます。
例えば、enum_helpを使わずにユーザーの血液型を日本語で表示するにはどうすれば良いでしょうか?

コンソール | enum_helpを使わなかった場合
1
2
3
4
5
6
7
8
9
10
11
12
user = User.find_by(name: 'programan')
=> #<User:0x007f9ab7e01358
  id: 1,
  name: "programan",
  age: 25,
  blood_type: "A",
  is_married: false,
  created_at: Thu, 30 Jan 2020 06:55:05 UTC +00:00,
  updated_at: Fri, 31 Jan 2020 04:48:28 UTC +00:00>

I18n.t("activerecord.attributes.user.blood_types.#{user.blood_type}")
=> "A型"

こうしなければいけました。ただ記述も長いですし、毎回I18nのメソッドを直接指定して呼び出さなければいけません。その点enum_helpを使えば簡単に日本語表示できます。

コンソール | enum_helpを使った場合
1
2
3
4
5
6
7
8
9
10
11
12
user = User.find_by(name: 'programan')
=> #<User:0x007f9ab7e01358
  id: 1,
  name: "programan",
  age: 25,
  blood_type: "A",
  is_married: false,
  created_at: Thu, 30 Jan 2020 06:55:05 UTC +00:00,
  updated_at: Fri, 31 Jan 2020 04:48:28 UTC +00:00>

user.blood_type_i18n
=> "A型"

かなり簡単に書ける様になりましたね!それ以外にも日本語と英語のハッシュも簡単に作るメソッドが用意されています。

コンソール | enum_helpを使った場合
1
2
3
4
5
6
7
8
9
10
# enum_helpを使わずに書いた場合
User.blood_types.map { |k, _v| [I18n.t("activerecord.attributes.user.blood_types.#{k}"), k] }.to_h
=> {"A型"=>"A", "B型"=>"B", "O型"=>"O", "AB型"=>"AB"}

# enum_helpを使って書いた場合
User.blood_types_i18n.invert
=> {"A型"=>"A", "B型"=>"B", "O型"=>"O", "AB型"=>"AB"}

User.blood_types.map { |k, _v| [I18n.t("activerecord.attributes.user.blood_types.#{k}"), k] }.to_h == User.blood_types_i18n.invert
=> true

enum_helpを使わなければ、mapでi18nと定数の配列を作った後にto_hメソッドでハッシュ化しなければいけません。

その点enum_helpを使うと日本語と英語のハッシュを作る際にも非常に簡単になります。
使い所はselectboxなどで有効に使えそうですね!
ぜひenum_helpを導入してenumとi18nの結びつきを簡単にしましょう。

rails6で追加されたenumで便利な機能
リンクをコピーしました

rails6に新たに追加されたenumのメソッドがあります。そのメソッドはenumの否定系メソッドです。
否定系メソッドとは、User.A(検索メソッド)の否定系の様なイメージのメソッドです。
下記の順番でrails6で追加された既存の検索メソッドについて説明します。

  1. 既存の検索メソッドと否定系メソッドの違い
  2. rails5で否定系メソッドを書く場合

既存の検索メソッドと否定系メソッドの違い
リンクをコピーしました

既存の検索メソッドとどう違うか比較するためにまず既存の検索メソッドが見ていきましょう。

コンソール | 検索メソッド
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
User.A

SELECT "users".* FROM "users" WHERE "users"."blood_type" = ?  [["blood_type", 0]]

=> [#<User:0x00007ff37a4c0868
  id: 1,
  name: "programan",
  age: 25,
  blood_type: "A",
  is_married: false>,
  #<User:0x00007ff37a4c07a0
  id: 2,
  name: "programan_father",
  age: 58,
  blood_type: "A",
  is_married: true>,
 #<User:0x00007ff37a4c06d8
  id: 6,
  name: "programan_bigbrother",
  age: 28,
  blood_type: "A",
  is_married: true>
]

この様にUser.AはA型だけのユーザーを取得しています。
SQLの話になりますがWHERE "users"."blood_type" = ? [["blood_type", 0]]のところを見ると、blood_typeの値が0だけのモノを取得していることが分かります。

対して否定系メソッドはA型以外のユーザーを取得します。
モデルクラス.not_定数名で実行します。

コンソール | 否定系の検索メソッドの使い方
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
User.not_A

SELECT "users".* FROM "users" WHERE "users"."blood_type" != ?  [["blood_type", 0]]

=> [#<User:0x00007ff37dc31018
  id: 3,
  name: "programan_mother",
  age: 58,
  blood_type: "O",
  is_married: true>,
 #<User:0x00007ff37dc30eb0
  id: 4,
  name: "programan_bigsister",
  age: 30,
  blood_type: "B",
  is_married: true>,
 #<User:0x00007ff37dc30d70
  id: 5,
  name: "programan_sister",
  age: 20, blood_type: "AB",
  is_married: false>,
 #<User:0x00007ff37dc30c08
  id: 7,
  name: "programan_brother",
  age: 22,
  blood_type: "O",
  is_married: true>,
  #<User:0x00007ff37dc309d8
  id: 8,
  name: "血液型B型一郎",
  age: 40,
  blood_type: "B",
  is_married: false>,
  #<User:0x00007ff37dc30898
  id: 9,
  name: "AB型二郎",
  age: 29,
  blood_type: "AB",
  is_married: false>
]

上記の結果を見るとA型以外のユーザーを取得できていることが分かります。
SQLのWHERE "users"."blood_type" != ? [["blood_type", 0]]のところを見ると!=となっていてblood_typeが0以外のユーザーを取得というSQLの意味になります。

このように否定系メソッドは、モデルクラスに対してモデルクラス.not_定数名で実行するメソッドです。
かなり直感的に書けるので、メソッドがどのような返り値になるか想像しやすいですね。
この否定系メソッドをrails5で書いた場合と比較して、どれくらい直感的に書けているか見てみましょう。

rails5で否定系メソッドを書く場合
リンクをコピーしました

rails5で否定系メソッドを使った場合を見てみます。当然先ほどのモデルクラス.not_定数名で実行するとrails5であれば、エラーになります。

コンソール | rails5でrails6の否定系の検索メソッドを使った場合
1
2
User.not_A
NoMethodError: undefined method `not_A' for #<Class:0x007fa8f556dec0>

ですから、rails5で否定系の検索メソッドを使う場合はwhereメソッドで指定します。

コンソール | rails5で否定系の検索メソッドを使った場合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
User.where.not(blood_type: 'A')
=> [#<User:0x00007ff37dc31018
  id: 3,
  name: "programan_mother",
  age: 58,
  blood_type: "O",
  is_married: true>,
 #<User:0x00007ff37dc30eb0
  id: 4,
  name: "programan_bigsister",
  age: 30,
  blood_type: "B",
  is_married: true>,
 #<User:0x00007ff37dc30d70
  id: 5,
  name: "programan_sister",
  age: 20, blood_type: "AB",
  is_married: false>,
 #<User:0x00007ff37dc30c08
  id: 7,
  name: "programan_brother",
  age: 22,
  blood_type: "O",
  is_married: true>,
  #<User:0x00007ff37dc309d8
  id: 8,
  name: "血液型B型一郎",
  age: 40,
  blood_type: "B",
  is_married: false>,
  #<User:0x00007ff37dc30898
  id: 9,
  name: "AB型二郎",
  age: 29,
  blood_type: "AB",
  is_married: false>
]

rails5で書く場合はwhere.not(blood_type: 'A')と書くとrails6で書いたnot_Aと同じ結果を得られます。

コンソール | rails5とrails6の否定系検索メソッドを比べた場合
1
2
3
4
5
# rails5の場合
User.where.not(blood_type: 'A')

# rails6の場合
User.not_A

人によるかもしれませんが、rails6の否定系メソッドのほうが直感的に書けるし記述が短いですね。今rails6のアプリを使っててenumを使っている人はぜひ新しく追加された否定系メソッドを使ってみてください。

まとめ
  • enumのテーブル定義はinteger型にして、defaultもつけて定義する。
  • モデルには配列かハッシュ形式でenumを使用して定義する。
  • enumには直感的で使いやすいメソッドが用意されている。
  • i18nとenumを使うと日本語表示でenumの機能を使用できる
  • enumにはgemのenum_helpやrails6で新しく追加されたenumメソッドがあるので、enumの基礎を理解できた後に積極的に取り入れよう