How do I call a method in a helper from a Haml file?
In sample.haml, I need to call the show_message method depending on some condition. Then I moved the method to the helper, but the returned value from the method is treated as just a string, not a Haml element.
This is sample.haml:
- flash.each do |msg|
- if msg.is_a?(Array)
- msg.each do |m|
= show_message(m)
- if msg.is_a?(String)
= show_message(msg)
This is helper.rb:
def show_message(msg)
haml = <<-HAML
%div{class: some_class}
= content_tag :div, #{msg}, id: "id"
HAML
end
If I write the same HTML element in show_message in sample.html directly, it works properly. How can I solve this?
Your helper method needs to construct the full HTML via methods. Since it's not actually part of the HAML, you cannot rely on syntax like %div{class: some_class}. Something like this:
def show_message(msg)
content_tag(:div, class: 'some_class') do
content_tag(:div, msg, id: "id")
end
end
See the content_tag documentation for more usage examples.
Related
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!
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
I'm working on a Sinatra app and want to write my own form helpers. In my erb file I want to use the rails 2.3 style syntax and pass a block to a form_helper method:
<% form_helper 'action' do |f| %>
<%= f.label 'name' %>
<%= f.field 'name' %>
<%= f.button 'name' %>
<% end %>
Then in my simplified form helper I can create a FormBuilder class and yield the methods to the erb block like so:
module ViewHelpers
class FormBuilder
def label(name)
name
end
def field(name)
name
end
def button(name)
name
end
end
def form_helper(action)
form = FormBuilder.new
yield(form)
end
end
What I don't understand is how to output the surrounding <form></form> tags. Is there a way to append text on only the first and last <%= f.___ %> tags?
Rails has had to use some tricks in order to get block helpers to work as wanted, and they changed moving from Rails 2 to Rails 3 (see the blogposts Simplifying Rails Block Helpers and Block Helpers in Rails 3 for more info).
The form_for helper in Rails 2.3 works by directly writing to the output buffer from the method, using the Rails concat method. In order to do something similar in Sinatra, you’ll need to find a way of writing to the output from your helper in the same way.
Erb works by creating Ruby code that builds up the output in a variable. It also allows you to set the name of this variable, by default it is _erbout (or _buf in Erubis). If you change this to be an instance variable rather than a local variable (i.e. provide a variable name that starts with #) you can access it from helpers. (Rails uses the name #output_buffer).
Sinatra uses Tilt for rendering templates, and Tilt provides an :outvar option for setting the variable name in Erb or Erubis templates.
Here’s an example of how this would work:
# set the name of the output variable
set :erb, :outvar => '#output_buffer'
helpers do
def form_helper
# use the new name to write directly to the output buffer
#output_buffer << "<form>\n"
# yield to the block (this is a simplified example, you'll want
# to yield your FormBuilder object here)
yield
# after the block has returned, write any closing text
#output_buffer << "</form>\n"
end
end
With this (fairly simple) example, an Erb template like this:
<% form_helper do %>
... call other methods here
<% end %>
results in the generated HTML:
<form>
... call other methods here
</form>
What would the erb template look like for a ruby enumerator? The answer will be a erb template.
require "erb"
# build data class
class Foo < Array
def build
b = binding
# create and run templates, filling member data variables
ERB.new(File.read('test2.erb')).result b
end
end
# setup template data
bar = Foo.new([1,2,3])
puts bar.build
I would like some way of accessing the 1,2,3 items in the erb template.
Focus on Ruby 1.9.3 compatibility.
Note: the Class is an extension of Array, and I want to access the elements of this array in its erb template.
Ok, it was as simple as reaching into the self reference.
<% self.each{|element| %> <%= element %> <% } %>
I have a bit of HAML code that gets repeated in something like 10 views and would like to put it into a helper of some kind. Doing a search here produced some interesting results but ultimately no answers, so:
In application_helper.rb I have this:
def pagination_helper(object)
haml_tag :div, :class => 'apple_pagination page_info' do
page_entries_info #book_formats
paginate #book_formats
end
end
In the view template I have this:
- pagination_helper(#book_formats)
If I try calling it with = to output something I get an error.
The above will not give me an error but it won't call the methods either. I get empty divs.
Ultimately the code I want to repeat is this:
.apple_pagination.page_info
= page_entries_info #book_formats
= paginate #book_formats
The code would be the same except the object would change and I send that from the view template. For example #book_formats would change to #dvds, etc.
The block passed to haml_tag doesn’t automatically get added to the output. You need to use haml_concat:
def pagination_helper(object)
haml_tag :div, :class => 'apple_pagination page_info' do
haml_concat(page_entries_info #book_formats)
haml_concat(paginate #book_formats)
end
end
I'm trying to use the Thor::Actions template method to generate some C++ test file templates, but erb keeps telling me that I have undefined variables and methods.
Here's the calling code:
def test (name, dir)
template "tasks/templates/new_test_file", "src/#{dir}/test/#{name}Test.cpp"
insert_into_file "src/#{dir}/test/CMakeLists.txt",
"#{dir}/test/#{name}Test ", :after => "set(Local "
end
Here's the template:
<% test_name = name + "Test" %>
#include <gtest/gtest.h>
#include "<%= dir %>/<%= name %>.h"
class <%= test_name %> : public testing::Test {
protected:
<%= test_name %> () {}
~<%= test_name %> () {}
virtual void SetUp () {}
virtual void TearDown () {}
};
// Don't forget to write your tests before you write your implementation!
TEST_F (<%= test_name %>, Sample) {
ASSERT_EQ(1 + 1, 3);
}
What do I have to do to get name and dir into scope here? I have more complex templates that I need this functionality for too.
I realize you already solved this, but I'm posting this answer in case someone else turns up looking for the solution to the question you asked (as I was).
Inside the class that #test belongs to, make an attr_accessor, then set its value in the same method that calls the template.
class MyGenerator < Thor
attr_accessor :name, :dir
def test (name, dir)
self.name = name
self.dir = dir
template "tasks/templates/new_test_file", "src/#{dir}/test/#{name}Test.cpp"
end
end
Note: that if you chain methods using #invoke, then a new instance of the class will be used for each invocation. Therefore you have to set the instance variable in the method with the template call. For example, the following wont work.
class MyGenerator < Thor
attr_accessor :name
def one (name)
self.name = name
invoke :two
end
def two (name)
# by the time we get here, this is another instance of MyGenerator, so #name is empty
template "tasks/templates/new_test_file", "src/#{name}Test.cpp"
end
end
You should put self.name = name inside #two instead
For making generators, if you inherit from Thor::Group instead, all the methods are called in order, and the attr_accessor will be set up for you with the instance variables set for each method. In my case, I had to use Invocations instead of Thor::Group because I couldn't get Thor::Group classes to be recognized as subcommands of an executable.
ERB uses ruby's binding object to retrieve the variables that you want. Every object in ruby has a binding, but access to the binding is limited to the object itself, by default. you can work around this, and pass the binding that you wish into your ERB template, by creating a module that exposes an object's binding, like this:
module GetBinding
def get_binding
binding
end
end
Then you need to extend any object that has the vars you want with this module.
something.extend GetBinding
and pass the binding of that object into erb
something.extend GetBinding
some_binding = something.get_binding
erb = ERB.new template
output = erb.result(some_binding)
for a complete example of working with ERB, see this wiki page for one of my projects: https://github.com/derickbailey/Albacore/wiki/Custom-Tasks