Are there better ways to prevent 'yield' when no block is passed in?
Yes! :)
def a_method(*params)
# ...
yield if block_given?
# ...
end
What if method has yield inside, but a block wasn't passed to it?
You can use Kernel#block_given?
to determine if a block is passed and take the appropriate action.
class SomeClass < Array
def some_method
if block_given?
yield(self[i])
else
# not given
end
end
end
That means you can also have a default action if the block is not passed.
Ruby: How can I properly use `yield` to pass unnamed code block to Integer#times method?
For sake of completeness on this topic, I wanted to demonstrate another technique to call the original block:
def withProc
p = Proc.new
3.times(&p)
end
withProc { print "Go" }
When Proc.new
is not given a block, it uses the block that was given to withProc
instead. Now you can call p
, and it will call the original block. You can also pass p
to other methods like times
either as a regular argument or as a block argument.
See https://medium.com/@amliving/proc-new-trick-c1df16185599 for more discussion
How to prevent problems with `return` from block when using Ruby `yield`
There is a built-in solution to detect whether a block contains a return
statement.
You can use RubyVM::InstructionSequence.disasm
to disassemble the block passed in by the user, then search it for throw 1
, which represents a return
statement.
Here's a sample implementation:
def safe_yield(&block)
if RubyVM::InstructionSequence.disasm(block) =~ /^\d+ throw +1$/
raise LocalJumpError
end
block.call
end
Here's how you might incorporate it into your library:
def library_method(&block)
safe_yield(&block)
puts "library_method succeeded"
rescue LocalJumpError
puts "library_method encountered illegal return but resumed execution"
end
And here's the user experience for a well-behaved and a misbehaving user:
def nice_user_method
library_method { 1 + 1 }
end
nice_user_method
# library_method succeeded
def naughty_user_method
library_method { return false if rand > 0.5 }
end
naughty_user_method
# library_method encountered illegal return but resumed execution
Commentary:
Using raise LocalJumpError
/rescue LocalJumpError
gets around the issues you encountered when using a blanket ensure
.
I chose LocalJumpError
because it seems relevant, and because (I think!) there is no possible Ruby code that would result in LocalJumpError
being raised "naturally" in this context. If that turns out to be false, you can easily substitute your own new exception class.
Yield within Set to eliminate in an Array
In Ruby, when you are putting yield
keyword inside any method(say #bar
), you are explicitly telling #bar
that, you will be using a block with the method #bar
. So yield
knows, inside the method block will be converted to a Proc
object, and yield
have to call that Proc
object.
Example :
def bar
yield
end
p bar { "hello" } # "hello"
p bar # bar': no block given (yield) (LocalJumpError)
In the
uniq_by
method, we did not do anything to handle block argument. How is the passed argument handled byuniq_by
method?
You did do, that is you put yield
. Once you will put this yield
, now method is very smart to know, what it supposed to so. In the line Messages.all.uniq_by { |h| h.body }
you are passing a block { |h| h.body }
, and inside the method definition of uniq_by
, that block has been converted to a Proc
object, and yield
does Proc#call
.
Proof:
def bar
p block_given? # true
yield
end
bar { "hello" } # "hello"
Better for understanding :
class Array
def uniq_by
seen = Set.new
select{ |x| seen.add?( yield( x ) ) }
end
end
is same as
class Array
def uniq_by
seen = Set.new
# Below you are telling uniq_by, you will be using a block with it
# by using `yield`.
select{ |x| var = yield(x); seen.add?(var) }
end
end
Read the doc of yield
Called from inside a method body, yields control to the code block (if any) supplied as part of the method call. If no code block has been supplied, calling
yield
raises an exception.yield
can take an argument; any values thus yielded are bound to the block's parameters. The value of a call toyield
is the value of the executed code block.
Related Topics
Checking Whether the C Compiler Works... No
How to Run Ruby 2.0 with Jruby 1.7
How to Output Names of Ruby Unit Tests
Why Is Ruby's Date Class Automatically Loaded But Datetime Is Not
Using Named Captures with Regex Match in Ruby's Case...When
Setting Up Facets in Elasticsearch with Searchkick Gem in Rails 4.1
How to Generate PDF from Markdown Using Pure Ruby
Use Sudo for Gem Install Cocoapods
How to Loop Through a Daterange with Different Intervals
What Do the Fields of Ruby's Gc.Stat Mean
Why Do I Get an "Undefined Method for 'Has_Attached_File' When Installing Paperclip
Can't Install Rmagick, Pkg-Config: Command Not Found
How to Get the Current Route in Rails
Rails 3.1 Actioncontroller::Routingerror (No Route Matches [Get] "/Assets/Rails.Png"):