Is there a method_eval or similar thing?


#1

I have a method called askyesno which takes a string and returns whether
the user pressed y or n. However, I now want the user to be able to pass
a block in with blocks for what to do for YES and NO.

However, in this block I need to be able to access method level
variables. Here’s how i am trying to code a sample of it. btw, askyesno
will actually sit inside a module in my app.

the ch here is only for testing, it contains y or n, so we can test

this out

easily

def askyn(str, ch, &bl)
puts str
h = {}

def actionbind(key, &block)
h[key] = block
end

if block_given?
method_eval(&bl) # or module_eval etc
end

case ch
when ‘y’
h[:yes].call if h.include? :yes
when ‘n’
h[:no].call if h.include? :no
end
end

askyn(“Do you wish to proceed?”, ‘n’) do
actionbind(:yes) { puts “user pressed yes” }
actionbind(:no) { puts “user pressed no” }
end

Currently, i have “ask” methods that allow for many options (not just
yes/no), the selection is passed back to the caller and he has a
case-when in which he takes appropriate action.

I was considering trying out something like the above where he could
pass in proc - bindings for each key. Is there any way to access the
hash “h” from the block ?


#2

I think that maybe you can wrap everything in a class:

class Ask
def initialize(question)
@question = question; @h={}
yield(self)
end
def actionbind(key, &block)
@h[key] = block
end
end

Ask.new(“Do you wish to proceed?”) do |question|
question.actionbind(:yes){ puts “user pressed yes” }
question.actionbind(:no) { puts “user pressed no” }
end


#3

Sandro P. wrote:

I think that maybe you can wrap everything in a class:

class Ask
def initialize(question)
@question = question; @h={}
yield(self)
end
def actionbind(key, &block)
@h[key] = block
end
end

Ask.new(“Do you wish to proceed?”) do |question|
question.actionbind(:yes){ puts “user pressed yes” }
question.actionbind(:no) { puts “user pressed no” }
end

Thanks, I will be using this whereever there are classes, but would like
to know, is there any way i can do it with methods. I know i could
define the hash as a global (or maybe module level object) but that
would not be a clean solution.

There is also one question I would like to ask as a ruby newbie
(struggling to grow up). Is the above approach of passing blocks to an
ask method making it more “rubyish” or better than returning the input
character and letting the user decide what to do.


#4

On Wed, Oct 22, 2008 at 7:54 AM, Nit K. removed_email_address@domain.invalid
wrote:

There is also one question I would like to ask as a ruby newbie
(struggling to grow up). Is the above approach of passing blocks to an
ask method making it more “rubyish” or better than returning the input
character and letting the user decide what to do.

Sorry to self-promote, but you may want to use HighLine or at least
look at it for ideas:

http://highline.rubyforge.org/


#5

On Wed, Oct 22, 2008 at 7:40 AM, Gregory B.
removed_email_address@domain.invalidwrote:

http://highline.rubyforge.org/

Is this the main source repository: git://
github.com/elliottcable/highline.git ?


#6

Gregory B. wrote:

On Wed, Oct 22, 2008 at 7:54 AM, Nit K. removed_email_address@domain.invalid
wrote:

Sorry to self-promote, but you may want to use HighLine or at least
look at it for ideas:

http://highline.rubyforge.org/
Ah. that’s fine :slight_smile: I incidentally have been studying a lot of code in
the last month, and Highline is what i did go through the other day. It
was recommended to me by someone in another thread as a project to read
to improve my ruby.

However, here I mean passing a block to a method in a more general way.
This was just an example. Thanks for the reminder, will go back and
check …


#7

On Oct 22, 2008, at 10:52 AM, David Rio wrote:

input
character and letting the user decide what to do.

Sorry to self-promote, but you may want to use HighLine or at least
look at it for ideas:

http://highline.rubyforge.org/

Is this the main source repository: git://
github.com/elliottcable/highline.git ?

No. It’s here:

http://rubyforge.org/scm/?group_id=683

James Edward G. II


#8

Nit K. wrote:

askyn(“Do you wish to proceed?”, ‘n’) do
actionbind(:yes) { puts “user pressed yes” }
actionbind(:no) { puts “user pressed no” }
end

As you have already realised, you need somewhere to store the bindings
between keys and actions. This could be a hash allocated by askyn:

askyn(“Do you wish to proceed?”, “n”) do |h|
actionbind(h,:yes) { puts “user pressed yes” }
… etc

But in this case I think a wrapper object to hold the bindings would be
better, as already posted by someone else. (That’s what the optparse.rb
library does, amongst others; it’s a clean and well-recognised solution)

However you could turn it around and get the caller to pass a
pre-prepared hash containing all the bindings to askyn:

askyn(“Do you wish to proceed?”, “n”,
:yes => lambda { puts “user pressed yes” }
:no => lambda { puts “user pressed no” }
)

I quite like that approach. This sort of API also makes it easy for the
user to invoke other methods in their object for each response, e.g.

askyn(“Do you wish to proceed?”, “n”
:yes => method(:do_yes),
:no => method(:do_no)
)

Now, as you say, you could keep your hash in a global variable, but that
would be very poor as it would make your code non-threadsafe. But it’s
possible to fix this by using a thread-local variable:

def askyn(prompt, default)
Thread[:askyn] = {}
yield
… etc
end

def actionbind(key, &block)
Thread[:askyn][key] = block
end

I’d say that smells somewhat, but it would work and it hides the hash.

Or scarily, you could attempt to create a local variable in the binding
of the caller, which is I think what you were asking for initially. This
is pretty horrible and I won’t even attempt it here :slight_smile: You can google
for binding_of_caller, and note that eval lets you pass in a binding.

Regards,

Brian.

P.S. Note that if you nest def within a def, as your posted code does,
it probably doesn’t do what you expect. The inner def will define a new
method in your object, at the time when the outer def is called.


#9

Brian C. wrote:

However you could turn it around and get the caller to pass a
pre-prepared hash containing all the bindings to askyn:

askyn(“Do you wish to proceed?”, “n”,
:yes => lambda { puts “user pressed yes” }
:no => lambda { puts “user pressed no” }
)

That is something which immediately struck me as a possibility since I
was considering putting some configurations into a hash anyway.

askyn(“Do you wish to proceed?”, config = {}, &block)

I had thought that would be ugly, and I might be missing something
elegant and simple. However, perhaps that is the simple way to go.

I’ll do a check on your other options but keep them for future use.

P.S. Note that if you nest def within a def, as your posted code does,
it probably doesn’t do what you expect. The inner def will define a new
method in your object, at the time when the outer def is called.

Actually, while generating code some time back, I had accidentally
generated some methods within my run() and not noticed. The code had
worked fine. I’ve rectified that, but used that “mistake” here!
Thanks again.


#10

askyn(“Do you wish to proceed?”, “n”,
:yes => lambda { puts “user pressed yes” }
:no => lambda { puts “user pressed no” }
)

Oops, I missed a comma from the end of the second line.


#11

On Thu, Oct 23, 2008 at 8:12 AM, Nit K. removed_email_address@domain.invalid
wrote:

That is something which immediately struck me as a possibility since I
was considering putting some configurations into a hash anyway.

askyn(“Do you wish to proceed?”, config = {}, &block)

I had thought that would be ugly, and I might be missing something
elegant and simple. However, perhaps that is the simple way to go.

How about this? (Someone suggested before using an object to abstract
your needs, I’ve only implemented it)

class Answer
def yes(&block)
@yes = block
end

def no(&block)
@no = block
end

def process(string)
(string =~ /\by(es)?\b/i ? @yes : @no).call
end
end

def ask_yn(question, &block)
ans = Answer.new
ans.instance_eval(&block)
ans.process(gets.chomp)
end

ask_yn(“Are you cool?”) do
yes { puts “Awesome” }
no { puts “Lame” }
end


#12

Gregory B. wrote:

How about this? (Someone suggested before using an object to abstract
your needs, I’ve only implemented it)

Looks awesome ! May i use it, with credits of course.


#13

On Oct 24, 2008, at 12:00 AM, Nit K. wrote:

Gregory B. wrote:

How about this? (Someone suggested before using an object to abstract
your needs, I’ve only implemented it)

Looks awesome ! May i use it, with credits of course.

Sure, no credit necessary, it’s fairly generic. Enjoy!

-greg