减少两个 类 之间不必要的重复
Reduce unnecessary duplication between two classes
我有两个 classes 负责属性验证:
class NameValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
message = options.fetch(:message, I18n.t('errors.attributes.name.invalid'))
record.errors[attribute] << message unless NameValidator.valid_name?(value)
end
def self.valid_name?(name)
name =~ /\A[a-z][\w\p{Blank}]+\z/i
end
end
和第二个
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
message = options.fetch(:message, I18n.t('errors.attributes.email.invalid'))
record.errors[attribute] << message unless EmailValidator.valid_email?(value)
end
def self.valid_email?(email)
email =~ /\A.+@.+\..+\z/i
end
end
他们基本上是一样的。我应该从一个 class 继承它们并使用受保护的实用方法还是什么?
为了清晰起见,将它们分开。这些方法足够小,抽象的混淆会使正在发生的事情更明显,而不是更不明显。
如果您开始拥有 3、4、5、6 或更多类似的验证器,并且此模式开始变得明显,添加抽象可能会使其更易于理解、更改、依赖或删除。
仅当一个 class 显然是另一个的特例时才使用继承。在您的示例中,这两个 class 似乎是相等的。然后,使用 mixin,而不是继承。
您的代码中反对通用化 validate_each
的一个小问题是 NameValidator.valid_name?
和 EmailValidator.valid_email?
的硬编码。您需要在两个 classes 中使用的公共代码中使它们相同。首先,你不需要给valid_name?
和valid_email?
取不同的名字。它们的差异应该通过使用各自的 classes 来吸收。使用通用名称。其次,您不需要对接收器进行硬编码。相反,使用 self.class
。但与其使用 class 方法,不如使用实例方法。
module ValidatorModule
def validate_each(record, attribute, value)
message = options.fetch(:message, I18n.t("errors.attributes.#{attribute}.invalid"))
record.errors[attribute] << message unless valid?(value)
end
end
class NameValidator < ActiveModel::EachValidator
include ValidatorModule
def attribute; "name" end
def valid?(value); value =~ /\A[a-z][\w\p{Blank}]+\z/i end
end
class EmailValidator < ActiveModel::EachValidator
include ValidatorModule
def attribute; "email" end
def valid?(value); value =~ /\A.+@.+\..+\z/i end
end
如果您认为验证总是使用单个正则表达式完成,则可以更进一步:
module ValidatorModule
def validate_each(record, attribute, value)
message = options.fetch(:message, I18n.t("errors.attributes.#{attribute}.invalid"))
record.errors[attribute] << message unless value =~ validation_pattern
end
end
class NameValidator < ActiveModel::EachValidator
include ValidatorModule
def attribute; "name" end
def validation_pattern; /\A[a-z][\w\p{Blank}]+\z/i end
end
class EmailValidator < ActiveModel::EachValidator
include ValidatorModule
def attribute; "email" end
def validation_pattern; /\A.+@.+\..+\z/i end
end
你可以进一步简化这个
class PatternValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
message = options.fetch(:message) || kind
record.errors[attribute] << message unless value =~ validation_pattern
end
end
class NameValidator < PatternValidator
def validation_pattern; /\A[a-z][\w\p{Blank}]+\z/i end
end
class EmailValidator < PatternValidator
def validation_pattern; /\A.+@.+\..+\z/i end
end
EachValidator 都有一个#kind 方法,因此它将添加 :name 或 :email 作为失败消息,除非被覆盖。然后您可以让 i18n 按照 rails guide.
中记录的标准级联进行查找
我有两个 classes 负责属性验证:
class NameValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
message = options.fetch(:message, I18n.t('errors.attributes.name.invalid'))
record.errors[attribute] << message unless NameValidator.valid_name?(value)
end
def self.valid_name?(name)
name =~ /\A[a-z][\w\p{Blank}]+\z/i
end
end
和第二个
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
message = options.fetch(:message, I18n.t('errors.attributes.email.invalid'))
record.errors[attribute] << message unless EmailValidator.valid_email?(value)
end
def self.valid_email?(email)
email =~ /\A.+@.+\..+\z/i
end
end
他们基本上是一样的。我应该从一个 class 继承它们并使用受保护的实用方法还是什么?
为了清晰起见,将它们分开。这些方法足够小,抽象的混淆会使正在发生的事情更明显,而不是更不明显。
如果您开始拥有 3、4、5、6 或更多类似的验证器,并且此模式开始变得明显,添加抽象可能会使其更易于理解、更改、依赖或删除。
仅当一个 class 显然是另一个的特例时才使用继承。在您的示例中,这两个 class 似乎是相等的。然后,使用 mixin,而不是继承。
您的代码中反对通用化 validate_each
的一个小问题是 NameValidator.valid_name?
和 EmailValidator.valid_email?
的硬编码。您需要在两个 classes 中使用的公共代码中使它们相同。首先,你不需要给valid_name?
和valid_email?
取不同的名字。它们的差异应该通过使用各自的 classes 来吸收。使用通用名称。其次,您不需要对接收器进行硬编码。相反,使用 self.class
。但与其使用 class 方法,不如使用实例方法。
module ValidatorModule
def validate_each(record, attribute, value)
message = options.fetch(:message, I18n.t("errors.attributes.#{attribute}.invalid"))
record.errors[attribute] << message unless valid?(value)
end
end
class NameValidator < ActiveModel::EachValidator
include ValidatorModule
def attribute; "name" end
def valid?(value); value =~ /\A[a-z][\w\p{Blank}]+\z/i end
end
class EmailValidator < ActiveModel::EachValidator
include ValidatorModule
def attribute; "email" end
def valid?(value); value =~ /\A.+@.+\..+\z/i end
end
如果您认为验证总是使用单个正则表达式完成,则可以更进一步:
module ValidatorModule
def validate_each(record, attribute, value)
message = options.fetch(:message, I18n.t("errors.attributes.#{attribute}.invalid"))
record.errors[attribute] << message unless value =~ validation_pattern
end
end
class NameValidator < ActiveModel::EachValidator
include ValidatorModule
def attribute; "name" end
def validation_pattern; /\A[a-z][\w\p{Blank}]+\z/i end
end
class EmailValidator < ActiveModel::EachValidator
include ValidatorModule
def attribute; "email" end
def validation_pattern; /\A.+@.+\..+\z/i end
end
你可以进一步简化这个
class PatternValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
message = options.fetch(:message) || kind
record.errors[attribute] << message unless value =~ validation_pattern
end
end
class NameValidator < PatternValidator
def validation_pattern; /\A[a-z][\w\p{Blank}]+\z/i end
end
class EmailValidator < PatternValidator
def validation_pattern; /\A.+@.+\..+\z/i end
end
EachValidator 都有一个#kind 方法,因此它将添加 :name 或 :email 作为失败消息,除非被覆盖。然后您可以让 i18n 按照 rails guide.
中记录的标准级联进行查找