Reputation: 131
I am using the following code to enforce context of DSL nested constructs. What are the other ways of achieving the same functionality?
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
Upvotes: 3
Views: 359
Reputation: 54223
You asked about other ways, not the best way. So here's some examples :
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
A bit shorter :
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
If you don't plan to have a d method for an A::B::C object :
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
This was a fun one :
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
I dare say this doesn't look half bad:
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)} }
A bit more robust :
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) }
In example B, we first define :
both are defined in main, because we want a() to be available directly.
a() method doesn't do much expect printing "a"
and passing a block to an instance of A.
Then comes b() method. We don't want it to be available from main, so we define it inside A class. We want to continue with the nested methods, so we define a B class, which is also defined inside A. The B class is actually a A::B class. The A::B#b() method also prints "b"
, and passes a block to an instance of B.
We continue with A::B::C inside of A::B, just like we did with A::B and A.
Example F is basically like Example B, but written dynamically.
In example B, we defined an x method and an X class in every step, with the exact same structure. It should be possible to avoid code repetition with a method called new_method_and_class(x)
which uses define_method
, const_set
and Class.new
:
new_method_and_class("a") # <- Object#a() and A are now defined
a do
puts self.inspect
end
#=> "a"
# <A:0x00000000e58bc0>
Now, we want to define a b() method and a B class, but they shouldn't be in main. new_method_and_class("b")
wouldn't do. So we pass an extra parameter, called parent_klass, which defaults to 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)
To define the c method, we just add another line :
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)
And so on and so on.
To avoid code repetition, we can use inject with the parent_klass as accumulator value :
["a", "b", "c"].inject(Object){|klass,x| new_method_and_class(x,klass) }
Here's a modified code from Example F which works with a basic tree structure.
# 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>
Upvotes: 3