HAML - if / elsif construction - ruby

I need this construction in my HAML code:
- if something1
%div.a
- elsif something2
%div.b
- elsif something3
%div.c
- else
%div.d
%div another content
I would expected I get something like:
<div class="a|b|c|d">
<div>another content</div>
</div>
But in the fact I get
<div class="a|b|c|d"></div>
<div>another content</div>
How I must to update my code, if I need to get:
another content
?

I think you should create a helper method instead:
%div{:class => helper_method(useful_parameters)}
The really ugly way to accomplish this is with ternary operators (condition ? true_case : false_case) which doesn't sound like a good solution given from the fact that you selected haml and want to have your code base clean.

#Ingenu's helper method looks like the smarter approach, but if you don't mind it quicker and dirtier, you could do:
- if something1
-divclass = 'a'
- elsif something2
-divclass = 'b'
- elsif something3
-divclass = 'c'
- else
-divclass = 'd'
%div{:class => divclass}
%div another content

You could extend your if condition with this module and then use smalltalk-style conditions
module IfTrue
def ifTrue &block
yield if self
slf=self
o = Object.new
def o.elseIf val, &block
yield if !slf && val
end
end
end
now you could code stuff like this:
condition.extend(IfTrue).ifTrue{
do_stuff
}elseIf(condition2){
doOtherStuff
}
or, if you are a naughty monkey patcher ;-):
Object.include IfTrue
condition.ifTrue{
do_stuff
}elseIf(condition2){
doOtherStuff
}
if you want to chain more than one elseif you will have to adapt this code by somehow factoring the elsif definition

Related

Refactor ruby code to split array into two small arrays

#open = Array.new
#close = Array.new
#posts.each do |post|
if !post.status.nil? and post.status == 'Open'
#open.push(post)
else
#close.push(post)
end
end
Can i write it in less verbose way ?
Sounds like a job for partition:
partition { |obj| block } → [ true_array, false_array ]
partition → an_enumerator
Returns two arrays, the first containing the elements of enum for which the block evaluates to true, the second containing the rest.
This should do the job:
#open, #closed = #posts.partition { |p| p.status == 'Open' }
Another idea:
#open = #post.select{ |post| post.status == 'Open'}
#close = #post.reject{ |post| post.status == 'Open'}
You dont have to check nil explicity. Something like this will do.
#posts.each { |post| post.status == 'Open' ? #open.push post : #close.push }
Just to help you write code that's more Ruby-like, here's how I'd write your original code:
open_ary = []
close_ary = []
#posts.each do |post|
if post.status && post.status == 'Open'
open_ary << post
else
close_ary << post
end
end
It could be written more concisely, and would remove a couple lines, but it'd lose readability, which is always something to consider. That said, #muistooshort's answer is really the best way to do it because it relies on the partition method which was created for this sort of use-case.

Insert HAML into a Sinatra helper

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.

Turn this haml snippet into a helper to DRY up Sinatra

This bit of haml works:
%select{ :name => 'color', :value => #p.color}
- Person.color.options[:flags].each do |colors|
- if #p.color == colors
%option{:value => colors, :selected => "selected"}= colors
- else
%option{:value => colors}= colors
I'm trying to create a helper out of it so I can reuse it:
helpers do
def options(prop, p)
Person.prop.options[:flags].each do |x|
if p.prop == x
"%option{:value => #{x}, :selected => 'selected'}= #{x}"
else
"%option{:value => #{x}}= #{x}"
end
end
end
end
And then calling it with:
%select{ :name => 'color', :value => #p.color}
- options(color, #p)
But I'm getting this error: undefined local variable or method 'color'
Am I far off?
EDIT 2:
Something funky is going on with the loop.
Even a simple example such as this:
helpers do
def options(prop, p)
Person.send(prop).options[:flags].each do |x|
"<p>test</p>"
end
end
end
and = options(:color, #p)
prints an array of the options ( in my case [:red, :blue, :yellow]) and does not insert any html. However, if I do puts <p>test</p> it does go through the loop three times and prints them correctly—they just don't show up in the html.
Use a symbol instead of undefined method:
options(:color, #p)
instead of:
options(color, #p)
and on helper method use send:
if p.send(prop) == x
instead of
if p.prop == x
as well as:
Person.send(prop)
instead of:
Person.prop
Also, i'm in doubt HAML will accept a string like "%option{:value => #{x}" as a tag.
You can use HTML instead or just find another way to DRY, like render a partial or use haml_tag:
def options(prop, p)
Person.send(prop).options[:flags].each do |x|
haml_tag :option, "#{x}", :value=>x
end
end
If you use haml_tag call it with - options(:color, #p)
There are a couple of things going on here. First, the message undefined local variable or method 'color' is caused by the line
- options(color, #p)
in your Haml. Here color is the undefined local variable. If I understand correctly you have a Person class that has various properties, each of which has several possible options and you want to be able to choose which one to use without hardcoding it and needing several helpers method. One way to do that would be to change the line
Person.prop.options[:flags].each do |x|
in your helper to
Person.send(prop).options[:flags].each do |x|
and then pass a symbol specifying the property to use when you call the helper:
- options(:color, #p)
The next problem is writing the generated code to the output. Your helper can either return a string which you can include with =, or it can write directly to the output using helpers like haml_tag and haml_concat. Note that you shouldn’t use the return value of haml_tag or haml_concat, it will generate an error.
So here you can either create the HTML in the helper:
if p.prop == x
"<option value='#{x}' selected='selected'>#{x}</option>"
else
"<option value='#{x}' />#{x}</option>"
end
and then use it with = (if you use - the output will get ignored):
= options(:color, #p)
If you do this you need to make sure the helper returns the string you want to embed in your Haml. In this case the helper returns the value the call to each, which is the array itself. You need to use something like map and join to create the desired string:
def options(prop, p)
Person.send(prop).options[:flags].map do |x|
if p.prop == x
"<option value='#{x}' selected='selected'>#{x}</option>"
else
"<option value='#{x}' />#{x}</option>"
end
end.join("\n")
end
The alternative is to use haml_tag to write the output directly.
if p.prop == x
haml_tag :option, x, :value=>x, :selected => true
else
haml_tag :option, x, :value =>x
end
Note that in Haml, any entry in an attribute hash that has a boolean value will only be output if the value is true, and it will be formatted correctly depending on the output, e.g. selected='selected' got XHTML and just selected for HTML. So this last example can be simplified to
haml_tag :option, x, :value=>x, :selected => (p.prop == x)
(You could always simplify the other example (returning a string) in a similar manner, with something like #{"selected='selected'" if p.prop == x}, but Haml has it built in.)

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

How to build nested menu "trees" in HAML

I am trying to build a simple nested html menu using HAML and am not sure how to go about inserting the elements with the correct indentation, or the general best way to build nested trees. I would like to be able to do something like this, but infinitely deep:
- categories.each_key do |category|
%li.cat-item{:id => "category-#{category}"}
%a{:href => "/category/#{category}", :title => "#{category.titleize}"}
= category.titleize
It feels like I should be able to accomplish this pretty easily without resorting to writing the tags by hand in html, but I'm not the best with recursion. Here is the code I've currently come up with:
View Helper
def menu_tag_builder(array, &block)
return "" if array.nil?
result = "<ul>\n"
array.each do |node|
result += "<li"
attributes = {}
if block_given?
text = yield(attributes, node)
else
text = node["title"]
end
attributes.each { |k,v| result += " #{k.to_s}='#{v.to_s}'"}
result += ">\n"
result += text
result += menu_tag_builder(node["children"], &block)
result += "</li>\n"
end
result += "</ul>"
result
end
def menu_tag(array, &block)
haml_concat(menu_tag_builder(array, &block))
end
View
# index.haml, where config(:menu) converts the yaml below
# to an array of objects, where object[:children] is a nested array
- menu_tag(config(:menu)) do |attributes, node|
- attributes[:class] = "one two"
- node["title"]
Sample YAML defining Menu
menu:
-
title: "Home"
path: "/home"
-
title: "About Us"
path: "/about"
children:
-
title: "Our Story"
path: "/about/our-story"
Any ideas how to do that so the output is like this:
<ul>
<li class='one two'>
Home
</li>
<li class='one two'>
About Us
</li>
</ul>
...not like this:
<ul>
<li class='one two'>
Home</li>
<li class='one two'>
About Us</li>
</ul>
... and so it's properly indented globally.
Thanks for the help,
Lance
The trick to nicely-indented, Ruby-generated Haml code is the haml_tag helper. Here's how I'd convert your menu_tag method to using haml_tag:
def menu_tag(array, &block)
return unless array
haml_tag :ul do
array.each do |node|
attributes = {}
if block_given?
text = yield(attributes, node)
else
text = node["title"]
end
haml_tag :li, text, attributes
menu_tag_builder(node["children"], &block)
end
end
end
How about something along the lines of:
def nested_list(list)
return unless list
haml_tag :ul do
list.each do |item|
haml_tag :li do
haml_concat link_to item["title"], item["path"]
if item["children"]
nested_list item["children"]
end
end
end
end
end
Awesome, #shingara's hint put me in the right direction :). This works perfectly:
def menu_tag(array, &block)
return "" if array.nil?
haml_tag :ui do
array.each do |node|
attributes = {}
if block_given?
text = yield(attributes, node)
else
text = node[:title]
end
haml_tag :li, attributes do
haml_concat text
menu_tag_builder(node[:children], &block)
end
end
end
end
If somebody can make that even shorter, or make it more easy to customize the attributes on the nested nodes, I'll mark that as correct instead of this.
Cheers.
It's because you send a pur HTML by your helper. The indentation become with HAML. You can can generate some HAML in your helper.

Resources