Insert HAML into a Sinatra helper - ruby

I'm writing a helper for a small Sinatra app that prints some gaming cards stored as hash in an array.
Every card has this structure:
{ card: 'Ace', suit: :spades, value: 11 }
and the filename of the card image is "spades_11.jpg".
I'm writing a helper to display the cards in my view:
def view(hand)
hand.each do |card|
#print the card
end
end
I need an output like this:
.span2
%img(src="/images/#{card[:suite]}_#{card[:value]}")
How can I insert my Haml code inside the helper block keeping the indentation?

The simplest solution would be to just return the HTML directly from your helper as a string:
def view(hand)
hand.map do |card|
"<div class='span2'><img src='/images/#{card[:suite]}_#{card[:value]}'></div>"
end.join
end
The call it from your Haml with something like:
= view(#the_hand)
You could make use of the haml_tag helper which would let you write something like:
def view(hand)
hand.each do |card|
haml_tag '.span2' do
haml_tag :img, 'src' => "/images/#{card[:suite]}_#{card[:value]}"
end
end
end
Note that haml_tag writes directly to the output rather than returning a string, so you would have to use it with - rather than =:
- view(#the_hand)
or use capture_haml.
This method means your helper depends on Haml. The first method would be usable whatever template language you used, but wouldn’t respect settings like format for whether to end the img tag with />.
If you want to use pure Haml for the markup for each card (this example is simple enough to get away with helpers, but you would certainly want to do this for more complex sections) you could use a partial. Add you Haml code to a file named e.g. view.haml, then you can render it from the containing template, passing in the hand as a local variable:
view.haml:
- hand.each do |card|
.span2
%img(src="/images/#{card[:suite]}_#{card[:value]}")
Parent template:
= haml :view, :locals => {:hand => #the_hand}

You should be able to use a here doc
def view(hand)
hand.each do |card|
<<-HAML
.span2
%img(src="/images/#{card[:suite]}_#{card[:value]}")
HAML
end
end
but note that here docs take the whitespace from the start of the line the are on, so unfortunately this will make your indentation somewhat ugly.
For anything more complicated it probably makes sense to write your haml in a separate .haml file.

Related

Naming different files. Handler? IO? Stream? Processor? Controller?

I'm having some trouble naming some files that I wrote. I don't really know the different between a stream, I/O, a handler, a processor (Is this a real concept?), and a controller. These are what my files look like in Ruby:
Starting from the rakefile:
desc "Calculate chocolate totals from a CSV of orders"
task :redeem_orders, [:orders_csv_path, :redemptions_csv_path] do |t, args|
args.with_defaults(:orders_csv_path => "./public/input/orders.csv", :redemptions_csv_path => "./public/output/redemptions.csv")
DataController.transfer(
input_path: args[:orders_csv_path],
output_path: args[:redemptions_csv_path],
formatter: ChocolateTotalsFormatter,
converter: ChocolateTotalsConverter
)
end
Then the controller (which in my mind delegates between different classes with the data obtained from the rakefile):
class DataController
def self.transfer(input_path:, output_path:, formatter:, converter:)
data_processor = DataProcessor.new(
input_path: input_path,
output_path: output_path,
formatter: formatter
)
export_data = converter.convert(data_processor.import)
data_processor.export(export_data)
end
end
The processor (which performs imports and exports according to the various files that were passed into this file):
class DataProcessor
attr_reader :input_path,
:output_path,
:formatter,
:input_file_processor,
:output_file_processor
def initialize(input_path:, output_path:, formatter:)
#input_path = input_path
#output_path = output_path
#formatter = formatter
#input_file_processor = FileProcessorFactory.create(File.extname(input_path))
#output_file_processor = FileProcessorFactory.create(File.extname(output_path))
end
def import
formatter.format_input(input_file_processor.read(input_path: input_path))
end
def export(export_data)
output_file_processor.write(
output_path: output_path,
data: formatter.format_output(export_data)
)
end
end
the converter referenced in the controller looks like this (it converts data that was passed in to a different format... I'm more confident about this naming):
class ChocolateTotalsConverter
def self.convert(data)
data.map do |row|
ChocolateTotalsCalculator.new(row).calculate
end
end
end
And the FileProcessorFactory in the above code snippet creates a file like this one that actually does the reading and the writing to CSV:
require 'csv'
class CSVProcessor
include FileTypeProcessor
def self.read(input_path:, with_headers: true, return_headers: false)
CSV.read(input_path, headers: with_headers, return_headers: return_headers, converters: :numeric)
end
def self.write(output_path:, data:, write_headers: false)
CSV.open(output_path, "w", write_headers: write_headers) do |csv|
data.each do |row|
csv << row
end
end
end
end
I'm having trouble with naming. Does it looks like I named things correctly? What should be named something like DataIO vs DataProcessor? What should a file named DataStream be doing? What about something that's a converter?
Ruby isn't a kingdom of nouns. Some programmers hear "everything is an object" and think "I am processing data, therefore I need a DataProcessor object!" But in Ruby, "everything is an object". There's only one novel "thing" in your example: a chocolate order (maybe redemptions, too). So you only need one custom class: ChocolateOrder. The other "things" we already have objects for: CSV represents the CSV file, Array (or Set or Hash) can represent the collection of chocolate orders.
Processing a CSV row into an order, converting an order into workable data, and totaling those data into a result aren't "things". They're actions! In Ruby, actions are methods, blocks, procs, lambdas, or top-level functions*. In your case I see a method like ChocolateOrder#payment for getting just the price to add up, then maybe some blocks for the rest of the processing.
In pseudocode I imagine something like this:
# input
orders = CSV.foreach(input_file).map do |row|
# get important stuff out of the row
Order.new(x, y, z)
end
# processing
redemptions = orders.map { |order| order.get_redemption }
# output
CSV.open(output_file, "wb") do |csv|
redemptions.each do |redemption|
# convert redemption to an array of strings
csv << redemption_ary
end
end
If your rows are really simple, I would even consider just setting headers:true on the CSV so it returns Hash and leave orders as that.
* Procs, lambdas, and top-level functions are objects too. But that's beside the point.
This seems like quite a 'java' way of thinking - in Ruby I haven't seen patterns like this used very often. I'd say that you might only really need the DataProcessor class. CSVProcessor and ChocolateTotalsConverter have only class methods, which might be more idiomatic if they were instance methods of DataProcessor instead. I'd start there and see how you feel about it.

How to output data from Yaml variables (written in markdown) into an HTML Haml file?

In a Yaml data file aaa.yml I have:
yyy: |
[foo](http://example.com) bar.
I want to pull this data from a Haml file (zzz.html.haml).
1. If I do zzz.html.haml:
%p
= data.aaa.yyy
Middleman renders zzz.html:
<p> [foo](http://example.com) bar</p>
2. If I do:
%p
:markdown
= data.aaa.yyy
Middleman renders:
<p>= data.aaa.yyy</p>
How can I make it render:
<p>foo bar</p>
?
You have a string that contains markdown, and you want to render that markdown and include the result in your output.
One way would be to use the Haml :markdown filter. You can’t use normal Haml syntax inside a filter (like you’ve tried), but you can use interpolation inside #{...}, so you could do
:markdown
#{data.aaa.yyy}
A better way, if the string is a standalone chunk, might be to create a helper method that renders markdown, and call that.
Wherever you have your helpers add something like:
def markdown s
# I'm using kramdown here, but you can use whatever processor you prefer.
Kramdown::Document.new(s).to_html
end
Then in your Haml:
= markdown(data.aaa.yyy)
Yay! Turns out there's this helper, which was already in my config.rb but didn't work by itself:
def markdown(text)
Tilt['markdown'].new { text }.render
end
When I installed this helper (from #matt's answer) with the first one:
def markdown s
Kramdown::Document.new(s).to_html
end
Then = markdown(foo.bar) worked, which made my day!! :D

How to handle NILs with Anemone / Nokogiri web scraper?

def scrape!(url)
Anemone.crawl(url) do |anemone|
anemone.on_pages_like %[/events/detail/.*] do |page|
show = {
headliner: page.doc.at_css('h1.summary').text,
openers: page.doc.at_css('.details h2').text
}
puts show
end
end
end
Writing a scraper in Anemone, which uses Nokogiri under the hood..
Sometime the selector .details h2'returns nothing because its not in the HTML, and calling text on it throws an exception.
I'd like to avoid if/elses all over the place...
if page.doc.at_css('.details h2').empty?
openers: page.doc.at_css('.details h2').text
end
Is there any more eloquent way of handling errors produced by inconsistant mark up? For instance CoffeeScript has the existentional operator person.name?.first(). If the HTML has the element, great make the object and call text on it. If not, move on and dont add it to the hash.
You just need do:
anemone.on_pages_like %[/events/detail/.*] do |page|
if not page.nil?
...#your code
end
end

Ruby, better way to implement conditional iteration than this?

I have an array #cities = ["Vienna", "Barcelona", "Paris"];
and I am trying to display the individual items with a spacer in between. However it is possible that there is only 1 element in the array, in which case I do not want to display the spacer. And also the array could be empty, in which case I want to display nothing.
For the above array I want the following output:
Vienna
-----
Barcelona
-----
Paris
I use an erb template cityview to apply formatting, css, etc before actually printing the city names. Simplified, it looks like this:
<p><%= #cities[#city_id] %></p>
I have implemented it as follows...
unless #array.empty?
#city_id = 0;
erb :cityview
end
unless #array[1..-1].nil?
#array[1..-1].each_index do |i|
#city_id = i+1;
puts "<p>-------</p>";
erb :cityview
end
end
Is there a better way?
#cities.join("<p>--------</p>")
Edit to address the template
Here I'm assuming that there's an erbs method that returns the rendered template without doing a puts. Returning the string allows easier manipulation and reuse.
#cities.map { |c| #city = c; erb :cityview }.join("<p>--------</p>")
I'd prefer:
erb:
<p><%= #city %></p>
and loop
#array.each_with_index do |e, i|
#city = e
erb :cityview
puts "<p>-------</p>" if i < #array.length - 1
end
I assume you have split the erb, bit because you want to customize it.
If you want to mix HTML with your city names then you'll need to worry about HTML encoding things before you mix in your HTML. Using just the standard library:
require 'cgi'
html = #cities.map { |c| CGI.escapeHTML(c) }.join('<p>-----</p>')
If you're in Rails, then you can use html_escape from ERB::Util and mark the result as safe-for-HTML with html_safe to avoid having to worry about the encoding in your view:
include ERB::Util
html = #cities.map { |c| html_escape(c) }.join('<p>-----</p>').html_safe
The simpler solution would be to use a spacer template.
http://guides.rubyonrails.org/layouts_and_rendering.html#spacer-templates

Rails 3 and html_safe confusion (allow pictures (smiles) in chat but deny everything else)

I have here is a module that replaces the smilies (like ":-)") as icons:
module Smileize
PATH = "/images/smiles"
SMILES = [/\;\-?p/i, /\$\-?\)/, /8\-?\)/, /\>\:\-?\(/, /\:\-?\*/, /\:\-?o/i, /\:\-?c/i, /\;\-?\)/,
/\:\-?s/i, /\:\-?\|/, /\:\-?p/i, /\:\-?D/i, /\:\-?\?/, /\:\-?\(/, /\:\-?\)/]
def to_icon(key)
return "<img class='smiley' src='#{PATH}/smile#{SMILES.index(key) + 1}.png'/>"
end
module_function :to_icon
end
class String
def to_smile
Smileize::SMILES.each do |smile|
if self =~ smile
self.gsub!(smile, Smileize.to_icon(smile))
end
end
self
end
end
So pictures show that I'm using html_safe, like this:
<%= #message.text.to_smile.html_safe %>
But it does not suit me, because but pictures will be displayed and other tags, too.
My question is: how to display only my smile, ignoring the other tags?
I think you'll need to do it like this:
HTML encode the string.
Perform your substitution.
Mark the final result as HTML safe.
Add a helper something like this:
def expand_smilies(s)
s = ERB::Util::html_escape(s)
Smileize::SMILES.each do |smile|
s.gsub!(smile, Smileize.to_icon(smile))
end
s.html_safe
end
And then in your ERB:
<%= expand_smilies some_text %>
ERB uses ERB::Util::html_escape to encode HTML so using it yourself makes sense if you're targeting ERB. Calling html_safe on a string returns you something that ERB will leave alone when it is HTML encoding things.
Note that there is no usable html_safe! on strings and html_safe returns an ActiveSupport::SafeBuffer rather than a String so you'll have to use a helper rather than monkey patching a new method into String. ActiveSupport does patch an html_safe! method into String but all it does is raise an exception saying "don't do that":
def html_safe!
raise "You can't call html_safe! on a String"
end

Resources