jeremyevans/roda

Problem using ERB blocks within content_for

njh opened this issue · 4 comments

njh commented

I have been having trouble with not being able to use ERB blocks within a content_for block. It is slightly tricky to describe in words, so I have created a example, which hopefully illustrates the problem. Please let me know if it isn't clear.

Test App

/config.ru

require 'roda'
require 'erubi'

class App < Roda
  plugin :render, :escape => true
  plugin :content_for

  route do |r|

    r.root do
      @hash = {:a => 1, :b => 2, :c => 3}
      view('homepage')
    end
  end

end

run App.freeze.app

/views/homepage.erb

<% content_for :page_title, 'Home' %>
<% content_for :head do %>
  <link rel="canonical" href="http://www.example.com/" />
  <% @hash.each_pair do |key,value| %>
    <meta name="<%= key %>" content="<%= value %>" />
  <% end %>
<% end %>

Hello World

/views/layout.erb

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <title><%= content_for :page_title %></title>
  <%== content_for :head %>
</head>
<body>
  <h1><%= content_for :page_title %></h1>
  <%== yield %>
</body>
</html>

Expected Result

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <title>Home</title>
  <link rel="canonical" href="http://www.example.com/" />
   <meta name="a" content="1" />
   <meta name="b" content="2" />
   <meta name="c" content="3" />
</head>
<body>
  <h1>Home</h1>
  
Hello World

</body>
</html>

Actual Result

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <title>Home</title>
  {:a=>1, :b=>2, :c=>3}
</head>
<body>
  <h1>Home</h1>
  
Hello World

</body>
</html>

Dependency versions:

  • erubi (1.7.1)
  • rack (2.0.6)
  • roda (3.14.0)
  • tilt (2.0.8)

content_for stores the value the block returns, and in your case that is the return value of hash.each_pair, which is the hash itself. To workaround that, try adding an extra line before the end of the content_for block and see if that fixes the issue.

It is possible to modify the content_for plugin to support always using the temporary template output buffer as the return value instead of the result of the block, but it would break when a non-ERB template engine is used. I suppose that could be made conditional on the template engine. Can you try this patch and let me know if it fixes your issue?

diff --git a/lib/roda/plugins/content_for.rb b/lib/roda/plugins/content_for.rb
index 993ff1f..974a105 100644
--- a/lib/roda/plugins/content_for.rb
+++ b/lib/roda/plugins/content_for.rb
@@ -63,6 +63,9 @@ class Roda
               # Use temporary output buffer for ERB-based rendering systems
               instance_variable_set(outvar, String.new)
               value = Tilt[render_opts[:engine]].new{yield.to_s}.render
+              if render_opts[:engine] == 'erb'
+                value = instance_variable_get(outvar)
+              end
               instance_variable_set(outvar, buf_was)
             end
njh commented

Thanks for the fast response.

Yes, an extra line before the end of the content_for block does work.
I thought I had tried that before, but clearly not.

And your patch also works for me - I think it will make the behaviour in an ERB template less 'surprising'!

Unfortunately, the patch would break the case where you use a block normally without a template:

content_for(:foo){"bar"}

So I think the documentation needs to be clear that the content stored is the block output, and to discuss this issue and how to work around it.

njh commented

Thanks Jeremy!

BTW, I built this website using Roda, Sequel and Erubi:
https://www.radiodns.uk/

Thanks for all the nice Open Source things you have made 🙂