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?
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 %>
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, "Goodby world") %>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With