如何在Ruby DSL中作为常量的枚举(附代码)

108 阅读2分钟

许多Rails项目都有一个 "常量 "列表,其中有一些预定义的东西,如用户角色、食物类别、公寓类型等。这些东西通常被称为 "枚举"。 在开始时,人们更喜欢一些简单的实现。例如,有人可以在应用程序代码中到处使用符号,如:admin:leader 。这完全没问题......直到改变的时候到来。企业决定将:admin 改名为:superadmin 。在代码中尝试应用这一变化时,最初的实现可能看起来并不那么简单。问题是,仅仅在所有地方将:admin 改名为:superadmin 是不够的。:admin 可以在不同的上下文中使用,可能根本不意味着用户角色。它可能是其他东西,即控制器的范围,或模型,或...你明白的,对吗?

如果有人去做重命名,整个应用程序应该被手动测试。 我不认为,世界上有人会乐意这样做,你呢?

那该怎么做呢?

我建议为这些东西定义常量,称为 "枚举",并把它们放在它们的命名空间中。 请看下面的Ruby片段:

module HasEnumConstants
  class ConstantsBuilder
    def initialize(namespace, const)
      @namespace = namespace
      @collection = @namespace.const_get(const)
    end

    def constant(name)
      val = name.to_s.downcase
      @collection.push(val)
      @namespace.const_set(name, val)
    end
  end

  # Introduces DSL for constants definition.
  # The all defined contants are put into the `collection` constant.
  #
  # Usage example:
  #   class User
  #     extend HasEnumConstants
  #
  #     constants_group :KINDS do
  #       constant :ADMIN
  #       constant :GUEST
  #     end
  #   end
  #   User::KINDS # => ['admin', 'guest']
  #   User::ADMIN # => 'admin'
  #   User::GUEST # => 'guest'
  def constants_group(collection, &block)
    const_set(collection, [])
    ConstantsBuilder.new(self, collection).instance_eval(&block)
    const_get(collection).freeze
  end
end

如果像下面这样通过ApplicationRecord ,所有的模型都会获得允许枚举定义为常量的DSL:

class ApplicationRecord
  extend HasEnumConstants
end

例如,以前的:admin 键可以像这样定义在User 模型上:

class User < ApplicationRecord
  constants_group :KINDS do
    constant :ADMIN
    constant :GUEST
  end
end

感觉一下它是如何工作的:

> User::KINDS # => ['admin', 'guest']
> User::ADMIN # => 'admin'
> User::GUEST # => 'guest'

如果应用这种做法,所有提到:admin 的代码都应该提到常量User::ADMIN 。现在,正如你所看到的,这个名字是非常独特的(因为它的范围是User )。这个东西可能意味着其他东西的机会被减少到最小。

考虑到代码中有一个拼写错误,即User::AMDIN ,而不是User::ADMIN 。 如果代码有良好的覆盖率,在单元测试中,代码将立即失败,并出现一个适当的异常。 有了这个异常,就很容易理解问题所在。 这就是为什么它比内置的ActiveRecord枚举更好。ActiveRecord枚举在强类型和防止开发过程中的错误方面比较弱。 基本上,它并不比只有一堆不相关的符号好多少,就像这篇文章开头的例子。

人们可能会注意到,模块可以被纯Ruby或Rails应用程序的其他层使用,即控制器、服务、邮件等。

实践证明,如果涉及到生产和维护中的发布,最好是越早越好地遵循这样的做法。