许多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应用程序的其他层使用,即控制器、服务、邮件等。
实践证明,如果涉及到生产和维护中的发布,最好是越早越好地遵循这样的做法。