如何在 Ruby 中元编程地定义类型转换属性读取器/写入器

Cli*_*chl 5 ruby metaprogramming

我正在尝试创建一个简单的类,该类在设置或读取字段时自动将一组字段转换为指定的 Ruby 类型。

这是我到目前为止所拥有的,并且有效。然而,它不是 DRY 并且我的元编程是基本的。

有没有更好、更干净的方法来实现这一点?

class BasicModel

  def self.fields(params)
    params.each do |name, type|

      # Define field writers
      define_method("#{name}=") {|v| @fields[name] = v}

      # Define field readers
      case type.name
      when 'String'
        define_method(name) { @fields[name].to_s }
      when 'Integer'
        define_method(name) { @fields[name].to_i }
      when 'Float'
        define_method(name) { @fields[name].to_f }
      else raise 'invalid field type'
      end

    end
  end

  fields(
    name: String,
    qty: Integer,
    weight: Float
  )

  def initialize
    @fields = {}
  end

end

# specification
m = BasicModel.new
m.name         => ""
m.name = 2     => 2
m.name         => "2"
m.qty          => 0
m.qty = "1"    => "1"
m.qty          => 1
m.weight       => 0.0
m.weight = 10  => 10
m.weight       => 10.0
Run Code Online (Sandbox Code Playgroud)

对读者和作者进行类型转换的缺点/优势是什么?例如,以下代码对作者进行类型转换,而不是对读者(上图)。我也把case里面的define_method.

class BasicModel
  def self.fields(params)
    params.each do |name, type|

      define_method(name) { @fields[name] }

      define_method("#{name}=") do |val|
        @fields[name] = case type.name
                        when 'Integer'  then val.to_i
                        when 'Float'    then val.to_f
                        when 'String'   then val.to_s
                        else raise 'invalid field type'
                        end
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

我在想一个可能的问题是决策树(例如 case 语句)应该放在define_method. 我假设每次设置/读取该字段时都会毫无意义地评估该语句。这样对吗?

las*_*nal 1

您真正需要的是对每个字段使用的类型转换方法的引用。您可以在定义 setter 方法之前确定类型转换方法,并send在调用 setter 时用于执行类型转换。

这是一个例子:

class BasicModel
  def self.fields(params)
    params.each do |name, type|

      operator = case type.name
        when 'Integer'  then :to_i
        when 'Float'    then :to_f
        when 'String'   then :to_s
        else raise 'invalid field type'
      end

      define_method(name) { @fields[name] }

      define_method("#{name}=") do |val|
        @fields[name] = val.send(operator)
      end

    end
  end

  def initialize
    @fields = {}
  end
end
Run Code Online (Sandbox Code Playgroud)