如何抽象 Rails 中多个模型使用的枚举属性?

How to abstract enum attribute used by multiple models in Rails?

我有一个名为 currency 的数据库属性,它被多个模型使用。我想创建一个关注点(或类似的)来定义 currency 属性的基本功能。如果它只用于单个模型,我可能会这样做:

class Transaction < ApplicationRecord
  enum currency: [ :USD, :AUD ] 
  validates :currency, inclusion: { in: currencies.keys }
end

但是因为它被多个模型使用,所以我想做这样的事情:

class Transaction < ApplicationRecord
  include Currency # maybe in a Concern?
  acts_as_currency :currency
end

整个想法是能够执行 Transaction.first.currency.USD? 和枚举属性具有的类似功能,以及在一个地方定义属性验证。

你会如何用 Rails 设计它?有什么首选方法吗?

在 Ruby 中,共享行为需要模块,ActiveSupport 关注点提供了很酷的语法糖,e.g. included

# app/models/concerns/has_currency.rb
module HasCurrency
  extend ActiveSupport::Concern
  included do
    enum currency: [ :USD, :AUD ] 
    validates :currency, inclusion: { in: currencies.keys }
  end
end

class Transaction < ApplicationRecord
  include HasCurrency
end

我们可以通过一个函数来给我们关注的用户更多的自由,正如你似乎建议的那样:

# app/models/concerns/has_currency.rb
module HasCurrency
  def acts_as_currency(column = :currency)
    enum column => [ :USD, :AUD ] 
    validates column, inclusion: { in: column.to_s.pluralize.keys }
  end
end

class Transaction < ApplicationRecord
  extend HasCurrency # Note the `extend`, not `include`
  acts_as_currency
end

基于 Matthieu Libeer 的回答,我建立了这个问题以在我的所有模型中使用。

  module SharedEnums
    def acts_as_enum(*fields)
      @fields = fields
      dynamic_enums
    end

    private

    attr_reader :fields
    attr_accessor :enum_field

    def dynamic_enums
      fields.each do |enum_field|
        @enum_field = enum_field
        case enum_field
        when :attr_1 then format_enum('MODEL.ATTR_1')
        when :attr_2 then format_enum('MODEL.ATTR_2')
        end
      end
    end

    def format_enum(i18n)
      enum_array = I18n.t("activerecord.enums.#{i18n}").stringify_keys.keys
      enum({ "#{enum_field}": Hash[enum_array.zip(enum_array)] })
    end
  end

通过扩展 SharedEnums 并在需要的模型中添加 act_as_enum 方法来使用它非常简单。

  class Model < ApplicationRecord
    extend SharedEnums
    acts_as_enum :attr_1, :attr_2
  end