Problem with escaping HTML characters in Rails 3 - ruby-on-rails

I use foo helper function in my view:
<%= foo ["hello", "stack", "overflow"] %>
When foo is defined like this:
def foo(arr)
result = ''
arr.each do |a|
result += content_tag(:div, a)
end
result
end
The page renders:
<div>hello</div><div>stack</div><div>overflow</div>
But, if change foo's definition to be:
def foo(arr)
content_tag(:div, arr[0]) + content_tag(:div, arr[1]) + content_tag(:div, arr[2])
end
I get the expected result:
hello
stack
overflow
How would you fix foo's definition above to get the expected result ? (i.e. I don't want the characters to be escaped)

Try this:
def foo(arr)
result = ''
arr.each do |a|
result += content_tag(:div, a)
end
raw result
end
Edit.
To be clearer, you're creating a string and Rails doesn't know whether or not it's safe to display.
To be even more precise, Rails has no doubt concerning the content_tags it creates.
So you could solve your problem telling rails your initializer string is safe:
def foo(arr)
result = ''.html_safe
arr.each do |a|
result += content_tag(:div, a)
end
result
end

Related

Rails views output is different than Puma web server logs

I have a ruby script called abc.rb in rails config/initializers
require 'http'
class Abc
def initialize(url)
#url = url
#doc = web_lookup(#url)
end
def web_lookup(url_to_open)
begin
return Nokogiri::HTML(HTTP.get(url_to_open).to_s)
rescue
"Please check your URL!"
end
end
def frequency_count
#word_array = #doc.css("p").text.split(" ")
#occurance = Hash.new(0)
#word_array.each {|x| #occurance[x.downcase] += 1 }
#occurance.each {|x,y|
if y > 5
puts "#{x} : #{y} times"
end
}
end
end
And I'm trying to access that script's class in a rails controller.
class UrlsController < ApplicationController
def index
#url_to_check = Abc.new("http://ecodehut.com/linux")
end
end
Everything is fine so far but when I call this variable in index.html.erb <%= #url_to_check.frequency_count %> output is this:
{"it’s"=>1, "like"=>1, "asking"=>1, "“should"=>1, "i"=>5, "go"=>3, "to"=>14, "school?”"=>1, "and"=>8, "“would"=>1, "get"=>1, "a"=>3, "job"=>1, "if"=>2, "school?”.list"=>1, "of"=>1, "advantages"=>1, "is"=>5, "never"=>1, "ending"=>1, "elaborating"=>1, "each"=>1, "throw"=>1, "your"=>2, "mouse"=>1, "away"=>1, "this"=>3, "time."=>1, "keyboard"=>1, "all"=>1, "need"=>1, "mozart."=>1, "navigate..."=>1, "before"=>2, "we"=>1, "begin"=>1, "with"=>3, "configuration"=>1, "here"=>1, "download"=>1, "install"=>2, "system."=>1, "after"=>1, "come"=>1, "back"=>1, "continue."=>1}
But my expected output is this:
to : 14 times
and : 8 times
you : 9 times
the : 6 times
I'm not getting why rails printing everything inside the hash instead of the key values with values bigger than 5 like mentioned in the frequency_count method.
P.S: Abc.rb script results fine and dandy when ran in terminal using ruby Abc.rb
Plese change this and try again
def frequency_count
#word_array = #doc.css("p").text.split(" ")
#occurance = Hash.new(0)
#word_array.each {|x| #occurance[x.downcase] += 1 }
#occurance.select {|x, y| y> 5}
end
The reason is the last line of your method is
#occurance.each {|x,y|
if y > 5
puts "#{x} : #{y} times"
end
}
Just iterate all key and values inside #occurance variable but not sort based on the count. So you are getting all key values in the views instead which has count more than 5.
When you do #occurance.select {|x, y| y> 5}, which filter your hash and gives you the desired values which count is more than 5. Now you can just loop it and print inside views.
Hope you understand.
Update
In your index.html.erb put this code, where you would like to print hash details
<% #occurance.each do |key, value| %>
<span><%= "#{key}: #{value}" %></span><br />
<% end %>

yield to an anonymous block two functions up

there is probably a simple way to do this.
I'm trying to refactor something like the following
def foo(baz)
baz.update_first
if baz.has_condition?
yield baz.val if block_given?
baz.a
else
baz.b
end
end
called like
foo(baz) {|b| b.modify}
to something like
def foo(baz)
baz.update_first
bar(baz) {|i| yield i if block_given? }
end
def bar(baz)
if baz.has_condition?
yield baz.val if block_given?
baz.a
else
baz.b
end
end
Will that work? How?
I think it will, but I'd appreciate a clear explanation of how yielding inside a block works... reading through proc.c and vm.c and a relevant git commit in the ruby source code , I think when bar is called in foo it executes until it yields, and then you walk up the frame stack to the local environment pointer for block defined in foo, which is called, where the yield walks up to the block foo is called with, executes it, and then you are back in bar. Is that correct? Is there a better way to do this?
This feels a little weird to me, like inverting control, and it requires foo to know about baz more then I'd like, but I unfortunately can't simply pass a proc or lambda in this code.
I think maybe the concept of yield will be more clear if you look at an alternative syntax, which is converting the bloc to a proc argument.
For example, the following examples are the same
def my_each(arr)
arr.each { |x| yield x }
end
def my_each(arr, &blk)
arr.each { |x| blk.call(x) }
end
# Both are called the same way
my_each([1,2,3]) { |x| print x }
# => 123
When using yield, the variable is available in the method without declaring it in the parameters list. Prepending an & sign to a parameter converts it to a proc, so in the method it can be run with .call.
Here's an example of providing a block to one method then executing it two scopes in:
def method_a(number, &blk)
method_b do
method_c do
blk.call(number)
end
end
end
def method_b(&blk)
blk.call
end
def method_c(&blk)
blk.call
end
method_a(1) { |num| puts num + 1 }
# => 2
Note that blk is not a magic word - you can name the variable whatever you want.
Here's the same thing with yield:
def method_a(number)
method_b do
method_c do
yield number
end
end
end
def method_b
yield
end
def method_c
yield
end
method_a(1) { |num| puts num + 1 }
# => 2
I think using the &blk syntax is clearer because it assigns a variable to the proc. Just because a proc is used in the method doesn't mean you have to ever run Proc.new. The block is automatically converted to a proc.

Rails: How to initialize an object with the attributes in strings?

Probably been working on this too long, sloppy design, or both. My issue is I have a model I wish to initialize. The object has like 52 attributes, but I'm only setting a certain ~25 depending on which object I've just scanned. When I scan an object I get the columns and match them up with a hash_map I've created.
Example Hash Map
This just matches the scanned text to their respective attribute name.
hash_map = {"Pizza."=>"pizza_pie","PastaBowl"=>"pasta_bowl","tacos"=>"hard_shell_taco","IceCream"=>"ice_cream","PopTarts"=>"pop_tart"}
What I want to do
menu = RestaurantMenu.new(pizza_pie => var1, pasta_bowl => var2, ...)
My only problem is in my code at the moment I have this...
t.rows.each do |r|
for i in 0..r.length-1
#hash_map[t.combined_columns[i]] => r.[i]
puts "#{hash_map["#{t.combined_columns[i]}"]} => #{r[i]}"
end
end
the puts line displays what I want, but unsure how to get that in my app properly.
Here is several ways to fix this:
hash_map = {"Pizza."=>"pizza_pie","PastaBowl"=>"pasta_bowl","tacos"=>"hard_shell_taco","IceCream"=>"ice_cream","PopTarts"=>"pop_tart"}
attributes.each do |attribute, element|
message.send((attribute + '=').to_sym, hash_map[element])
end
or like this:
class Example
attr_reader :Pizza, :PastaBowl #...
def initialize args
args.each do |k, v|
instance_variable_set("##{k}", v) unless v.nil?
end
end
end
for more details click here
I ended up doing the following method:
attributes = Hash[]
attributes["restaurant"] = tmp_basic_info.name
attributes["menu_item"] = tmp_basic_info.item_name
t.rows.each do |r|
for i in 0..r.length-1
attributes["other"] = t.other_information
attributes[hash_map[t.combined_columns[i]] = r[i]
end
row = ImportMenuItem.new(attributes)
row.save
end

How to make this code more Ruby-way?

I am new to Ruby and Rails (switched from Python and Python frameworks). I'm writing a simple dashboard website which displays information about the S.M.A.R.T. state of hard disks. Here I wrote a helper to display a badge in a table cell near the relevant S.M.A.R.T attribute if it's value meets a condition. At first, the helper code was as simple as in Listing 1, but then I decided to draw a summary of all badges for the specific drive, in addition to the badges near individual S.M.A.R.T. attributes in the table. So at first I added a simple method like:
def smart_chk_device(attrs)
attrs.each { |item| smart_chk_attr(item) }
end
But this approach didn't work and it caused the entire array of attributes to be output to the resulting page. It only started to work when I made it as in Listing 2, but I believe there's something wrong there, and the same thing can be done in a more simple way. Please show me the right, Ruby-way of doing it.
Listing 1:
module HomeHelper
def smart_chk_attr(attr)
case attr[:id].to_i
when 1,197
content_tag(:span, "Warning", :class => "label label-warning") if attr[:raw].to_i > 0
when 5,7,10,196,198
content_tag(:span, "Critical", :class => "label label-important") if attr[:raw].to_i > 0
end
end
end
Listing 2 (works, but I don't like it):
module HomeHelper
def smart_chk_attr(attr)
case attr[:id].to_i
when 1,197
return content_tag(:span, "Warning", :class => "label label-warning") if attr[:raw].to_i > 0
when 5,7,10,196,198
return content_tag(:span, "Critical", :class => "label label-important") if attr[:raw].to_i > 0
else
return String.new
end
return String.new
end
def smart_chk_device(attrs)
output = ""
attrs.each { |item| output << smart_chk_attr(item) }
return output.html_safe
end
end
attrs is an Array of Hashes, where each Hash contains keys :id and :raw with the numeric code of a S.M.A.R.T attribute and its RAW value, both in Strings.
Also, RoR complaints if to remove the last "return String.new" in Listing 2. Why is it so? Doesn't the "case" block all the possible cases, so that control should never reach the end of the function?
I believe this would behave in the same way, and is much shorter:
module HomeHelper
def smart_chk_attr(attr)
return '' unless attr[:raw].to_i > 0
case attr[:id].to_i
when 1,197
content_tag(:span, "Warning", :class => "label label-warning")
when 5,7,10,196,198
content_tag(:span, "Critical", :class => "label label-important")
else ''
end
end
def smart_chk_device(attrs)
attrs.map { |item| smart_chk_attr(item) }.join.html_safe
end
end
Ruby methods return the value of the last expression, so get rid of the explicit returns all over that method. Also, DRY: Don't Repeat Yourself (the attr[:raw] check). In this case, I replaced those with a guard clause at the start of the method. Short-circuiting guard clauses are a matter of taste, but I like them and you'll see them in a lot of Ruby code.
Your method smart_chk_attr(attr) has an extra return at the end, it will never get run.
The each is an enumerator, when it finishes going through each item that you supplied it returns the the original object passed into it, not the modified stuff inside.
If you use collect you will get an array with your modified objects.
If you are wanting a string output you can use join to put them into a string. Join will also take an option for how to join your items.
def smart_chk_device(attrs)
attrs.collect{ |item| smart_chk_attr(item) }.join.html_safe
end

HTML escaped in Rails 3

I have a method call in my view like this
<%= Navigation.with(params) do |menu|
if current_user && current_user.can_verify?
menu.item("Listings", manage_listings_path())
menu.item("Listing changes", needing_change_approval_manage_listings_path())
menu.item("Flagged Items", flagged_manage_listings_path())
menu.item("Transfers", manage_listing_transfers_path())
menu.item("Reviews", manage_listing_reviews_path())
end
if current_user && current_user.admin?
menu.item("Log", manage_verifications_path())
menu.item("Indexer Compensations", manage_compensations_path())
menu.item("Users", manage_users_path())
end
end%>
that splits out the below string
"<li>Listings</li> <li>Listing changes</li> <li>Flagged Items</li> <li>Transfers</li> <li>Reviews</li> <li>Log</li> <li>Indexer Compensations</li> <li>Users</li>"
I just get this string in my page. I wanted them to be menus nicely styled by CSS. I am just getting the above raw text in my page. How do I convert this string to be treated as HTML by the browser.
Please help
Here is the navigation class
class NavigationMenu < ActionView::Base
def initialize(params)
#params = params
end
def item(title, path, options={})
#items ||= Array.new
unless (route = Rails.application.routes.recognize_path(path,:method => options[:method]|| :get))
raise "Unrecognised path #{path}, are you sure it's in routes.rb?"
end
#items << content_tag(:li, link_to(title,path, :class => (#params[:controller] == route[:controller] && #params[:action] == route[:action])? 'active' : nil))
end
def output
return '' if #items.blank?
content_tag(:ul, #items.join("\n"), :id => 'navigation')
end
end
class Navigation
def self.with(params, &block)
menu = NavigationMenu.new(params)
yield menu
menu.output
end
end
You have to add a call to the raw method:
<%= raw ... %>
This is necessary, because in Rails 3 every string is escaped by default, unless you use the raw method.
It's like an inverse of the h method in Rails 2, where every string is unescaped by default, unless you use the h method.
Example:
This code in Rails 2...
<%= h "String which must be escaped" %>
<%= "String which must be output raw %>
... must be this in Rails 3:
<%= "String which must be escaped" %>
<%= raw "String which must be output raw %>
(Although an additional call to h doesn't do any harm in Rails 3)
You need to append .html_safe to the string - this will stop rails from escaping it when it's time to output text. Probably best to put it in the item method that you call repeatedly.
I recently wrote an article regarding XSS protection in Rails 3 when upgrading from Rails 2:
http://developer.uservoice.com/entries/upgrading-to-rails-3-printing-escaped-strings
The idea is to hook code to printing HTML so that we can determine when we are actually printing something we don't want to:
module ActionView
module Helpers
module TextHelper
def simple_format_with_double_escape_reporting(*args)
HtmlDoubleEscapeReporter.assert_sane(simple_format_without_double_escape_reporting(*args))
end
alias_method_chain :simple_format, :double_escape_reporting
end
module TagHelper
private
def content_tag_string_with_double_escape_reporting(*args)
HtmlDoubleEscapeReporter.assert_sane(content_tag_string_without_double_escape_reporting(*args))
end
alias_method_chain :content_tag_string, :double_escape_reporting
end
module UrlHelper
def link_to_with_double_escape_reporting(*args, &block)
HtmlDoubleEscapeReporter.assert_sane(link_to_without_double_escape_reporting(*args, &block))
end
alias_method_chain :link_to, :double_escape_reporting
end
end
end
Method HtmlDoubleEscapeReporter.assert_sane can be written, for example, like this:
class HtmlDoubleEscapeReporter
def self.assert_sane(str)
if (str.match(/<[a-z]/) || str.match(/&(quot|rarr|larr|amp|#)/)) &&
!str.match(/looks something you do not want to print/
send_problem_report('#{str}' looks something you do not want to print")
end
return str
end
end
Here, 'looks something you do not want to print' is used to prevent the possibility of infinite loops. The line send_problem_report('#{str}' looks something you do not want to print") can be replaced with a call to "debugger" (from ruby-debug gem) so that you are able to check the backtrace and see where the problem is coming from.
Here is the new class. At last... I got that bug.
class NavigationMenu < ActionView::Base
def initialize(params)
#params = params
end
def item(title, path, options={})
#items ||= Array.new
unless (route = Rails.application.routes.recognize_path(path,:method => options[:method]|| :get))
raise "Unrecognised path #{path}, are you sure it's in routes.rb?"
end
#items << content_tag(:li, link_to(title,path, :class => (#params[:controller] == route[:controller] && #params[:action] == route[:action])? 'active' : nil))
end
def output
#items = #items.join("\n").html_safe
return '' if #items.blank?
content_tag(:ul, #items, :id => 'navigation')
end
end
class Navigation
def self.with(params, &block)
menu = NavigationMenu.new(params)
yield menu
menu.output
end
end

Resources