block_given? always returns true in erb templates
Asked Answered
U

2

11

In Rails 5.2.3, I need to render a partial which takes an optional block.

# users/_user.html.erb
...
<% if block_given? %>
  <%= yield %>
<% else %>
  <h1>Goodbye world</h1>
<% end %>
...

However block_given? returns true regardless of which version I choose to go with:


<%# Version 1 - block_given? returns true %>
<%= render partial: "users/_user" do %>
  <h1>hello world</h1>
<% end %>

<%# Version 2 - block_given? also returns true %>
<%= render partial: "users/_user" %>

What's going on here and why is this happening?

Unmeet answered 22/10, 2019 at 19:24 Comment(0)
D
10

Because all Rails templates support content_for :xyz, which is triggered by yield :xyz, it means all templates are always wrapped in a block that is prepared to fetch this content_for data.

Because this pre-programmed block is always there in order to accommodate content_for, it means block_given? will always return true.

I think this may actually be a small oversight in the Rails view design. It would be nice if we'd have a separate method to detect if a partial was supplied a block.

One idea for workaround:

<% if (block = yield).empty? %>
  <h1>Goodbye world</h1>
<% else %>
  <%= block %>
<% end %>
Darlington answered 23/10, 2019 at 11:44 Comment(1)
Great answer! Thanks.Unmeet
P
4

While being clever and a generic solution, I'm not a fan of the (block = yield).empty? in that particular instance.

In my use case and this one, where the default content is so simple, I prefer this approach:

<%= yield.presence || content_tag(:h1, "Goodbye world") %>
Pedanticism answered 19/3, 2021 at 15:45 Comment(1)
Still relevant for Rails 7. ThanksSavate

© 2022 - 2024 — McMap. All rights reserved.