创建具有可选默认值的属性访问器

Creating attribute accessors with optional defaults

我有一个 Rails 应用程序,它经常需要设置默认属性。有时用户会为将要遵守的属性提供值,但在其他情况下,模型或用户可能希望使用默认值覆盖这些属性,而忽略原始值。

我猜想这个问题需要一个 banged (!) 和 non-banged 方法来设置默认值,允许用户和程序切换到适当的状态。 non-banged setter 只会在它们不为 nil 时设置默认值,而 banged 版本将始终使用默认值覆盖属性。差异很小:

class BangDiBang
  attr_accessor :value

  def set_default
    self.value ||= do_some_suff_to_determine_default_value
  end

  def set_default!
    self.value = do_some_suff_to_determine_default_value
  end

  ...
end

这段代码的问题是,如果我要设置一堆变量,我最终会为每个变量重复相同的代码两次。

我的问题是如何把这段代码partial出来?将逻辑保存在一个方法中,并有两个方法 set_valueset_value! 调用具有不同赋值运算符的中央逻辑。

我想到了一个解决方案:将中心逻辑写成文本,替换 setter 方法中的赋值操作并求值(但这感觉不对)。我怎样才能不重复自己?

您的处理方式不适用于多个参数,因为您正在调用 set_default 但未指定哪个变量。理想情况下,您需要为每个变量定义行为。有一些库可以处理这类事情,但您可以很容易地自己动手:

class Example
  def self.default_param(name, default_value)
    define_method("default_#{name}") { default_value }

    define_method(name) do 
      instance_variable_get("@#{name}") || default_value
    end

    attr_writer name
  end

  default_param :foo, 'foo default'
end

ex = Example.new
ex.foo #=> "foo default" 
ex.foo = 'bar'
ex.foo #=> "bar"
ex.default_foo #=> "foo default" 

我重命名了 set_defaultset_default! 以便更清楚:对于每个具有默认值的变量,创建三个方法(例如使用 foo 作为变量名称) :

  1. foo — returns @foo 的值如果为真,则 default_foo 否则
  2. foo= — 设置 @foo
  3. 的值
  4. default_foo — returns 指定的默认值

您可以进一步划分和整理上面的一些代码,创建一个 default_params(复数)方​​法来获取哈希,将 class 宏提取到关注点:

module DefaultParams
  def default_param(name, default_value)
    define_method("default_#{name}") { default_value }

    define_method(name) do 
      instance_variable_get("@#{name}") || default_value
    end

    attr_writer name
  end

  def default_params(params)
    params.each { |default|  default_param(*default) }
  end
end

class Example
  extend DefaultParams
  default_params foo: 'default foo', bar: 'my favorite bar'
end

我在 coreyward 的启发下实施了一个解决方案。起初我没有意识到,我需要将默认值分离到单个方法中,而不是使用两种方法来设置默认值数组。这为应用程序的设计提供了极大的灵活性。所以非常感谢你的回答。

对于 rails 设置,我已将其添加到 application_record.rb

def set_attr_from_defaults
  default_attrs.each do |atr|
    eval("self.#{atr[0..atr.length-9]} ||= self.#{atr}")
  end
end

def set_attr_from_defaults!
  default_attrs.each do |atr|
    eval("self.#{atr[0..atr.length-9]} = self.#{atr}")
  end
end

def set_default_attr params
  params.each { |key, value| self.define_singleton_method(key){value} }
end

def default_attrs
  self.attributes.keys.map{|i| (i+'_default').to_sym} & self.methods
end

default_attrs 生成一个不包含默认属性的符号列表。 set_default_attr 定义单例方法,预期使用像 attrname_default: 'default_value' ... 这样的散列中的解析。 set_attr_from_defaults 将从默认值设置属性,当属性具有像 var: var_default 这样的一对时。数字 9_default + 1.

的长度