Open9

ActiveHashとEnumのまとめ

philosophynotephilosophynote

3種類の値を管理する必要があり、複数箇所で使用する予定がある場合にどちらが良いのかを検討する

philosophynotephilosophynote

それぞれの特徴をまとめる

ActiveHash

https://github.com/active-hash/active_hash

導入はいいので使い方をまとめる

model.rb

# in app/models/country.rb
class Country < ActiveHash::Base
  self.data = [
    {:id => 1, :name => "US"},
    {:id => 2, :name => "Canada"}
  ]
end

# in some view
<%= collection_select :person, :country_id, Country.all, :id, :name %>

delegatedみたいに短縮もできる

model.rb

# app/models/country.rb
class Country < ActiveHash::Base
end

# app/models/person.rb
class Person < ActiveRecord::Base
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to_active_hash :country, :shortcuts => [:name]
end

# config/initializers/data.rb
Rails.application.config.to_prepare do
  Country.data = [
      {:id => 1, :name => "US"},
      {:id => 2, :name => "Canada"}
  ]
end

# Using `rails console`
john = Person.new
john.country_name = "US"
# Is the same as doing `john.country = Country.find_by_name("US")`
john.country_name
# Will return "US", and is the same as doing `john.country.try(:name)`

philosophynotephilosophynote

会社のモデルでは次のように記述している

model.rb

include ActiveHash::Enum

(省略)

enum_accessor :カラム名

ActiveHashは、各レコードに定数を設定することで、そのデータをEnumerationで公開することができます。これにより、ActiveHashクラスに設定された定数を介して、コード内でレコードにアクセスすることができます。

定数として使用するフィールドは、フィールド名を引数に取るenum_accessorを使用して設定します。

model.rb

class Country < ActiveHash::Base
  include ActiveHash::Enum
  self.data = [
      {:id => 1, :name => "US", :capital => "Washington, DC"},
      {:id => 2, :name => "Canada", :capital => "Ottawa"},
      {:id => 3, :name => "Mexico", :capital => "Mexico City"}
  ]
  enum_accessor :name
end

>> Country::US.capital
=> "Washington DC"
>> Country::MEXICO.id
=> 3
>> Country::CANADA
=> #<Country:0x10229fb28 @attributes={:name=>"Canada", :id=>2}

class Town < ActiveHash::Base
  include ActiveHash::Enum
  self.data = [
      {:id => 1, :name => "Columbus", :state => "NY"},
      {:id => 2, :name => "Columbus", :state => "OH"}
  ]
  enum_accessor :name, :state
end

>> Town::COLUMBUS_NY
>> Town::COLUMBUS_OH

定数は、まず単語以外の文字をすべて削除し、その結果をアップスケールして形成されます。つまり、"Blazing Saddles", "ReBar", "Mike & Ike", "Ho! Ho!Ho!" は BLAZING_SADDLES、REBAR、MIKE_IKE、HO_HO_HO となります。

enum_accessor に指定するフィールドは、一意なデータ値を持つ必要がある。

philosophynotephilosophynote

enum

https://techracho.bpsinc.jp/hachi8833/2022_02_18/115735

概ね知っていることが多い

このRailsアプリのPostモデルにはさまざまなステータスがあるので、そのうち指定のステータスのレコードだけを取り出したくなるでしょう。Railsにはこのクエリを解決する動的なメソッドが追加されています。
たとえばステータスがpublishedのpostをすべてフェッチするなら、RailsのコントローラでPost.where(status: "published")のように書くことも一応可能です。

そのように書く代わりに、Postでpublishedメソッドをスコープとして使えます。Railsは、enumにあるすべてのステータスごとに、ステータスと同じ名前のクラスメソッドを動的に追加します。この場合、Postモデルに#draft、#published、#archived、#trashedメソッドが生成されます。

Rails 6では、enumのスコープの否定条件も利用できます。ステータスがpublishedでないpostをすべてフェッチするには、以下のようにpublishedメソッド名の冒頭にnot_を追加できます。

model.rb

Post.published
select "posts".* from "posts" where "posts"."status" = $1 [["status", 1]]

enumの値だけだと意味がわからないこともあるので、enum名にプレフィックスやサフィックスを適用する方がよいでしょう。ここでは、アプリケーションでenumとして定義されたstatusカラムを持つUserモデルを考えてみましょう。

user.rb

class User < ApplicationRecord
  enum :status, { invited: 0, active: 1, deactivated: 2 }
end

# 次のように書く

class User < ApplicationRecord
  enum :status, { invited: 0, active: 1, deactivated: 2 }, suffix: true
end

user.invited_status?

user.active_status!

User.deactivated_status

class User < ApplicationRecord
  enum :status, { invited: 0, active: 1, deactivated: 2 }, prefix: true
end


プレフィックスやサフィックスのないenumを定義すると、Post.freeやpost.published?やpost.premium!がどちらのカラムを参照しているかがわかりにくくなり、開発者が戸惑ってしまいます。
代わりに、以下のようにプレフィックスやサフィックスをメソッドに追加して、必要なメソッドを呼び出せるようにできます。

philosophynotephilosophynote

enum VS active_hashではなく、
数字を1つのカテゴリ名に変換したい場合はenumを使用し、
カテゴリ名以外に情報を持たせたい場合やカテゴリの個数が多い場合ははActive _hashを使用する

従ってenum型を採用するか否かという問題になる