Search code examples
ruby-on-railsrubyactivesupport

How do I override ActiveSupport::SafeBuffer when I yield a hash provided in a view


My profile.html.erb provides a hash upstream to the layout.html.erb to be included in the metadata

  <% provide(:twitter, { %>
  <% card: 'summary', %>
  <% site: '@twitter', %>
  <% title: 'Lorem ipsum', %>
  <% description: 'Non sequitar', %>
  <% image: gravatar_for(@user, { size: 150 } )%>
  <% }) %>

The layout.html.erb then is supposed to iterate over this and create the meta tags:

  <% yield(:twitter).each do |key, value|%>
  <meta name="twitter:#{key}" content="#{value}" />
  <% end %>

However, it gives me the error undefined method 'each' for #<ActiveSupport::SafeBuffer:0x007f8d616e5870>, and after inspecting yield(:twitter) with byebug I see that I yield an instance of ActiveSupport::SafeBuffer:

(byebug) hash = yield(:twitter)
"{:card=&gt;&quot;summary&quot;, :site=&gt;&quot;@twitter&quot;, :title=&gt;&quot;Lorem ipsum&quot;, :description=&gt;&quot;Non sequitar&quot;, :image=&gt;&quot;https://secure.gravatar.com/avatar/a937016807c66d89d0a62d7a1b235533?s=150&quot;}"
(byebug) hash.class
ActiveSupport::SafeBuffer

I could eval the string into a hash, but ActiveSupport::SafeBuffer encodes it html safe, and there's no method to get the original back out. Is there a smarter way to do this, or do I have to hack around it with a helper converting the string back into a hash, and then eval()ing it into a has?


Solution

  • Since provide expects either a string or a block then it might be better to create the meta tags in a helper and pass it as string argument.

    module TwitterHelper
      def twitter_meta_tags(hash)
         hash.each_with_object([]) do |(key, value), array|
            a.push( tag(:meta, { name: "twitter:#{k}", content: v }, false, false ) )
         end.join("\n").html_safe
         # not sure if that last html_safe is really needed.
      end
    end
    

    Downstream view:

    <%
    provide(:twitter, twitter_meta_tags({
      foo: "bar"
    }));
    %>
    

    Layout:

    <%= yield(:twitter) %>
    

    Using a partial

    Another option is to use a partial and pass a block instead of a string to provide:

    <% # app/views/twitter/_meta_tags.html.erb %>
    <% hash.each do |key, value|%>
      <meta name="twitter:#{key}" content="#{value}" />
    <% end %>
    

    Downstream view:

    <% provide(:twitter) do %>
      <%= render partial: "twitter/meta_tags", hash: {
        card: 'summary', 
        site: '@twitter',
        title: 'Lorem ipsum',
        description: 'Non sequitar',
        image: gravatar_for(@user, { size: 150 } )
      } %>
    <% end %>
    

    Layout:

    <%= yield(:twitter) %>