我使用以下代码来强制执行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
答案 0 :(得分:2)
你问过其他方法,而不是最好的方法。所以这里有一些例子:
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
稍短一些:
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
如果您不打算为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
这很有趣:
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
我敢说这看起来并不坏:
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)} }
更强大一点:
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中,我们首先定义:
两者都在main中定义,因为我们希望a()可以直接使用。
a()方法不会期望打印"a"
并将块传递给A的实例。
然后是b()方法。我们不希望它从main获得,所以我们在A类中定义它。我们希望继续使用嵌套方法,因此我们定义了一个B类,它也在A中定义.B类实际上是A :: B类。 A :: B#b()方法还打印"b"
,并将块传递给B的实例。
我们继续A :: B内部的A :: B :: C,就像我们对A :: B和A一样。
示例F基本上与示例B类似,但是动态编写。
在示例B中,我们在每个步骤中定义了一个x方法和一个X类,结构完全相同。应该可以通过名为new_method_and_class(x)
的方法避免代码重复,该方法使用define_method
,const_set
和Class.new
:
new_method_and_class("a") # <- Object#a() and A are now defined
a do
puts self.inspect
end
#=> "a"
# <A:0x00000000e58bc0>
现在,我们要定义一个b()方法和一个B类,但它们不应该在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作为累加器值:
["a", "b", "c"].inject(Object){|klass,x| new_method_and_class(x,klass) }
这是来自示例F的修改代码,它使用基本树结构。
# http://stackoverflow.com/questions/40641273/ruby-dsl-nested-constructs/40641743#40641743
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>