Warn about using return inside inline callback blocks [ci skip]#13271
Merged
rafaelfranca merged 1 commit intorails:masterfrom Dec 12, 2013
Merged
Conversation
Member
Author
Member
Author
|
@rafaelfranca fixed, thanks! |
rafaelfranca
added a commit
that referenced
this pull request
Dec 12, 2013
…_as_callbacks Warn about using `return` inside inline callback blocks [ci skip]
Member
|
❤️ 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. |
Member
|
👍 I merged since I'm trying to get the beta out this week, but please feel free to review |
Contributor
There was a problem hiding this comment.
Haha, "meta pull request" -- awesome 😄
coolo
added a commit
to coolo/open-build-service
that referenced
this pull request
May 12, 2014
coolo
added a commit
to coolo/open-build-service
that referenced
this pull request
May 12, 2014
coolo
added a commit
to coolo/open-build-service
that referenced
this pull request
May 14, 2014
coolo
added a commit
to coolo/open-build-service
that referenced
this pull request
May 15, 2014
coolo
added a commit
to openSUSE/open-build-service
that referenced
this pull request
May 16, 2014
This was referenced Aug 28, 2014
Contributor
|
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 |
This was referenced Sep 15, 2021
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
returnstatements 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:
procvslambdaThere are two flavours of
Procobjects in Ruby -procs andlambdas. The rule of thumb is,procbehaves like a block andlambdabehaves like a method. So you canreturnout of a lambda but not in a block:As you can see, returning from a
procblock causes an error, while returning from alambdablock does not. Also note that when you use an&to extract a block argument, it's converted into aprocblock for you, meaning that you normally wouldn't be able toreturnfrom an inline block.(Okay, I lied, you can in fact
returnfrom 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 intoprocs notlambdas).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_forcan 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_createhere 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 falseto 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_blockin a method call expands aprocorlambdainto a block argumentRuby tries to preserve the lambda/proc property of a
Procobject 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
breakdoesn't work, you should read this)Closes #12981