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:
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
call
ed.The
def
keyword resets the local variable scope. To get a value into a function, you can either define it usingdefine_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-Proc
s 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
Rails 4 User Roles and Permissions
Differencebetween #Encode and #Force_Encoding in Ruby
Ruby Equivalent of Perl Data::Dumper
How to Add Child Nodes in Nodeset Using Nokogiri
Rails Console Not Working on Server
In Ruby, Should I Use ||= or If Defined? for Memoization
Simple_Form's Collection_Radio_Button and Custom Label Class
Rails Force Models to Eager Load
What Is the Activemodel Method Attribute "_Was" Used For
Extract Fast Fourier Transform Data from File
How to Expire a View Cached Fragment from Console
How to Replace the Last Occurrence of a Substring in Ruby
Best Way to Group by Date with Mongoid
Removing Child Root Nodes in Rabl
String Interpolation When Not Using a String Literal
How to Configure Mongomapper and Activerecord in Same Ruby Rails Project