rails/rails — pull request #13271 by chancancode (2013-12-11 12:59:03+00:00)
Title: Warn about using `return` inside inline callback blocks [ci skip]

The Problem
=========

In previous versions of Rails, you used to be able to do this:

```ruby
class MyModel < ActiveRecord::Base
before_save do
return if special_snow_flake?
# ...more checks...
end
end
```

...or...

```ruby
class MyController < ApplicationController
before_action do
if maintenance_mode?
render text: "

Maintenance Mode

" and return
end
end
end
```

**This is no longer possible.** Using a `return` (or `break`) inside a inline callback block now raises a `LocalJumpError`.

**This behaviour was never intentionally supported.**

The Solution
=========

If you are currently using `return` statements inside an inline callback block, it is recommended that you explicitly define it as a method and pass a symbol to the callback macro instead (i.e. `before_save :check_xyz`).

The Details
========

**The short version**: it was working before because internally Rails used to turn the inline callbacks into actual methods on the class. It doesn't do that anymore, and returning from a block like this is not something you can do.

The long version requires a bit more explanation:

## `proc` vs `lambda`

There are two flavours of `Proc` objects in Ruby - `proc`s and `lambda`s. The rule of thumb is, `proc` behaves like a block and `lambda` behaves like a method. So you can `return` out of a lambda but not in a block:

```ruby
# You cannot return from a proc
>> p = proc { return 1 }
=> #
>> p.lambda?
=> false
>> p.call
LocalJumpError: unexpected return
from (irb):1:in `block in irb_binding'
from (irb):3:in `call'
from (irb):3
from /Users/godfrey/.rvm/rubies/ruby-2.0.0-p353/bin/irb:12:in `

'

# But you can return from a lambda
>> l = lambda { return 1 }
=> #
>> l.lambda?
=> true
>> l.call
=> 1

# Block arguments are converted into procs
>> def extract_block(&block); block; end
=> nil
>> e = extract_block { return 1 }
=> #
>> e.lambda?
=> false
>> e.call
LocalJumpError: unexpected return
from (irb):10:in `block in irb_binding'
from (irb):12:in `call'
from (irb):12
from /Users/godfrey/.rvm/rubies/ruby-2.0.0-p353/bin/irb:12:in `

'

# While methods are converted into lambdas
>> class A; def a; return 1; end; end
=> nil
>> a = A.new.method(:a).to_proc
=> #
>> a.lambda?
=> true
>> a.call
=> 1
```

As you can see, returning from a `proc` block causes an error, while returning from a `lambda` block does not. Also note that when you use an `&` to extract a block argument, it's converted into a `proc` block for you, meaning that you normally wouldn't be able to `return` from an inline block.

(Okay, I lied, you *can* in fact `return` from a `proc`, but it almost certainly does *not* do what you expect, so you should just never do that. @fxn gave an excellent explanation of that [here](https://github.com/rails/rails/issues/12981#issuecomment-30209389))

## Why was it working before?

In previous versions of Rails, inline callback blocks are internally [being defined as methods on the class](https://github.com/rails/rails/blob/4-0-stable/activesupport/lib/active_support/callbacks.rb#L267-L272). Therefore you were allowed to do anything you can normally do inside a method, including using `return`.

As of 2b1500d6, these inline blocks are being `instance_exec`-ed directly rather than being defined into methods. Therefore, this stopped working (again, because block arguments are extracted into `proc`s not `lambda`s).

## What should I do moving forward?

As mentioned above, you should just explicitly define the callback blocks as methods and pass a symbol to the callback "macros". This also has a side-effect of making these methods easily testable should the need arise.

However, there is one scenario that this might be challenging – dynamic callbacks.

Let's say you are writing a library that supplies (dynamic) default values for ActiveRecord models:

```ruby
module HasDefaultValueFor
extend ActiveSupport::Concern

module ClassMethods
def has_default_value_for(field, value = nil, &block)
before_create do
return unless self.send(field).present?

if block
self.send("#{field}=", block.call(self))
else
self.send("#{field}=", value)
end

# Always return true, otherwise when value is false or the block
# evaluates to false, then the record won't be saved
return true
end
end
end
end

ActiveRecord::Base.send(:include, HasDefaultValueFor)

# Usage
#
# class User < ActiveRecord::Base
# has_default_value_for :display_name { |u| u.first_name }
# has_default_value_for :receive_marketing_emails, true
# end
```

In this case, since `has_default_value_for` can be called multiple times on the same model class, it would be a little annoying having to define explicit methods and deal with potential name conflicts, etc. Using the inline block form of `before_create` here seems quite apt.

While it's possible to unfold the conditionals so that an explicit return isn't necessary, but it would be quite difficult in other cases especially when you are trying to explicitly `return false` to halt the callback chain.

In these cases you can consider using the following trick:

```ruby
before_create(&->{
return unless self.send(field).present?

if block
self.send("#{field}=", block.call(self))
else
self.send("#{field}=", value)
end

# Always return true, otherwise when value is false or the block
# evaluates to false, then the record won't be saved
return true
})
```

It takes advantage of a few things:

1. `->{ }` is the new syntax for `lambda{ ... }`
2. `&some_block` in a method call expands a `proc` or `lambda` into a block argument
3. Ruby tries to preserve the lambda/proc property of a `Proc` object whenever possible:

```ruby
>> l = lambda { return 1 }
=> #
>> p = proc { return 1 }
=> #
>> lambda(&p).lambda?
=> false
>> proc(&l).lambda?
=> true
>> def extract_block(&block); block; end
=> nil
>> extract_block(&l).lambda?
=> true
>> extract_block(&p).lambda?
=> false
>> def call_block(&block); block.call; end
=> nil
>> call_block { return 1 }
LocalJumpError: unexpected return
from (irb):10:in `block in irb_binding'
from (irb):9:in `call'
from (irb):9:in `call_block'
from (irb):10
from /Users/godfrey/.rvm/rubies/ruby-2.0.0-p353/bin/irb:12:in `

'
>> call_block &p
LocalJumpError: unexpected return
from (irb):3:in `block in irb_binding'
from (irb):9:in `call'
from (irb):9:in `call_block'
from (irb):11
from /Users/godfrey/.rvm/rubies/ruby-2.0.0-p353/bin/irb:12:in `

'
>> call_block &l
=> 1
```

**This is JustRuby(tm), and it's [documented behaviour](http://www.ruby-doc.org/core-2.0.0/Proc.html#method-i-lambda-3F), no black magic involved.** The only case this won't help you is when you try to yield a block:

```ruby
>> def a; yield; end
=> nil
>> a { return 1 }
LocalJumpError: unexpected return
from (irb):2:in `block in irb_binding'
from (irb):1:in `a'
from (irb):2
from /Users/godfrey/.rvm/rubies/ruby-2.0.0-p353/bin/irb:12:in `

'
>> a &->{ return 1 }
LocalJumpError: unexpected return
from (irb):3:in `block in irb_binding'
from (irb):1:in `a'
from (irb):3
from /Users/godfrey/.rvm/rubies/ruby-2.0.0-p353/bin/irb:12:in `

'
```

However I think this is unlikely to become a problem for `ActiveSupport::Callbacks`.

## Further Reading

http://readruby.io/closures (Hint: if you are wondering why `break` doesn't work, you should read this)