Skip to content

Instantly share code, notes, and snippets.

@al2o3cr al2o3cr/presentation.md
Last active Mar 1, 2019

Embed
What would you like to do?
50 Ways To Leave Your Method

50 Ways To Leave Your Method

Control the flow of execution for your Ruby code with this one weird trick!

Handy resource for keywords: http://ruby-doc.org/docs/keywords/1.9/

This doc: http://bit.ly/1EqHNPH

Returning values

Fall off the end

  def foo
    42
  end

  foo

Return explicitly

  def foo
    return 42
    puts 'wat'
  end

  foo

Return doesn't always work

  return 'huh'

Blocks - a brief detour

  def foo
    yield
  end

  foo do
    2
  end

Returning from a block returns from the caller

  def foo
    yield
  end

  foo do
    return
  end
  def foo
    yield
  end

  def bar
    foo do
      return
    end
    puts 'y hello thar'
  end

  bar

Blocks can be passed as arguments

  def bar
    yield + 1
  end

  def foo(&block)
    bar(&block)
  end

  foo do
    2
  end

Iterating over things

  def foo
    (1..3).each do |x|
      puts x
    end
    puts 'done'
  end

  foo

Skipping some things - next

  def foo
    (1..3).each do |x|
      next if x == 1
      puts x
    end
    puts 'done'
  end

  foo

Giving up - break

  def foo
    (1..3).each do |x|
      break if x == 1
      puts x
    end
    puts 'done'
  end

  foo

Break is different from return

  def foo
    (1..3).each do |x|
      return if x == 1
      puts x
    end
    puts 'done'
  end

  foo

next and break can take an argument

  def foo
    doubles = (1..3).map do |x|
      2*x
    end
    doubles.reduce(:+)
  end

  foo
  def foo
    doubles = (1..3).map do |x|
      next 1000 if x == 1
      2*x
    end
    doubles.reduce(:+)
  end

  foo

Inside a map, next with an argument specifies the value to be used for the current iteration.

  def foo
    doubles = (1..3).map do |x|
      break 1000 if x == 1
      2*x
    end
    doubles.reduce(:+)
  end

  foo

break terminates the iteration immediately and specifies the return value of the whole operation.

redo - wait, what?

  def foo
    doubles = (1..3).map do |x|
      redo if x == 1
      2*x
    end
    doubles.reduce(:+)
  end

  foo

redo causes the containing block to be re-executed with the same parameters again.

next, break and redo are not methods

  def foo
    next
  end

They also aren't limited to iterators

  def foo
    yield + yield
  end

  foo do
    2
  end
  def foo
    yield + yield
  end

  foo do
    next 2
    puts 'never happens'
  end

Here, next is acting a lot like a return statement but for the block.

  def foo
    yield + yield
  end

  foo do
    break 2
    puts 'never happens'
  end

break terminates the execution of the method that the block was passed to and returns the value.

  def bar
    yield + yield
  end

  def foo(&block)
    result = bar(&block)
    puts 'hi there'
    result
  end

  foo do
    break 2
  end

break unwinds all the way to the outside.

Exceptions

  def foo
    raise 'uh-oh'
    puts 'never happens'
  end

  begin
    foo
  rescue
    puts 'rescued exception'
  end

rescue applies inside of begin/end, methods

  def foo
    raise 'uh-oh'
    puts 'never happens'
  end

  def bar
    foo
  rescue
    puts 'rescued exception'
  end

  bar

rescue can tell you about what went wrong

  def foo
    raise 'uh-oh'
    puts 'never happens'
  end

  begin
    foo
  rescue => e
    puts "rescued exception #{e.inspect}"
  end

exceptions can have classes

  class WatException < StandardError; end

  def foo
    raise WatException, 'this is a message'
    puts 'never happens'
  end

  begin
    foo
  rescue => e
    puts "rescued exception #{e.inspect}"
  end

exceptions can be rescued by class

  class WatException < StandardError; end

  def foo
    method_that_doesnt_exist
    raise WatException, 'this is a message'
    puts 'never happens'
  end

  begin
    foo
  rescue WatException => e
    puts "rescued exception #{e.inspect}"
  end

do not rescue Exception

Exception is the root of the entire hierarchy. Some of its subclasses should NOT be rescued.

From http://blog.nicksieger.com/articles/2006/09/06/rubys-exception-hierarchy/ :

Exception
 NoMemoryError
 ScriptError
   LoadError
   NotImplementedError
   SyntaxError
 SignalException
   Interrupt
 StandardError
   ArgumentError
   IOError
     EOFError
   IndexError
   LocalJumpError
   NameError
     NoMethodError
   RangeError
     FloatDomainError
   RegexpError
   RuntimeError
   SecurityError
   SystemCallError
   SystemStackError
   ThreadError
   TypeError
   ZeroDivisionError
 SystemExit
 fatal
  def foo
    exit
  rescue Exception => e
    puts 'welcome to the ruby california'
  end

  foo

The rescue => e form is equivalent to rescue StandardError => e and avoids this behavior.

re-raising exceptions

  class WatException < StandardError; end

  def foo
    raise WatException, 'this is a message'
    puts 'never happens'
  end

  begin
    foo
  rescue => e
    puts "rescued exception #{e.inspect}"
    raise
  end

retrying

  class WatException < StandardError; end

  def foo
    raise WatException, 'this is a message'
    puts 'never happens'
  end

  retries = 0
  begin
    foo
  rescue => e
    puts "rescued exception #{e.inspect}"
    retries += 1
    retry unless retries >= 3
  end

Handy for dealing with things like APIs that time out erratically.

cleaning up

  def foo
    raise 'uh-oh'
    puts 'never happens'
  end

  begin
    foo
  rescue => e
    puts "rescued exception #{e.inspect}"
  ensure
    puts "always happens"
  end

if everything went OK

  def foo
    puts 'never happens except this time'
  end

  begin
    foo
  rescue => e
    puts "rescued exception #{e.inspect}"
  else
    puts "no exception, yay"
  end

throw and catch

You do not 'throw' exceptions in Ruby

  def foo
    throw :huh
  end

  catch :huh do
    foo
  end

  foo

catch allows returning a value from anywhere

  def foo
    throw :huh, 'sekrit werd'
  end

  result = catch :huh do
    foo
  end

One place this is handy is Sinatra's halt method: https://github.com/sinatra/sinatra/blob/b8f95af6510311031e1c7979a3043c585fa39b58/lib/sinatra/base.rb#L940

if nothing is thrown, catch returns the last value from the block

  def foo
    2
  end

  result = catch :huh do
    foo
  end

catch takes a block

  def foo
    throw :huh, 12
  end

  result = catch :huh do
    break 42
    foo
  end

why to use catch

  • creating an exception object captures a stack trace, which can be expensive

  • rescue => e will not swallow throw :foo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.