-
Notifications
You must be signed in to change notification settings - Fork 21.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Warn about using return
inside inline callback blocks [ci skip]
#13271
Warn about using return
inside inline callback blocks [ci skip]
#13271
Conversation
This behaviour was never intentionally supported. Due to a change in the interns | ||
of `ActiveSupport::Callbacks`, this is no longer allowed in Rails 4.1. Using a | ||
`return` statement in an inline callback block will cause a `LocalJumpError` to | ||
be raised when the callback is executed. If you need to use `reutrn` statements |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo return
@rafaelfranca fixed, thanks! |
…_as_callbacks Warn about using `return` inside inline callback blocks [ci skip]
❤️ sorry for not giving feedback couldn't yet have a look at the patch other than a quick scan, I'll do it soon anyway. |
👍 I merged since I'm trying to get the beta out this week, but please feel free to review |
|
||
This change applies to most places in Rails where callbacks are used, including | ||
Active Record and Active Model callbacks, as well as "filters" in Action | ||
Controller (e.g. `before_action`). See [this pull request](https://github.com/rails/rails/pull/13271) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Meta pull request 🤘
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haha, "meta pull request" -- awesome 😄
Just a small point. It looks like you can use >> a = proc { return false; puts 'nope' }
=> #<Proc:0x007fabcd9c9250@(irb):1>
>> a.call
LocalJumpError: unexpected return
from (irb):1:in `block in irb_binding'
from (irb):2:in `call'
from (irb):2
from /Users/rywall/.rbenv/versions/2.1.5/bin/irb:11:in `<main>'
>> b = proc { next false; puts 'nope' }
=> #<Proc:0x007fabcda1b758@(irb):3>
>> b.call
=> false |
The Problem
In previous versions of Rails, you used to be able to do this:
...or...
This is no longer possible. Using a
return
(orbreak
) inside a inline callback block now raises aLocalJumpError
.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
vslambda
There are two flavours of
Proc
objects in Ruby -proc
s andlambda
s. The rule of thumb is,proc
behaves like a block andlambda
behaves like a method. So you canreturn
out of a lambda but not in a block:As you can see, returning from a
proc
block causes an error, while returning from alambda
block does not. Also note that when you use an&
to extract a block argument, it's converted into aproc
block for you, meaning that you normally wouldn't be able toreturn
from an inline block.(Okay, I lied, you can in fact
return
from aproc
, 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)Why was it working before?
In previous versions of Rails, inline callback blocks are internally being defined as methods on the class. Therefore you were allowed to do anything you can normally do inside a method, including using
return
.As of 2b1500d, 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 intoproc
s notlambda
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:
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 ofbefore_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:
It takes advantage of a few things:
->{ }
is the new syntax forlambda{ ... }
&some_block
in a method call expands aproc
orlambda
into a block argumentRuby tries to preserve the lambda/proc property of a
Proc
object whenever possible:This is JustRuby(tm), and it's documented behaviour, no black magic involved. The only case this won't help you is when you try to yield a block:
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)Closes #12981