Render ERB as HTML and ERB from a Rails View - ruby-on-rails

I'm making a style guide where I output the code on the right that is displayed on the left.
I know that adding %% escapes ERB
I have written a helper that takes the contents of a block and renders the code in two places one showing the html and I want the other to show the source ERB that created the html.
The problem is I get back HTML where I wanted ERB.
The View Code
<%= display_code do %>
<%= link_to "Button", "/style_guide, class: "btn" %>
<% end %>
The Helper Code
module StyleGuideHelper
def display_code(&block)
content = with_output_buffer(&block)
html = ""
html << content_tag(:div, content, class: "rendered-code")
html << content_tag(:div, escape_erb(content), class: "source-code-preview")
html.html_safe
end
def escape_erb(code)
code = code.gsub("%=", "%%=")
end
end
Expected Result
Button <%= link_to "Button", "/style_guide, class: "btn" %>
Actual Result
Button Button
Cheers

The issue is that this helper runs the block (link_to "Button", ...) -- it never sees the source code inside the block, just its output. You could replace escape_erb with h to capture the resulting HTML, but that won't pop back up to the ERB that generated it.
As I see it, your options are:
Break out examples into partials, then make a helper that a) renders the partial and b) displays the underlying file.
Specify your ERB fragments as strings (heredocs?), pass the string into the helper, and have the helper a) evaluate it via ERB.new(string).result(binding) to render the result and b) display the string.
Make the helper determine what part of the view invoked it, then parse the .erb well enough to find the block. Catch is, the precise format of what you see in callers is subject to change without notice due to the way views are compiled.
Make a helper that uses crazy metaprogramming juju to evaluate the block in both an ERB context as well as your own special context that intercepts the code being evaluated and turns it back into markup.
...sorted in approximate order of complexity and odds of success.

This code below will allow you to retrieve the code for a given block.
class ERBSource
ERB = ::ActionView::Template::Handlers::ERB
def self.for(block)
new(block).source
end
attr_reader :block, :file, :line_number
def initialize(block)
#block = block
#file, #line_number = *block.source_location
end
def source
lines = File.readlines(file)
relevant_lines = lines[(line_number - 1)..-1] || []
extract_first_expression(relevant_lines)
end
private
def extract_first_expression(lines)
code = lines.slice[0,1].join # add the first two lines so it has to iterate less
lines.each do |line|
code << line
return code if correct_syntax?(compile_erb(code))
end
raise SyntaxError, "unexpected $end"
end
def correct_syntax?(code)
stderr = $stderr
$stderr.reopen(IO::NULL)
RubyVM::InstructionSequence.compile(code)
$stderr.reopen(stderr)
true
rescue Exception
$stderr.reopen(stderr)
false
end
def compile_erb(code)
ERB.erb_implementation.new(
code,
:escape => false,
:trim => (ERB.erb_trim_mode == "-")
).src
end
end
This is what the helper looks like
module StyleGuideHelper
def render_example(name, &block)
code = ERBSource.for(block)
content_tag(:h2, name) +
content_tag(:div, &block) +
content_tag(:pre, content_tag(:code, code))
end
end

Related

Adding a class to a helper method call

So I have a helper method that I am trying to apply css to without putting it in a div or any other element. How would I go about applying the css class to this helper in rails?
I tried:
<%= first_letter_content(e.content), :class => "first-letter" %>
and
<%= (first_letter_content(e.content), :class => "first-letter") %>
both resulting in syntax error, unexpected ',', expecting ')'
Helper code:
def first_letter_content(content)
first_letter = content[0]
return first_letter
end
Any suggestions? I have been trying to find the proper syntax, but no luck.
Your helper does not support options (extra args) but you are trying to give a HTML class to the element.
You should wrap the content of first_letter_content inside a div/span (depending on what you want, block or inline) and apply the class on this HTML element:
<div class='first-letter'>
<%= first_letter_content(e.content) %>
</div>
Or you can directly wrap the content[0] inside a div in the helper method:
def first_letter_content(content, options = {})
content_tag(:div, content[0], options)
end
And use it like this:
first_letter_content(content, class: 'first-letter')
first_letter_content(content, class: 'first-letter', id: 'something')
first_letter_content(content)
Also, you can refactor your helper method to this:
def first_letter_content(content)
content[0]
end
It is a minor improvement but in Ruby the "last thing" used in a method will be returned by this method.
Examples:
def something
a = 2
b = 3
a
end
# => returns `2`
def something_else
a = 2
b = 3
end
# => returns `3`
def whatever
a = 12
nil
end
# => returns `nil`
I am trying to apply css to without putting it in a div or any other element
Css classes are for DOM elements, so you should wrap this content into some element/node.
For example:
def first_letter_content(content, css_class)
content_tag(:div, content[0], class: css_class)
end
Call:
<%= first_letter_content(e.content, "first-letter") %>

Rails write variable in view without using <%= %>

Let's say I've got the variable #var. Usually I would use <%= #var %> to write it into the view. Now I want to call a module method from within my view which internally decides to write the content of #var or not.
Inside the module I can't use <%= %>. How can I print the content of #var from within the module? The method would be called like this: <% my_method %>. Thanks
Update
Thanks for the answers so far. Maybe I should say more about my initial problem to be more clear. Sorry if I wasn't clear enough.
At first I used the <%= %> tag like this:
def print_if_present(var)
var ? var : ""
end
<%= print_if_present var %>
But then, when the var was nil, I got "" as output, which took space in the view. How can I prevent this behavior?
I assume that your module is actualy the view helper. If is that so, simply return var.
def my_method
if my_condition
#var
else # else clause is optional
#other_var
end
end
Note that the else clause is optional. If you want to write something or nothing, you can simply use the if. This is so because if the if is not executed and there is no else, it will return nil, that will be casted to an empty string in your template. Just to ilustrate,
if true
1
end
=> 1 #return if last value
if false
1
end
=> nil # return nil because there is no else block
Since you still want to print the return of your method on your template, you need to keep the equal sign:
<%= my_method %>
The best way to do this is to have your method return the string and use <%= ... %> as in fotanus’ answer, but in Rails if you really need to write output directly from a helper you could use the concat method:
The preferred method of outputting text in your views is to use the <%= “text” %> eRuby syntax. The regular puts and print methods do not operate as expected in an eRuby code block. If you absolutely must output text within a non-output code block (i.e., <% %>), you can use the concat method.
So you can define a helper like this:
def my_method
if some_condition
concat "Something or other"
else
concat "Something else"
end
end
And then use it in a non-output block:
<% my_method %>

method attributes [ajax,jquery,rails4]

I am reading the book Agile web developpment with rails 4.
there is a part where the products' cart is showing only if it is not empty, my question is the function in the view send to the helper only 2 attributes while in the implementation there are 3 parameters.
in the view I have the bellow code, which render to _cart where I have the cart show
<%= hidden_div_if(#cart.line_items.empty?, id: 'cart') do %>
<%= render #cart %>
<% end %>
the helper has:
module ApplicationHelper
def hidden_div_if(condition, attributes = {}, &block)
if condition
attributes["style"] = "display: none"
end
content_tag("div", attributes, &block) end
end
My question is the &block in this case receives id: 'cart' but is it a optional attibute? that why it comes with &. but what about attributes = {}?
I am really not sure how that is happening, could someone explain me a bit?
Thanks!!
The code between and including do and end is the block, and this is the third argument for hidden_div_if, which is simply passed on to content_tag. The & in the definition of hidden_div_if captures the block in your view, whereas the & in the call to content_tag expands it again to pass it along.
The answer here explains this idea nicely with a few examples. I recommend testing everything out yourself in irb to get a feel for it.

ERB -- content_for_with_default

I am trying to reduce the repetitive code with the following pattern in an ERB template:
<% if content_for(some_key) %>
<%= yield(some_key) %>
<% else %>
Some default values here
<% end %>
I've tried defining the following method in ApplicationHelper but understandably it's not working as expected;
def content_for_with_default(key, &block)
if content_for?(key)
yield(key)
else
block.call
end
end
Here's how I'm trying to use it:
<%= content_for_with_default(some_key) do %>
Some default values here
<% end %>
How can I write the content_for_with_default helper so that it has the intended effect?
Your helper should be like this:
def content_for_with_default(key, &block)
if content_for?(key)
content_for(key)
else
capture(&block)
end
end
EDIT: difference between capture(&block) and block.call
After the erb file is compiled, the block will be some ruby code like this:
');#output_buffer.append= content_for_with_default('some_key') do #output_buffer.safe_concat('
');
#output_buffer.safe_concat(' Some default values here
'); end
You see, the strings within the block are concatenated to the output_buffer and safe_concate returns the whole output_buffer.
As a result, block.call also returns the whole output_buffer. However, capture(&block) creates a new buffer before calling the block and only returns the content of the block.

I can't figure out helper in Rails

Hey guys
I'm new to rails. I made this small test code for learning helper in rails:
apps/helpers/home_helper.rb
module HomeHelper
def show(var)
yield var
end
end
apps/views/home/index.html.rb
<%= show('hello world')%>
When I navigate to the url localhost:3000/home/index I got nothing in the html source
What did I do wrong?
There are few things to note here:
module HomeHelper
def show(var)
yield var
end
end
Firstly you're using yield which will pass control to the block given to the method. However, you then call the method without a block:
<%= show('hello world') %>
If you would have had a block it would have looked something like this:
<%= show('hello world') do |v| %>
<%= v %>
<% end %>
This would have output 'hello world' as you expected.
Most like you meant:
module HomeHelper
def show(var)
var
end
end
This returns the value you're passing in and will output it to the response stream.
While block helpers can often to be useful for drying up your code most of the time you want a partial with a layout.
why are you doing yield? Go with simple return:
def show(var)
var
end
Remove the yield. yield is meant for blocks - did you mean return? (it is optional).
Another (arguably better) option is to set the display text in your controller.
home_controller.rb
HomeController < ActionController
# Other controller code...
def index
#text = "Hello, world!"
end
end
index.html.erb
<%= #text %>

Resources