enumを使ってみる

こんにちは!oda@エンジニア1年目です!

業務では、今まで使っていなかったメソッドなど、様々なコードに触れる機会があります。

今回は、その中でもRuby on Railsのenumについて、整理してみたいと思います。

(以下、Rails 7.0.2.3を使用しています。)

はじめに

enumとは、属性で使う値を名前で参照できるようにする仕組みのことです。

これだけでは、非常にわかりにくいので、以下のコードを実行しながら、確認してみます。

事前準備

まずはFoodsテーブルを作成します。

bin/rails g model Foods name:string category:integer


models/food.rbを以下のように編集して、categoryにenumを設定します。

この時、以下のようにenumを配列で定義すると、:meatには0が、:vegetableには1が、:fruitには2が定義されます。

class Food < ApplicationRecord
  enum :category, [
    :meat,
    :vegetable,
    :fruit
  ]
end


次に、サンプルデータを作成します。

Food.create(name: "牛肉", category: :meat)
Food.create(name: "鶏肉", category: :meat)
Food.create(name: "トマト", category: :vegetable)
Food.create(name: "にんじん", category: :vegetable)
Food.create(name: "玉ねぎ", category: :vegetable)
Food.create(name: "りんご", category: :fruit)
Food.create(name: "みかん", category: :fruit)
Food.create(name: "バナナ", category: :fruit)
Food.create(name: "ぶどう", category: :fruit)
Food.create(name: "")

enumの動作を確認

まずは、DBを確認してみます。categoryには、数値でデータが保存されています。

id  |   name   | category 
----+----------+----------
  1 | 牛肉     |        0
  2 | 鶏肉     |        0
  3 | トマト     |        1
  4 | にんじん   |        1
  5 | 玉ねぎ    |        1
  6 | りんご     |        2
  7 | みかん    |        2
  8 | バナナ    |        2
  9 | ぶどう     |       2
 10 | 鯖       |


以下のように、categoryを呼び出してみると、定義した名前が返ってきます。

なお、鯖のようにcategoryが登録されていない場合は、nilが返ってきます。

food1 = Food.find(1)
food1.name # => "牛肉"
food1.category # => "meat"

food10 = Food.find(10)
food10.name # => "鯖"
food10.category # => nil


また、enumで定義した名前には、自動的にscopeが定義されるため、以下のように呼び出すことが可能です。

scopeとは、モデルに対して実行したいクエリを設定する仕組みです。

なお、notを付けて呼び出した場合、categoryがnilのものは呼び出されないので、注意が必要です。

foods_vegetable = Food.vegetable
foods_vegetable.all
# =>
[#<Food:0x00007f8803e4b758 id: 3, name: "トマト", category: "vegetable">,
 #<Food:0x00007f8803e4b690 id: 4, name: "にんじん", category: "vegetable">,
 #<Food:0x00007f8803e4b5c8 id: 5, name: "玉ねぎ", category: "vegetable">]

foods_not_vegetable = Food.not_vegetable
foods_not_vegetable.all
# =>
[#<Food:0x00007f8803e08ca0 id: 1, name: "牛肉", category: "meat">,                    
 #<Food:0x00007f8803e08bd8 id: 2, name: "鶏肉", category: "meat">,                    
 #<Food:0x00007f8803e08b10 id: 6, name: "りんご", category: "fruit">,                 
 #<Food:0x00007f8803e08a48 id: 7, name: "みかん", category: "fruit">,                 
 #<Food:0x00007f8803e08980 id: 8, name: "バナナ", category: "fruit">,                 
 #<Food:0x00007f8803e088b8 id: 9, name: "ぶどう", category: "fruit">]


さて、それではenumで設定されていないデータを登録しようとするとどうなるのでしょうか。

以下のとおり、エラーが発生し、登録はできません。

Food.create(name: '枝豆', category: :bean)
# => `assert_valid_value': 'bean' is not a valid category (ArgumentError)


DBはそのままで、次のようにenumを追加するとどのようになるでしょうか。

class Food < ApplicationRecord
  enum :category, [
    :meat,
    :fish, # ←ここに追加
    :vegetable,
    :fruit
  ]
end

試しに、トマトを見てみます。

tomato = Food.find(3) 
# => #<Food:0x00007fd188546ad0 id: 3, name: "トマト", category: "fish">

トマトのcategoryがfishに変わってしまいました。

このように、配列の途中に追加すると、enumの採番が変わってしまうので、追加する場合は、最後にする必要があります。

なお、上記のenumは次のようにハッシュを使って、明示的に定義することもできます。

class Food < ApplicationRecord
  enum :category, {
        meat: 0,
        vegetable: 1,
        fruit: 2
    }
end

ハッシュと使うと、次のように連番でない値を設定することもできます。

class Food < ApplicationRecord
  enum :category, {
        meat: 0,
        vegetable: 1,
        fruit: 2,
        others: 9
    }
end

今回は以上となります。

ご覧いただき、ありがとうございました!