Rails: ハッシュを含む列挙型がハッシュキー:値を返さない
概要
私は、性別やステータスなどの静的なモデル属性を持つ Ruby on Rails アプリケーションを構築しています。モデル内でそれらを列挙型として定義することにしました。列挙型を定義するために Ruby-enum gem を使用しています。
Ruby-enum gem をプロジェクトに追加し、バンドルインストールを実行しました。
gem 'ruby-enum', '~> 0.8.0'
ただし、列挙型は他のさまざまなモデルからアクセスできる必要があるため、モデルの懸念ディレクトリ内のモジュールとして列挙型を定義しました。
# app/models/concerns/status.rb
module Status
extend ActiveSupport::Concern
included do
include Ruby::Enum
define :ordered, 'Ordered'
define :cancelled, 'Cancelled'
define :waiting, 'Waiting'
end
end
このモジュールを Users モデルに含めました
class User < ApplicationRecord
include Status
end
そして、私の Users モデルにはステータスの列があります。
create_table "users", force: :cascade do |t|
t.string "first_name"
t.string "last_name"
t.string "status"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
ただし、Rails コンソールで Status モジュールをクエリすると、次のようになります。
Status.all
エラーが発生します:
NoMethodError (undefined method `all' for Status:Module)
Rails コンソールでユーザー モデルのすべてのステータスをクエリすると、次のようになります。
User.statuses.all
エラーが発生します:
ArgumentError (wrong number of arguments (given 2, expected 0..1))
これをビューで利用できるようにする方法もわかりません
# app/views/users/_form.html.erb
<%= form.label :status %><br />
<%= form.select :status, User.statuses.keys.collect { |status| status },{} %>
列挙型を定義し、ユーザーのフォーム ビューで列挙型のキー値を返し、フォームでステータスを選択できるようにするにはどうすればよいですか?
解決策
.all は、ActiveRecord クラスのすべてのレコードを要求します。ステータスは ActiveRecord クラスではありません。それはクラスですらない、モジュールです。
User.statuses.all に関しては、ステータスがどこで定義されているかがわかりません。
Ruby::Enum にはいくつかの定数とメソッドが追加されていますが、Rails はそれらについて知りません。これらを自分で Rails に統合する必要があります。
Rails にはすでに enum が統合されています。
module Status
extend ActiveSupport::Concern
included do
# It will take an Array, but it's good practice to be explicit.
enum status: {ordered: 0, cancelled: 1, waiting: 2}
end
end
User.statuses はステータスの HashWithIndependentAccess を返します。
これらの列挙型は整数にマップされ、文字列は列挙型のポイントを無効にすることに注意してください。ステータスを整数として保存すると、潜在的に多くのスペースを節約できます。 Rails は、整数を文字列にマッピングしたり、その逆のマッピングを処理します。
整数のステータスを使用するようにテーブルを必ず変更してください: t.string “status”。ステータスがオンのユーザーを回避するには、デフォルトまたは null のいずれかを定義する必要があります: false。
データの表示方法の詳細はデータベースに含まれていません。最も簡単な方法は、humanize を使用することです。
# ordered becomes Ordered. not_waiting becomes Not waiting.
User.statuses.keys.collect { |status| status.humanize }
それをステータスモジュールにプッシュできます。
module Status
...
class_methods do
def show_statuses
statuses.keys.collect { |status| status.humanize }
end
end
end
User.show_statuses
これを選択として実行すると…
<%= f.select :status, User.statuses.collect { |status,id| [status.humanize,id] } %>
また、それをステータスにプッシュすることもできます。
module Status
...
class_methods do
...
def statuses_for_select
statuses.collect { |status,id| [status.humanize,id] }
end
end
end
<%= f.select :status, User.statuses_for_select %>
表示の詳細が複雑になると、モデルが太くなります。次に、それをデコレータにプッシュすることを検討する必要があります。