以元编程方式定义采用关键字参数的Ruby方法?

时间:2014-10-24 18:16:51

标签: ruby metaprogramming

Struct让我创建一个带有参数的新类,并且有一些很好的语义。但是,参数不是必需的,它们的顺序需要参考定义:

Point = Struct.new(:x, :y)

Point.new(111, 222)
#=> <point instance with x = 111, y = 222>

Point.new(111)
#=> <point instance with x = 111, y = nil>

我喜欢类似于Struct的东西,但它使用关键字参数代替:

Point = StricterStruct.new(:x, :y)

Point.new(x: 111, y: 222)
#=> <point instance with x = 111, y = 222>

Point.new(x: 111)
#=> ArgumentError

这可能看起来像这样:

module StricterStruct
  def self.new(*attributes)
    klass = Class.new
    klass.instance_eval { ... }

    klass
  end
end

但是在initialize上定义klass方法的大括号应该是什么:

  • 它需要没有默认值的关键字参数;
  • 关键字以attributes中的符号数组的形式给出;和
  • initialize方法将它们分配给同名的实例变量

4 个答案:

答案 0 :(得分:6)

由于Ruby 2.0 +的新功能,我使用(令人惊讶的Pythonic)**kwargs策略结束了:

module StricterStruct
  def self.new(*attribute_names_as_symbols)
    c = Class.new
    l = attribute_names_as_symbols

    c.instance_eval {
      define_method(:initialize) do |**kwargs|
        unless kwargs.keys.sort == l.sort
          extra   = kwargs.keys - l
          missing = l - kwargs.keys

          raise ArgumentError.new <<-MESSAGE
            keys do not match expected list:
              -- missing keys: #{missing}
              -- extra keys:   #{extra}
          MESSAGE
        end

        kwargs.map do |k, v|
          instance_variable_set "@#{k}", v
        end
      end

      l.each do |sym|
        attr_reader sym
      end
    }

    c
  end
end

答案 1 :(得分:1)

我可能误解了这个问题,但你在找这样的东西吗?

module StricterStruct
  def self.new(*attributes)
    klass = Class.new
    klass.class_eval do 
      attributes.map!{|n| n.to_s.downcase.gsub(/[^\s\w\d]/,'').split.join("_")}
      define_method("initialize") do |args|
        raise ArgumentError unless args.keys.map(&:to_s).sort == attributes.sort
        args.each { |var,val| instance_variable_set("@#{var}",val) }
      end
      attr_accessor *attributes
    end
    klass
  end
end

然后

Point = StricterStruct.new(:x,:y)
#=> Point
p = Point.new(x: 12, y: 77)
#=> #<Point:0x2a89400 @x=12, @y=77>
p2 = Point.new(x: 17)
#=> ArgumentError
p2 = Point.new(y: 12)
#=> ArgumentError
p2 = Point.new(y:17, x: 22)
#=>  #<Point:0x28cf308 @y=17, @x=22>

如果你想要更多的东西请解释,因为我认为这符合你的标准,至少我对它的理解。因为它定义了方法并且可以采用&#34;关键字&#34;(Hash)参数并分配适当的实例变量。

如果您希望以与定义的顺序相同的顺序指定参数,只需删除排序。

也可能有更清洁的实施。

答案 2 :(得分:0)

听起来你正在寻找Ruby的内置OpenStruct

require 'ostruct'

foo = OpenStruct.new(bar: 1, 'baz' => 2)
foo # => #<OpenStruct bar=1, baz=2>

foo.bar # => 1
foo[:bar] # => 1
foo.baz # => 2
foo.baz = 3
foo # => #<OpenStruct bar=1, baz=3>

我认为OpenStruct是Hash上的糖果涂层,我们可以在没有任何实际约束的情况下访问和分配实例,而不像使用普通访问器创建真正的类。我们可以假装它是哈希,或者是带方法的类。这是一个甜点打顶,不是它的地板蜡,不,它是一体的两件事!

答案 3 :(得分:0)

我也在寻找这个,最终偶然发现了这个确实如此的宝石:

https://github.com/etiennebarrie/kwattr

class FooBar
  kwattr :foo, bar: 21
end

foobar = FooBar.new(foo: 42) # => #<FooBar @foo=42, @bar=21>
foobar.foo # => 42
foobar.bar # => 21

而不是

class FooBar
  attr_reader :foo, :bar

  def initialize(foo:, bar: 21)
    @foo = foo
    @bar = bar
  end
end