Tricky DSL, how to do it?

I’d want to write a DSL such that a surface method_missing catches
undefined methods and records the blocks that they define e.g.

foo do
puts “foo”
end

So I would end up with:

{ :foo=>#<Proc …> }

Presently I have something like this:

class Evaluator < BasicObject
def initialize(&block)
@config = {}
instance_eval(&block) if block
end

def __config__
  @__config__
end

def method_missing(sym, *args, &block)
  @__config__[sym] = block
end

end

However when I call on a block I want it to evaluate as if in the
defining
context (in this case toplevel), not inside the “DSL” class that
evaluated
via method_missing.

e = Evaluator.new do
foo do
puts “foo”
end
end

e.config[:foo].call

Instead of what I want, I get a method missing error for #puts.

Any ideas?

I’d be quite surprised if respond_to? ever evaluated true!

try it. I am pretty sure it would work.read this if you don’t believe
me:
http://www.trottercashion.com/2011/02/08/rubys-define_method-method_missing-and-instance_eval.html

On Sun, Nov 6, 2011 at 6:38 PM, Intransition [email protected]
wrote:

I’d be quite surprised if respond_to? ever evaluated true!


Sincerely,

Isaac S.
Section C-4B Vice Chief, Order of the Arrow
Vice Chief of Administration, Tecumseh #65
Eagle Scout

Any ideas?

This might not be the best solution, but if you give initialize
an argument about its surrounding state, then it becomes easier.

class Evaluator < Object
attr_reader :config, :outside

def initialize(outside, &block)
@config = {}
@outside = outside
instance_eval &block
end

def method_missing(sym, *args, &block)
if block_given?
@outside.class.class_eval do
define_method sym do |*args|
block.call *args
end
end
@config[sym] =
@outside.class.instance_method(sym).bind(self)
else
super
end
end

end

e = Evaluator.new(self) do
foo do
puts “foo”
end
end

e.config[:foo].call

I don’t know if that’s what you were after, but after playing around
for a bit that’s what I came up with.

Let me know if it helps,

-Luke

You are missing a super on the method_missing. first you should ask if
is
a method on the class, and if it is, call super with no args.
try:
class Evaluator < BasicObject
def initialize(&block)
@config = {}
instance_eval(&block) if block
end

def __config__
  @__config__
end

def method_missing(sym, *args, &block)
  respond_to? sym ? super : @__config__[sym] = block
end

end

On Sun, Nov 6, 2011 at 5:35 PM, Intransition [email protected] wrote

  @__config__

Any ideas?


Sincerely,

Isaac S.
Section C-4B Vice Chief, Order of the Arrow
Vice Chief of Administration, Tecumseh #65
Eagle Scout

Sorry 'bout the double posting, but it hit me that it’s only possible
to bind a method to an instance that’s of the same class. That and the
fact that the whole binding thing was useless in my previous post made
me go back to my computer and give it another shot.

It’s still not a great solution, because I couldn’t figure out how to
inherit from BasicObject and still have it work.

class Evaluator < Object
attr_reader :config, :outside

def initialize(outside, &block)
@config = {}
@outside = outside
instance_eval &block
end

def method_missing(sym, *args, &block)
if block_given?
outside = @outside
outside.class.class_eval do
define_method sym do |*args|
outside.instance_exec *args, &block
end
end
@config[sym] = @outside.method(sym)
else
super
end
end

end

class Test
def hunt
puts “DEAD”
end
end

t = Test.new

e = Evaluator.new(t) do
foo do
puts “foo”
end

baz do
hunt
end
end

e.config[:foo].call
e.config[:baz].call

-Luke

On Nov 6, 2011, at 7:16 PM, Ankush Ganatra wrote:

    puts "inside CEmployee"

Now if i create an Object of CManager class (unlike other languages like C++ )
the constructor of it gets called but not the base class “Until i use the super
keyword”.

objManager = CManager.new

Can someone please help me here? And even if the this is supposed to be working
like this, isn’t it something different from the Object Oriented Concept which
says base class constructor always gets called first?

This is how it is supposed to work indeed, in Ruby there’s no notion of
default constructor like in C++ (a non-default constructor must be
invoked explicitly even there). With a call to super, a subclass can
choose how a superclass constructor is invoked in regards to passed
parameters and the order of statements. Or not to invoke it at all.

And this is not something unheard of in Object Oriented Concept world.
Just look at Objective-C for quick examples, where a subclass is
responsible also for its superclass allocation, not only initialization.

Genandy.

Hi Luke,

I think your idea about the “outside” is on the right track. I ended
doing
something akin to that, but in my case the “outside” is just toplevel
(aka

). Your solution is defining methods on the class of the outside object. It gets the desired results, but at the expense of polluting the class's namespace. In my case, using toplevel, that actually might not be a problem, so yea, this might be a workable solution. But I found using methods had some other limitations (e.g. problem evaluate it in another scope). So I ended up doing something a bit different, but at it's core it's the same approach as yours, just without the method definitions. The "trick" I came up with was:
  def block.call(*args)
     ::Kernel.eval('self',::TOPLEVEL_BINDING).instance_exec(*args,

&self)
end

This defines a singleton method on the block overriding the original
#call
method, forcing evaluation of the block at the toplevel. Once I had that
working, I refactored so I could get the same result but without have to
monkey patch the Proc object. So far so good.

If you are interested in the actual project code, it is here:
https://github.com/rubyworks/confection/blob/master/lib/confection.rb

Thanks for the help.

On Mon, Nov 7, 2011 at 4:32 AM, Gennady B.
[email protected] wrote:

puts "inside CEmployee"

Now if i create an Object of CManager class (unlike other languages like C++ )
the constructor of it gets called but not the base class “Until i use the super
keyword”.

objManager = CManager.new

Can someone please help me here? And even if the this is supposed to be working
like this, isn’t it something different from the Object Oriented Concept which
says base class constructor always gets called first?

This is how it is supposed to work indeed, in Ruby there’s no notion of default
constructor like in C++ (a non-default constructor must be invoked explicitly even
there). With a call to super, a subclass can choose how a superclass constructor
is invoked in regards to passed parameters and the order of statements. Or not to
invoke it at all.

Ankush, an additional note: super is different than ordinary method
calls with regard to brackets. If you just write “super” the complete
argument list of the current method is copied (including block if
passed); regular methods are invoked with empty argument list in this
case. If you want to explicitly invoke the super class method without
arguments you need to write “super()”. Providing specific arguments
works as well of course: “super(1, ‘foo’)”.

Kind regards

robert

Hi Guys

I am a little confused and need some help. I am a newbie for Ruby but i
think i understand Object Oriented programming a little bit so i got
confused here.

I have got 2 classes here

  1. CEmployee

class CEmployee
def initialize()
puts “inside CEmployee”
end
end

  1. CManager

class CManager < CEmployee
def initialize()
puts “inside CManager”
end
end

Now if i create an Object of CManager class (unlike other languages like
C++ ) the constructor of it gets called but not the base class “Until i
use the super keyword”.

objManager = CManager.new

Can someone please help me here? And even if the this is supposed to be
working like this, isn’t it something different from the Object Oriented
Concept which says base class constructor always gets called first?

I really think that we should all take another look at the Ruby Object
Model. Dave T. has a great screencast on it. This question would be
answered if the ROM was analyzed closely.

On Mon, Nov 7, 2011 at 6:56 AM, Robert K.
[email protected]wrote:

  @__config__[sym] = block
end

Question is, what happens if a missing method is invoked? In your
3 class Evaluator < BasicObject
14 end
25 foo do
robert


remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/


Sincerely,

Isaac S.
Section C-4B Vice Chief, Order of the Arrow
Vice Chief of Administration, Tecumseh #65
Eagle Scout

On Sun, Nov 6, 2011 at 10:35 PM, Intransition [email protected]
wrote:

class Evaluator < BasicObject
end
Instead of what I want, I get a method missing error for #puts.
Any ideas?

You could capture the calling self by getting it from the binding of
the block and hide the evaluating call behind your own #eval method:

class Evaluator < BasicObject
def initialize(&block)
@config = {}
if block
@context = block.binding.eval(“self”)
instance_eval(&block)
end
end

def config
@config
end

def method_missing(sym, *args, &block)
@config[sym] = block
end

def eval(sym, *args)
@context.instance_exec(*args, &@config[sym])
end

end

e = Evaluator.new do
foo do
puts “foo”
end

bar do |txt|
puts “txt = #{txt}”
end
end

e.eval(:foo) # => “foo”
e.eval(:bar, “hello”) # => “txt = hello”

Regards,
Sean

On Sun, Nov 6, 2011 at 11:35 PM, Intransition [email protected]
wrote:

class Evaluator < BasicObject
end
Instead of what I want, I get a method missing error for #puts.
Any ideas?

You can’t have both (evaluate with self = Evaluator to trigger
Evaluator’s method missing and invoking the block in the defining
context) at the same time.

Question is, what happens if a missing method is invoked? In your
implementation the block is returned. I would at least change that
to return nil to avoid nasty side effects. But generally execution
cannot reasonably continue. What about catching the exception and
recording it?

12:54:59 ~$ ./x.rb
{:foo=>#Proc:[email protected]/x.rb:24}
12:55:02 ~$ cat -n x.rb
1 #!/bin/env ruby19
2
3 class Evaluator < BasicObject
4 def initialize(&block)
5 @config = {}
6
7 if block
8 begin
9 block.call
10 rescue ::NoMethodError => e
11 @config[e.name] = block
12 nil
13 end
14 end
15 end
16
17 def config
18 @config
19 end
20
21 end
22
23
24 e = Evaluator.new do
25 foo do
26 puts “foo”
27 end
28 end
29
30 p e.config
31
12:55:03 ~$

Kind regards

robert

This seems very advanced.

I am very confused and also fascinated.

Please continue. :slight_smile:

On Monday, November 7, 2011 9:14:44 AM UTC-5, Sean O’Halpin wrote:

end

def eval(sym, *args)
bar do |txt|
puts “txt = #{txt}”
end
end

e.eval(:foo) # => “foo”
e.eval(:bar, “hello”) # => “txt = hello”

Nicely coded. And spot on. If I didn’t know that self would always be
toplevel in my particular case this is the code I would be using.

Actually… this code seems like it might make for a good general use
DSL
pattern. I’m going to try to generalize it further and see if makes
sense
to make it a reusable library.

What do you think?

Check it out:

class ContextDSL < BasicObject

def initialize(&block)
  @missing_block = block
end

def eval!(&block)
  @context = block.binding.eval("self")
  instance_eval(&block)
end

def method_missing(sym, *xargs, &block)
  context = @context
  context_block = ::Proc.new do |*args|
    context.instance_exec(*args, &block)
  end
  @missing_block.call(sym, context_block)
end

end

Try it out:

config = {}

dsl = ContextDSL.new do |name, block|
config[name] = block
end

dsl.eval! do
foo do
puts self
end
end

config[:foo].call #=>

Now with this general solution in hand, it’s really got me asking why
Ruby
just doesn’t provide a way to set the context, e.g.

f = lambda{ self }

f.context = “Any Object”

f.call #=> “Any Object”

Sean O’halpin wrote in post #1030618:
On Sun, Nov 6, 2011 at 10:35 PM, Intransition [email protected]

class Evaluator < BasicObject
def initialize(&block)
@config = {}
if block
@context = block.binding.eval(“self”)
instance_eval(&block)

That’s a nice solution, I like that. I came up with something similar
after I posted last night, but I like this more.

Aside: What’s with threads sneaking their way into other ones? I
can’t be the only one seeing “Newbie:: Understand Constructor Flow”
alongside
the “Tricky DSL” one. Sorry if this has been discussed elsewhere.

Posted by Robert K.
You can’t have both (evaluate with self = Evaluator to trigger
Evaluator’s method missing and invoking the block in the defining
context) at the same time.

I’m not sure I understand what you’re saying. With some solutions being
posted above, at least ‘self’ is changed in the invoked block. That
seems
to be what Thomas was looking for, at least from my understanding.

Can you elaborate on what you said?

Thanks,

-Luke

On Sun, Nov 6, 2011 at 9:32 PM, Gennady B.
[email protected] wrote:

And this is not something unheard of in Object Oriented Concept world. Just look
at Objective-C for quick examples, where a subclass is responsible also for its
superclass allocation, not only initialization.

An init method in Objective-C (at least in Cocoa) only calls its
superclass’s init method, not alloc. The class method alloc
automatically allocates enough space for the whole object.

(The strange thing about calling -[super init] in Cocoa is that you
have to assign your self variable to the result, just in case -[super
init] actually returns a whole different object. In that case, the
super initializer will deallocate the original one. That’s an area I
don’t understand very well.)

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs