【问题标题】:Reduce unnecessary duplication between two classes减少两个类之间不必要的重复
【发布时间】:2016-01-25 22:54:47
【问题描述】:

我有两个类负责属性验证:

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

它们基本相同。我应该从一个具有受保护实用程序方法的类中继承它们还是什么?

【问题讨论】:

标签: ruby-on-rails ruby refactoring code-duplication


【解决方案1】:

你可以进一步简化

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

每个Validator 都有一个#kind 方法,所以它会添加:name 或:email 作为失败消息,除非被覆盖。然后,您可以让 i18n 按照rails guide 中记录的标准级联进行查找。

【讨论】:

    【解决方案2】:

    仅当一个类明显是另一个类的特例时才使用继承。在您的示例中,这两个类似乎是相等的。然后,使用 mixin,而不是继承。

    您的代码中反对共同化validate_each 的一个小点是NameValidator.valid_name?EmailValidator.valid_email? 的硬编码。您需要在两个类中使用的公共代码中使它们相同。首先,您不需要给出不同的名称valid_name?valid_email?。它们的差异应通过使用各自的类来吸收。使用通用名称。其次,您不需要对接收器进行硬编码。相反,请使用self.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
    

    【讨论】:

      【解决方案3】:

      为了清楚起见,请将它们分开。这些方法足够小,抽象的混淆会使正在发生的事情变得更明显,而不是更明显。

      如果您开始拥有 3、4、5、6 或更多类似的验证器,并且这种模式开始变得明显,那么添加抽象可能会使其更易于理解、更改、依赖或删除。

      【讨论】:

      • 就是这样,我的项目中还有更多的验证器要提取。帖子标题验证,例如..
      • 使用内置的 ActiveModel 格式验证器怎么样? edgeguides.rubyonrails.org/…
      • 不,我完全提取了这个验证器以避免这种情况
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-09
      • 2022-12-17
      • 2018-02-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多