nclbr nclbr - 3 months ago 15
Ruby Question

Ruby ERB - Create a content_for method

I'm currently working on an ERB View class for a gem. With this class I would like to have some helper methods for ERB templates.

It's okay about basic helpers like

h(string)
. I found
erbh
gem
who help me to understand more how context works.

But now I'm trying to create a
content_for
method like there is in Rails or Sinatra.

On first time I was using simple
Proc
to capture the view block and then just calling
call
method to print it. It was working enough at the beginning.

But after having completed views I saw wired thinks, some content are printed multiple times.

So I take a look on the Sinatra ContentFor helper to understand how they did it and I copy some methods of this helper. I have no errors, but the block return are always empty... and I don't really know why.

My knowledge about ERB are not good enough to know how ERB buffering works.

Code



Here a gist who explain the status of my code. I extracted the code from my library and simplified it a bit.

https://gist.github.com/nicolas-brousse/ac7f5454a1a45bae30c52dae826d587f/66cf76c97c35a02fc6bf4a3bc13d8d3b587356de

What I would like?



I just would like to have
content_for
methods works like they do with Rails and Sinatra.

Thanks!

Answer

After reading this blog article I finally found why it wasn't working. I don't know if I did it in the best way and cleaner way but it works.

So the bug was mainly from the ERB initilization. By using a property instead a local variable as eoutvar it now works.

erb = ERB.new(str, nil, "<>", "@_erbout")

I also change a bit the capture method who is used by content_for helper.

It looks like this now (gist)

def content_for(key, content = nil, &block)
  block ||= proc { |*| content }
  content_blocks[key.to_sym] << capture_later(&block)
end

def content_for?(key)
  content_blocks[key.to_sym].any?
end

def yield_content(key, default = nil)
  return default if content_blocks[key.to_sym].empty?
  content_blocks[key.to_sym].map { |b| capture(&b) }.join
end

def capture(&block)
  @capture = nil
  @_erbout, _buf_was = '', @_erbout
  result = yield
  @_erbout = _buf_was
  result.strip.empty? && @capture ? @capture : result
end

def capture_later(&block)
  proc { |*| @capture = capture(&block) }
end
Comments