如何解决一个问题包括参数的另一个问题

时间:2014-12-28 02:34:58

标签: ruby-on-rails ruby

假设您有来自ActiveSupport :: Concern文档的此代码,但您希望包含的Foo块具有不同的内容,具体取决于包含Foo的模块或类。

在我试图解决的具体问题中,我有一组地址验证,但地址字段将命名为home_zip_code或work_zip_code,我希望包含验证问题以了解zip_code的前缀字段。

require 'active_support/concern'

module Foo
  extend ActiveSupport::Concern
  included do
    # have some_value be accessible 
    def self.method_injected_by_foo
      ...
    end
  end
end

module Bar
  extend ActiveSupport::Concern
  # set some_value that will used when Foo is included 
  include Foo

  included do
    self.method_injected_by_foo
  end
end

class Host
  include Bar # It works, now Bar takes care of its dependencies
end

我在这里讨论了这个问题:http://forum.railsonmaui.com/t/how-make-a-concern-parameterized/173

以下2个选项有效。我想知道哪个更好。

使用类方法的关注

这是需要“参数化”的问题:

module Addressable
  extend ActiveSupport::Concern

  included do
    zip_field = "#{address_prefix}_zip_code".to_sym

    zip_code_regexp = /^\d{5}(?:[-\s]\d{4})?$/

    validates zip_field, format: zip_code_regexp, allow_blank: true
end

在找到包含可寻址关注点之前,我找到了两种设置address_prefix的方法。

当关注模块包含在类

中时

在包含关注点

之前,需要定义类方法
cattr_accessor :address_prefix
self.address_prefix = "home"
include Addressable

或者像这样

def self.address_prefix
    "home"
end
include Addressable

当关注模块包含在另一个模块中时

这里的技巧是覆盖self.append_features并添加方法。

  def self.append_features(base)
    base.class_eval do
      def self.address_prefix
        "home"
      end
    end
    super
  end

  def self.append_features(base)
    base.cattr_accessor :address_prefix
    base.address_prefix = "home"
    super
  end

问题

  1. 更可取的是cattr_accessor方式还是定义类方法?
  2. 对于担忧情况下的问题,是否覆盖self.append_features正确的挂钩?
  3. class_eval是否正确调用创建方法,而不是class exec?或者,如果代码不需要访问实例变量,则无关紧要。 Module docs here
  4. 我怎么能把这个问题包括两次,比如前缀为“work”和前缀为“home”,这样验证就适用于两者。显然在包含类上设置类方法是行不通的。或者,如果在夹杂物之间重新定义方法,它可能会这样吗?任何更清洁的方式?

1 个答案:

答案 0 :(得分:0)

cattr_accessor或类方法是我以前使用的一种好方法。 cattr_accessor定义了一个方法,所以也可以。差异涉及继承,我认为cattr_accessor变量不会被继承。如果这可能是一个问题,可能要调查一下。

但实际上我现在会以完全不同的方式来处理。对我来说,最简单的方法是定义一个可以接受参数的类方法,例如acts_as_addressable(address_prefix。您可以在模块Addressible中定义此方法,您可以在其中进行以下操作:

extend Addressible
acts_as_addressbile('home')
acts_as_addressible('work')

您可以采用许多宝石采用的快捷方式,并实际上将您的模块强制插入ActiveRecord::Base,从而避免您在每个课程中都需要使用extend,但是我个人讨厌这种样式。 / p>

根据您所包含的块在做什么,也许validates_zip_code是类方法的更好名称。对于样式,我建议您实际使用该字段的全名。稍微多点打字,更易读:

module ZipCodeValidatable
  ZIP_CODE_REGEX = /^\d{5}(?:[-\s]\d{4})?$/
  def validates_zip_code(zip_field)
    validates zip_field, format: ZIP_CODE_REGEX, allow_blank: true
  end
end

class MyRecord << ActiveRecord::Base
  extend ZipCodeValidatable
  validates_zip_code("work_zip_code")
  validates_zip_code("home_zip_code")
end

如果您要验证的邮政编码是 all ,则可以使用custom validator。在我看来,您可以使用ActiveModel::EachValidator的第二部分。我从没使用过这些,但我读过后看起来像这样:

class ZipCodeValidator < ActiveModel::EachValidator
  ZIP_CODE_REGEX = /^\d{5}(?:[-\s]\d{4})?$/
  def validate(record, attribute, value)
    unless ZIP_CODE_REGEX =~ value
      errors[attribute] << "Invalid zip code in field #{attribute}"
    end
  end
end