Using Yield Inside Define_Method in Ruby

How to define a method with yield in ruby

yield statement executes the block, which is passed as an argument to a method:

def my_method
if block_given? # checks if block is passed to the method
yield
end
end

my_method{ puts 'Method to execute' }
#=> Method to execute

To modify an object you need to pass that object as argument to yield for example:

class Array
def self.find_map
array = []
yield(array)
array
end
end
p Array.find_map{|arr| arr << 's' << 't' } #=> ["s", "t"]

You have find_map as a class method which can be called as: ActiveRecord::Base.find_map, by looking at your code expecting find_each to be class method(which expects an option), too. The workaround would be something like this:

class ActiveRecord::Base
def self.find_map
array = []
find_each(*yield(array))
array
end
end

So:

ActiveRecord::Base.find_map{|arr| arr << {:conditions => "age > 21"} }

will insert {:conditions => "age > 21"} in to array defined inside find_map when block is being executed and passed as {:conditions => "age > 21"} into find_each method.

Ruby define method to return proc which makes use of an argument

Your example has two problems:

  1. You can't call a "proc full of methods" like that -- it'll work as an association extension, but there the block is evaluated as a module body, not called.

  2. The def keyword resets the local variable scope. To get a value into a function, you can either define it using define_method instead (that block retains surrounding scope), or put the value somewhere else the function will be able to find it (a class variable, for example).


def test(word)
proc do
define_method(:hello) do
puts word
end
end
end

Class.new(&test("hello")).new.hello

Separately, if you're defining approximately the same method on several associations, there might be a simpler path by defining them as class-level scopes.

Using local variables in define_method

As mentionned in comments this is down to closures - the block passed to define_method captures the local variables from its scope (and not just their value as you found out).

for doesn't create a new scope, so your method 'sees' the change to i. If you use a block (for example with each) then a new scope is created and this won't happen. You just need to change your code to

class Test
def self.plugin
(1..2).each do |i|
define_method("test#{i}".to_sym) do
p i
end
end
end
plugin
end

which is basically what the question linked by Arup was about

Implicit block passing and dynamically defined methods

The problem with Method 3 is the scope. yield refers to the block passed to initialize, if any. It does not refer to the block passed to m. And since you created o by C.new without a block, the expected yield is absent, and that causes the error.

In Method 1, yield refers to the block passed to m. In Method 2, b refers to a proc converted from the block passed to m.

How to use define_method inside initialize()

Do as below :

class C
def initialize(n)
self.class.send(:define_method,n) { puts "some method #{n}" }
end
end

ob = C.new("abc")
ob.abc
# >> some method abc

Module#define_method is a private method and also a class method.Your one didn't work,as you tried to call it on the instance of C.You have to call it on C,using #send in your case.

How are variables bound to the body of a define_method?

Blocks in Ruby are closures: the block you pass to define_method captures the variable name itself–not its value—so that it remains in scope whenever that block is called. That's the first piece of the puzzle.

The second piece is that the method defined by define_method is the block itself. Basically, it converts a Proc object (the block passed to it) into a Method object, and binds it to the receiver.

So what you end up with is a method that has captured (is closed over) the variable name, which by the time your loop completes is set to :destroy.

Addition: The for ... in construction actually creates a new local variable, which the corresponding [ ... ].each {|name| ... } construction would not do. That is, your for ... in loop is equivalent to the following (in Ruby 1.8 anyway):

name = nil
[ :new, :create, :destroy ].each do |name|
define_method("test_#{name}") do
puts name
end
end
name # => :destroy

Is it possible to have Methods inside Methods?

UPDATE: Since this answer seems to have gotten some interest lately, I wanted to point out that there is discussion on the Ruby issue tracker to remove the feature discussed here, namely to forbid having method definitions inside a method body.


No, Ruby doesn't have nested methods.

You can do something like this:

class Test1
def meth1
def meth2
puts "Yay"
end
meth2
end
end

Test1.new.meth1

But that is not a nested method. I repeat: Ruby does not have nested methods.

What this is, is a dynamic method definition. When you run meth1, the body of meth1 will be executed. The body just happens to define a method named meth2, which is why after running meth1 once, you can call meth2.

But where is meth2 defined? Well, it's obviously not defined as a nested method, since there are no nested methods in Ruby. It's defined as an instance method of Test1:

Test1.new.meth2
# Yay

Also, it will obviously be redefined every time you run meth1:

Test1.new.meth1
# Yay

Test1.new.meth1
# test1.rb:3: warning: method redefined; discarding old meth2
# test1.rb:3: warning: previous definition of meth2 was here
# Yay

In short: no, Ruby does not support nested methods.

Note also that in Ruby, method bodies cannot be closures, only block bodies can. This pretty much eliminates the major use case for nested methods, since even if Ruby supported nested methods, you couldn't use the outer method's variables in the nested method.


UPDATE CONTINUED: at a later stage, then, this syntax might be re-used for adding nested methods to Ruby, which would behave the way I described: they would be scoped to their containing method, i.e. invisible and inaccessible outside of their containing method body. And possibly, they would have access to their containing method's lexical scope. However, if you read the discussion I linked above, you can observe that matz is heavily against nested methods (but still for removing nested method definitions).

using the same string interpolation inside the method as on its define_method line

Almost. define_method(....).each does not make sense. And the methods do not seem to need any arguments. Also, element does not include meditate_on_, so there is no need to remove it.

class Monk
["life", "the_universe", "everything"].each do |element|
define_method("meditate_on_#{element}") do
"I know the meaning of #{element.gsub('_', ' ')}"
end
end
end
Monk.new.meditate_on_life
# => "I know the meaning of life"

define_method doesn't receive block as a parameter ruby

I believe (but haven't checked) block_given? refers to a block being passed to the method defined by the closest lexically enclosing method definition, i.e. def, and does not work inside methods defined with define_method.

I know for a fact that yield only yields to a block being passed to the method defined by the closest lexically enclosing method definition, i.e. def, and does not yield from a block (which, after all, define_method is, it's just a method like any other method which takes a block, and just like any other taking a block, yield yields to the block of the method, not some other block).

It's kind of strange to combine yield and block_given? with explicitly named block-Procs anyway. If you have the name, there is no need for anonymity, you can just say

if block
define_method(args.to_sym) do block.() end
end

Or did you mean to pass the block to define_method to be used as the implementation of the method? Then it would be

if block
define_method(args.to_sym, &block)
end


Related Topics



Leave a reply



Submit