7

In Rails, blocks can be used as callbacks, e.g.:

class User < ActiveRecord::Base
  validates_presence_of :login, :email

  before_create {|user| user.name = user.login.capitalize
    if user.name.blank?}
end

When a block is used like this, is there any use for break and return? I'm asking because normally in a block, break will break out of the loop, and return will return from the enclosing method. But in a callback context, I can't get my head round what that means.

The Ruby Programming Language suggests that return could cause a LocalJumpError but I haven't been able to reproduce this in a Rails callback.


Edit: with the following code I'd expect a LocalJumpError, but all the return does is stop the rest of the callback executing.

class User < ActiveRecord::Base
  validates_presence_of :login, :email

  before_create do |user|
    return
    user.name = user.login.capitalize
end

2 Answers 2

11

Actually it's kind of interesting...

When you use before_create in Rails 3, we take the block or lambda that you give us and convert it into a method. We then invoke the method with the current ActiveRecord object, for backwards compatibility with the old Rails approach.

As a result, the following is equivalent to your snippet:

class User < ActiveRecord::Base
  validates_presence_of :login, :email

  before_create do
    self.name = login.capitalize if name.blank?
  end
end

Because of this behavior, you can call return from the block, and it will behave the same as a return in a normal method (because it is a normal method).

In general, next in a block "returns" from the block.

The specific behavior of normal blocks is:

  • When you call next, you are skipping the rest of the block, and returning control to the method that invoked the block.
  • When you call break, you are skipping the rest of the block, and also immediately returning from the method that invoked the block.

You can see that behavior in normal iterators:

value = [1,2,3,4].each do |i|
  next if i == 2
  puts i
end

In this case, value will be [1,2,3,4], the normal return value of the each method, and the output will be:

1
3
4

In the case of break:

value = [1,2,3,4].each do |i|
  break if i == 2
  puts i
end

In this case, the value will be nil, since the break also immediately returned from the each method. You can force a return with a specific value by using break n, which will make value the same as n. The output in the above case will be:

1

The important thing is that next and break do not just apply to iterators, although their semantics are designed to behave like their equivalents in C in the case of iterators.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks, that actually makes a lot of sense.
3

The operation of return within Ruby blocks depends whether the block was constructed with Proc.new or lambda.

I recommend you read the highest rated answer on this other Stack Overflow question: When to use lambda, when to use Proc.new?

In this case, the block has the properties of one created with Proc.new.

Calling return in the context of that kind of callback would only make sense if this also made sense (which it obviously doesn't):

class Foo
  return "bar"
end

10 Comments

@Andy - thanks. In this case the block is a Proc, but it wasn't constructed with Proc.new or lambda - it was passed in as a block. Or are you talking about inside before_create?
@Andy - having read that answer, the problem is that in that case it's obvious that return will return from the containing method. But what's the containing method if it's in a class definition?
In this case, the callback is basically a Proc.new created block.
So if I return from that block, what am I returning out of?
You're returning out of the context where it was created. This causes a LocalJumpError if it's executed in a different context.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.