【问题标题】:DRY up this model with virtual attributes用虚拟属性干燥这个模型
【发布时间】:2012-12-14 19:30:48
【问题描述】:

在我的表单中,我有一个虚拟属性,允许我接受混合数字(例如 38 1/2)并将它们转换为小数。我也有一些验证(我不确定我是否正确处理了这个),如果发生爆炸会引发错误。

class Client < ActiveRecord::Base
  attr_accessible :mixed_chest

  attr_writer :mixed_chest

  before_save :save_mixed_chest

  validate :check_mixed_chest

  def mixed_chest
    @mixed_chest || chest
  end

  def save_mixed_chest
    if @mixed_chest.present?
      self.chest = mixed_to_decimal(@mixed_chest)
    else
       self.chest = ""
    end
  end

  def check_mixed_chest
    if @mixed_chest.present? && mixed_to_decimal(@mixed_chest).nil?
      errors.add :mixed_chest, "Invalid format. Try 38.5 or 38 1/2"
    end
  rescue ArgumentError
    errors.add :mixed_chest, "Invalid format. Try 38.5 or 38 1/2"
  end

  private

  def mixed_to_decimal(value)
    value.split.map{|r| Rational(r)}.inject(:+).to_d
  end

end 

但是,我想添加另一列wingspan,它具有虚拟属性:mixed_wingspan,但我不知道如何抽象它以重用它——我将使用相同的转换/验证几十个输入。

理想情况下,我想使用 accept_mixed :chest, :wingspan ... 之类的东西,它会处理自定义的 getter、setter、验证等。

编辑:

我正在尝试使用元编程重新创建功能,但我在几个地方遇到了困难:

def self.mixed_number(*attributes)
  attributes.each do |attribute|
    define_method("mixed_#{attribute}") do
      "@mixed_#{attribute}" || attribute
    end
  end
end

mixed_number :chest

这会将箱子设置为“@mixed_chest”!我正在尝试像上面一样获取实例变量@mixed_chest

【问题讨论】:

    标签: ruby-on-rails


    【解决方案1】:

    你会想要一个custom validator

    类似

    class MixedNumberValidator < ActiveModel::EachValidator
      def validate_each(record, attribute, value)
        if value.present? && MixedNumber.new(value).to_d.nil?
          record.errors[attribute] << (options[:message] || "Invalid format. Try 38.5 or 38 1/2")
        end
      end
    end
    

    那你就可以了

    validates :chest, mixed_number: true
    

    请注意,我会将 mixed_to_decimal 内容提取到单独的类中

    class MixedNumber
      def initialize(value)
        @value = value
      end
    
      def to_s
        @value
      end
    
      def to_d
        return nil if @value.blank?
        @value.split.map{|r| Rational(r)}.inject(:+).to_d
      rescue ArgumentError
        nil
      end
    end
    

    并且此定义允许您在 save_chest 方法中删除 if 语句。

    现在您只需要进行一些元编程即可让一切顺利进行,正如我对您的其他问题所建议的 in my answer 一样。你基本上会想要类似的东西

    def self.mixed_number(*attributes)
      attributes.each do |attribute|
        define_method("mixed_#{attribute}") do
          instance_variable_get("@mixed_#{attribute}") || send(attribute)
        end
    
        attr_writer "mixed_#{attribute}"
    
        define_method("save_mixed_#{attribute}") do
          # exercise for the reader ;)
        end
    
        before_save "save_#{attribute}"
        validates "mixed_#{attribute}", mixed_number: true
      end
    end
    
    mixed_number :chest, :waist, :etc
    

    【讨论】:

    • +1 谢谢,安迪。我正在努力解决答案,试图理解每个部分。我知道if ... else self.chest = "" 部分有点愚蠢,但是当我删除它时,删除值不会改变它。它只是恢复到原来的样子。
    • 此外,我正在努力设置实例变量,我已将其添加到我更新的问题中。
    • 好的,我添加了更多细节来帮助您进行元编程。如果您将else 中的self.chest = "" 替换为self.chest = nil,会发生什么情况(因为当您完全删除if 后转换失败时会发生这种情况)。
    • 是的,您正在寻找 send(attribute)(或 send("#{attribute}=", value) 用于设置器)... some_object.send 采用方法名称(作为符号或字符串)并调用该方法some_object。你通常不需要做self.send,因为self是隐含的。
    • 我编辑了MixedNumber#to_d 方法来处理它试图做nil.to_d 的情况。
    猜你喜欢
    • 1970-01-01
    • 2011-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-11
    • 1970-01-01
    • 2014-05-08
    相关资源
    最近更新 更多