I have a controller which does the following line before rendering the view and outputting an error.
flash[:error]="Flash error"
flash[:info] = "Flash info"
I would like to format this nicely. For that I wrote a helper which looks like this
def show_flash
a=""
[:success, :info, :error, :warning].each do |key|
a += content_tag(:div, flash[key], :id => key, :class => "#{key}") unless flash[key].blank?
end
end
In my view, I call:
<%= show_flash %>
When I try to run this, the web page renders the full text of show_flash, including the div tags, angle brackets and all. When I inspect the element (using Firefox or Chrome), it shows the text surrounded with double quotes.
Then I tried changing one line in the helper as follows:
a = content_tag(:div, flash[key], :id=>key, :class=>"#{key]") unless flash[key].blank?
i.e. I would only capture the last content tag (error) instead of both of them.
In the second case, the web browser rendered the div tag formatted properly with my CSS rules for the "error" class. I didn't see any div tags printed out in the browser.
Why did concatenating two content_tag elements cause me this grief?
I appreciate any help you can give me.
Because "" wasn't marked as html_safe. This is part of Rails' XSS protection that is enabled by default in Rails 3.
You may find this Railscast on XSS protection informative.
It turns out that when going from Rails 2 to Rails 3, html escaping is enabled by default, and you must explicitly disable it before concatenating content_tag strings. The code looks like:
def show_flash
a=content_tag(:span, "",:escape=>false)
[:success, :info, :error, :warning].each do |key|
a = a+content_tag(:div, flash[key], :id => key, :class => "#{key}", :escape=>false) unless flash[key].blank?
end
a
end
That option, :escape=>false is what it took to make it work.
Andrew Marshall pointed me in the right direction, and after some searching, I stumbled on the words of wisdom from Yehuda. That's where the :escape clause became obvious.
Related
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
Using rails 2. I want a link to the current page (whatever it is) that keeps all of the params the same but changes the format to 'csv'. (setting the format can be done by having format=csv in the params or by putting .csv at the end of the path). Eg
posts/1
=> posts/1.csv OR posts/1?format=csv
posts?name=jim
=> posts.csv?name=jim OR posts?name=jim&format=csv
I tried this as a hacky attempt
request.url+"&format=csv"
and that works fine if there are params in the current url (case 2 above) but breaks if there aren't (case 1). I could come up with more hacky stuff along these lines, eg testing if the request has params, but i'm thinking there must be a nicer way.
cheers, max
EDIT - btw, it's not guaranteed that the current page could have a named route associated with it, in case that's relevant: we could have got there via the generic "/:controller/:action/:id" route.
<%= link_to "This page in CSV", {:format => :csv } %>
<%= link_to "This page in PDF", {:format => :pdf } %>
<%= link_to "This page in JPEG", {:format => :jpeg } %>
EDIT
Add helper
def current_url(new_params)
url_for :params => params.merge(new_params)
end
then use this
<%= link_to "This page in CSV", current_url(:format => :csv ) %>
EDIT 2
Or improve your hack:
def current_url(new_params)
params.merge!(new_params)
string = params.map{ |k,v| "#{k}=#{v}" }.join("&")
request.uri.split("?")[0] + "?" + string
end
EDIT
IMPORTANT! #floor - your approach above has a serious problem - it directly modifies params, so if you've got anything after a call to this method which uses params (such as will_paginate links for example) then that will get the modified version which you used to build your link. I changed it to call .dup on params and then modify the duplicated object rather than modifying params directly. – #Max Williams
You can use:
link_to "text of link", your_controller_path(format:'csv',params: request.query_parameters)
#floor's answer was great, I found it very useful.
Although the method can be improved by using the to_params method rather than contructing your own, like so:
def current_url(new_params)
params.merge!(new_params)
"#{request.uri}#{params.to_params}"
end
I have a flash_helper that I inconveniently downloaded from some web tutorial, which is now coming back to whack me on the head. On the good side, I'm sure the many talented coders here will find this easy. :)
# application_helper
def flash_helper
[:notice, :warning, :message].map { |f| content_tag(:div, flash[f], :class => f) if flash[f] }
end
This code, combined with <%= flash_helper %> in my views, is leading to the following html code generated:
["<div class=\"notice\">Your account has been reactivated.</div>", nil, nil]
...which renders as this rather unattractive string in the view itself:
["<div class=\"notice\">Your account has been reactivated.</div>", nil, nil]
How do I rewrite the code to sort this out?
[nil, nil, nil]
The above string is being sent to all of my views by the flash_helper code above when there is no flash. How can that code be rewritten to output nothing when there is no flash?
You need launch html_safe on all your String, on an array.
# application_helper
def flash_helper
[:notice, :warning, :message].map { |f|
content_tag(:div, flash[f].html_safe, :class => f) if flash[f]
}.compact
end
By default Rails 3 escapes HTML unless told otherwise. All you need to do is call .html_safe on the string being generated. Here is an overview:
HTML SAFE
I got this question in a previous interview and couldnt do it , any idea?
What does this return? Where would it be used?
module ApplicationHelper
def show_flash
flash.map{|key, value| content_tag(:div, value, {:class => key})}
end
end
The 'flash' is a ruby-on-rails convention for storing information generated in one request (say, "invalid username" or "session not found" or "thanks for buying from us" or "cart updated") temporarily for being rendered into the next view from the client.
The flash is a hash-like object.
The .map method on hash-like objects will iterate over all items in the hash; in this case, the .map method is being passed a block that accepts two parameters (which it names key and value, because the key could be used to look up the value from the hash). The block uses the content_tag helper to output new <div> elements with the value from the hash and the CSS selector-class key.
So for a flash like this: {:name => "sars", :food => "pizza"}
It would emit HTML roughly like this: <div class="name">sars</div><div class="food">pizza</div>.
This is a clever little helper method that probably saves a fair bit of typing, but it makes some assumptions: order in the view doesn't matter, all the keys are either in the CSS already or the CSS is prepared to handle unknown class elements in a graceful way. This helper might only be used once in a template, but it'd still be helpful to have as a method that could be dropped into other projects later.
module ApplicationHelper
def show_flash
flash.map{|key, value| content_tag(:div, value, {:class => key})}
end
end
I want to create a hidden field and create a link in one helper and then output both to my erb.
<%= my_cool_helper "something", form %>
Should out put the results of
link_to "something", a_path
form.hidden_field "something".tableize, :value => "something"
What would the definition of the helper look like? The details of what link_to and the form.hidden_field don't really matter. What matters is, how do I return the output from two different calls.
There are several ways to do this.
Remember that the existing rails helpers like link_to, etc, just output strings. You can concatenate the strings together and return that (which is what I do most of the time, if things are simple).
EG:
link_to( "something", something_path ) + #NOTE THE PLUS FOR STRING CONCAT
form.hidden_field('something'.tableize, :value=>'something')
If you're doing things which are more complicated, you could just put that code in a partial, and have your helper call render :partial.
If you're doing more complicated stuff than even that, then you may want to look at errtheblog's block_to_partial helper, which is pretty cool
So far the best I have come up with is:
def my_cool_helper(name, form)
out = capture { link_to name, a_path }
out << capture { form.hidden_field name.tableize, value => 'something' }
end
Is there a better way?
Using safe_join.
I typically prefer just concatenating with +, as shown in Orion Edwards's Answer, but here's another option I recently discovered.
safe_join( [
link_to( "something", something_path ),
form.hidden_field( "something".tableize, value: "something" )
] )
It has the advantage of explicitly listing all of the elements and the joining of those elements.
I find that with long elements, the + symbol can get lost at the end of the line. Additionally, if you're concatenating more than a few elements, I find listing them in an Array like this to be more obvious to the next reader.
If you want to buffer other output which apart from string then you can use concat instead of +.
see this - http://thepugautomatic.com/2013/06/helpers/
def output_siblings
div1 = tag.div 'some content'
div2 = tag.div 'other content'
div1 + div2
end
just simplifying some other answers in here.
This worked for me.
def format_paragraphs(text)
text.split(/\r?\n/).sum do |paragraph|
tag.p(paragraph)
end
end