Ruby DSL 嵌套结构

Ruby DSL nested constructs

我正在使用以下代码来强制执行 DSL 嵌套构造的上下文。实现相同功能的其他方法是什么?

def a &block
  p "a"
  def b &block
    p "b"
    def c &block
      p "c"
      instance_eval &block
    end 
    instance_eval &block
    undef :c
  end 
  instance_eval &block 
  undef :b
end 
# Works
a do
  b do
    c do
    end
  end
end

# Doesn't Work 
b do
end
c do
end

Source

你问的是其他方式,不是最好的方式。所以这里有一些例子:

示例 A

class A
  def initialize
    p "a"
  end

  def b &block
    B.new.instance_eval &block
  end
end

class B
  def initialize
    p "b"
  end

  def c &block
    C.new.instance_eval &block
  end
end

class C
  def initialize
    p "c"
  end
end

def a &block
  A.new.instance_eval &block
end

示例 B

短一点:

def a &block
  p "a"
  A.new.instance_eval &block
end

class A
  def b &block
    p "b"
    B.new.instance_eval &block
  end

  class B
    def c &block
      p "c"
      C.new.instance_eval &block
    end

    class C
    end
  end
end

示例 C

如果您不打算为 A::B::C 对象使用 d 方法:

def a &block
  p "a"
  A.new.instance_eval &block
end

class A
  def b &block
    p "b"
    B.new.instance_eval &block
  end

  class B
    def c &block
      p "c"
      instance_eval &block
    end
  end
end

示例 D

这很有趣:

def new_class_and_method(klass_name, next_klass=Object)
  dynamic_klass = Class.new do
    define_method(next_klass.name.downcase){|&block| p next_klass.name.downcase; next_klass.new.instance_eval &block}
  end
  Object.const_set(klass_name, dynamic_klass)
end

new_class_and_method("A", new_class_and_method("B", new_class_and_method("C")))

def a &block
  p "a"
  A.new.instance_eval &block
end

示例 E

我敢说这看起来还不错:

def new_method_and_class(x)
  define_method(x) do |&block|
    p x
    self.class.const_get(x.capitalize).new.instance_eval &block
  end

  self.const_set(x.capitalize, Class.new)
end

["a", "b", "c"].inject(Object){|klass,x| klass.instance_eval{new_method_and_class(x)} }

示例 F

更健壮一点:

def new_method_and_class(x, parent_klass = Object)
  parent_klass.class_eval do
    define_method(x) do |&block|
      p x
      parent_klass.const_get(x.capitalize).new.instance_eval &block if block
    end
  end

  parent_klass.const_set(x.capitalize, Class.new)
end

["a", "b", "c"].inject(Object){|klass,x| new_method_and_class(x,klass) }

说明

示例 B

在例子B中,我们首先定义:

  • a() 方法
  • 一个A class

两者都在main中定义,因为我们希望a()可以直接使用。 a() 方法并不期望打印 "a" 并将块传递给 A.

的实例

接着是b()方法。我们不希望它在 main 中可用,所以我们在 A class 中定义它。我们要继续嵌套方法,所以我们定义了一个Bclass,这个Bclass也在A里面定义,Bclass其实就是一个A::Bclass。 A::B#b() 方法还打印 "b",并将块传递给 B.

的实例

我们继续在 A::B 中使用 A::B::C,就像我们对 A::B 和 A.

所做的一样

示例 F

示例 F 基本上与示例 B 类似,但是是动态编写的。

在示例 B 中,我们在每个步骤中定义了一个 x 方法和一个 X class,具有完全相同的结构。应该可以使用名为 new_method_and_class(x) 的方法避免代码重复,该方法使用 define_methodconst_setClass.new :

new_method_and_class("a") # <- Object#a() and A are now defined

a do
  puts self.inspect
end
#=> "a"
#   <A:0x00000000e58bc0>

现在,我们想定义一个 b() 方法和一个 B class,但它们不应该在 main 中。 new_method_and_class("b") 不行。所以我们传递了一个额外的参数,叫做 parent_klass,默认为 Object :

parent_klass = new_method_and_class("a")
new_method_and_class("b", parent_klass)

a do 
  b do
    puts self.inspect
  end
end

# => "a"
#    "b"
#    <A::B:0x00000000daf368>

b do
  puts "Not defined"
end

# => in `<main>': undefined method `b' for main:Object (NoMethodError)

要定义 c 方法,我们只需添加另一行:

parent_klass = new_method_and_class("a")
parent_klass = new_method_and_class("b", parent_klass)
parent_klass = new_method_and_class("c", parent_klass)

等等等等。

为了避免代码重复,我们可以使用 inject 和 parent_klass 作为累加器值:

["a", "b", "c"].inject(Object){|klass,x| new_method_and_class(x,klass) }

奖金 - 示例 G

下面是示例 F 中的修改代码,它使用基本的树结构。

# 
def new_method_and_class(x, parent_klass = Object)
  parent_klass.class_eval do
    define_method(x) do |&block|
      p x.to_s
      parent_klass.const_get(x.capitalize).new.instance_eval &block if block
    end
  end

  parent_klass.const_set(x.capitalize, Class.new)
end

def create_dsl(branch,parent_klass = Object)
  case branch
  when Symbol, String
    new_method_and_class(branch,parent_klass)
  when Array
    branch.each do |child|
      create_dsl(child, parent_klass)
    end
  when Hash
    branch.each do |key, value|
      create_dsl(value, new_method_and_class(key,parent_klass))
    end
  end
end

methods_tree = {
  :a => {
    :b => [
      :c,
      :d
    ],
    :e => :f,
    :g => nil
  }
}

create_dsl(methods_tree)

a do 
  b do
    c do
      puts self.inspect
    end

    d do
    end
  end

  e do
    f do
    end
  end

  g do
    puts self.inspect
  end
end

# => 
#   "a"
#   "b"
#   "c"
#   #<A::B::C:0x0000000243dfa8>
#   "d"
#   "e"
#   "f"
#   "g"
#   #<A::G:0x0000000243d918>