Forum: Ruby Is there a method_eval or similar thing ?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
0026dd77fd9ecc97b36e5b79cdbcf590?d=identicon&s=25 R. Kumar (sentinel)
on 2008-10-22 11:40
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 ?
0df4a6c75caf1bd9b01d2dcbfb085ee4?d=identicon&s=25 Sandro Paganotti (Guest)
on 2008-10-22 13:47
(Received via mailing list)
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
0026dd77fd9ecc97b36e5b79cdbcf590?d=identicon&s=25 R. Kumar (sentinel)
on 2008-10-22 13:55
Sandro Paganotti 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.
31e038e4e9330f6c75ccfd1fca8010ee?d=identicon&s=25 Gregory Brown (Guest)
on 2008-10-22 14:42
(Received via mailing list)
On Wed, Oct 22, 2008 at 7:54 AM, Nit Khair <sentinel.2001@gmx.com>
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/
0026dd77fd9ecc97b36e5b79cdbcf590?d=identicon&s=25 R. Kumar (sentinel)
on 2008-10-22 15:26
Gregory Brown wrote:
> On Wed, Oct 22, 2008 at 7:54 AM, Nit Khair <sentinel.2001@gmx.com>
> 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 :-) 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 ...
60c6b87c4cc2716c83a737e0ba2d3bc0?d=identicon&s=25 David Rio (Guest)
on 2008-10-22 17:56
(Received via mailing list)
On Wed, Oct 22, 2008 at 7:40 AM, Gregory Brown
<gregory.t.brown@gmail.com>wrote:

> http://highline.rubyforge.org/
>

Is this the main source repository: git://
github.com/elliottcable/highline.git ?
4299e35bacef054df40583da2d51edea?d=identicon&s=25 James Gray (bbazzarrakk)
on 2008-10-22 18:13
(Received via mailing list)
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 Gray II
753dcb78b3a3651127665da4bed3c782?d=identicon&s=25 Brian Candler (candlerb)
on 2008-10-23 11:57
Nit Khair 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 :-) 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.
753dcb78b3a3651127665da4bed3c782?d=identicon&s=25 Brian Candler (candlerb)
on 2008-10-23 11:58
>   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.
0026dd77fd9ecc97b36e5b79cdbcf590?d=identicon&s=25 R. Kumar (sentinel)
on 2008-10-23 14:13
Brian Candler 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.
31e038e4e9330f6c75ccfd1fca8010ee?d=identicon&s=25 Gregory Brown (Guest)
on 2008-10-23 16:03
(Received via mailing list)
On Thu, Oct 23, 2008 at 8:12 AM, Nit Khair <sentinel.2001@gmx.com>
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
0026dd77fd9ecc97b36e5b79cdbcf590?d=identicon&s=25 R. Kumar (sentinel)
on 2008-10-24 06:02
Gregory Brown 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.
31e038e4e9330f6c75ccfd1fca8010ee?d=identicon&s=25 Gregory Brown (Guest)
on 2008-10-24 06:56
(Received via mailing list)
On Oct 24, 2008, at 12:00 AM, Nit Khair wrote:

> Gregory Brown 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
This topic is locked and can not be replied to.