Turn this haml snippet into a helper to DRY up Sinatra - ruby

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.)

Related

Ruby Conditional argument to method

I have some 'generic' methods that extract data based on css selectors that usually are the same in many websites. However I have another method that accept as argument the css selector for a given website.
I need to call the get_title method if title_selector argument is nos passed. How can I do that?
Scrape that accept css selectors as arguments
def scrape(urls, item_selector, title_selector, price_selector, image_selector)
collection = []
urls.each do |url|
doc = Nokogiri::HTML(open(url).read) # Opens URL
#items = doc.css(item_selector)[0..1].map {|item| item['href']} # Sets items
#items.each do |item| # Donwload each link and parse
page = Nokogiri::HTML(open(item).read)
collection << {
:title => page.css(title_selector).text, # I guess I need conditional here
:price => page.css(price_selector).text
}
end
#collection = collection
end
end
Generic title extractor
def get_title(doc)
if doc.at_css("meta[property='og:title']")
title = doc.css("meta[property='og:title']")
else doc.css('title')
title = doc.at_css('title').text
end
end
Use an or operator inside your page.css call. It will call get_title if title_selector is falsey (nil).
:title => page.css(title_selector || get_title(doc)).text,
I'm not sure what doc should actually be in this context, though.
EDIT
Given your comment below, I think you can just refactor get_title to handle all of the logic. Allow get_title to take an optional title_selector parameter and add this line to the top of your method:
return doc.css(title_selector).text if title_selector
Then, my original line becomes:
:title => get_title(page, title_selector)

Ruby method calls without defining variables

I am beyond confused on where the :find is coming from line 17, as well as :findcity... is that how you call a fucntion within a predefined method call from ruby???
cities = {'CA' => 'San Francisco',
'MI' => 'Detroit',
'FL' => 'Jacksonville'}
cities['NY'] = 'New York'
cities['OR'] = 'Portland'
def find_city(map, state)
if map.include? state
return map[state]
else
return "Not found."
end
end
# ok pay attention!
cities[:find] = method(:find_city)
while true
print "State? (ENTER to quit) "
state = gets.chomp
break if state.empty?
# this line is the most important ever! study!
puts cities[:find].call(cities, state)
end
For starters if you are a beginner in Ruby just don't bother trying to understand it. This is not the usual way of doing things in Ruby.
But here are some explanations:
:find is a Symbol and it could be :search or something else in this example.
You could actually use a different variable to store the method instead of storing inside the cities Hash. Like so:
# Instead of doing this
hash = {} # => {}
hash[:puts_method] = method(:puts)
hash[:puts_method].call("Foo")
# Foo
# You can just
puts_method = method(:puts)
puts_method.call("Foo")
# Foo
The find_city is the method defined in your code. Passing the symbol :find_city to the method method returns you an object representing that method (very meta uh?) of the class Method.
So like in the example above we can have an object representing the method puts with which we can send the method call to call it.
the_puts = method(:puts)
# => #<Method: Object(Kernel)#puts>
the_puts.call("Hey!")
# Hey!
# => nil
# Which is the same as simply doing
puts("Hey!")
# Hey!
# => nil

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.

How can I pass function parameters to an erb view with sinatra?

I currently have this:
get '/myapp/get/:func' do
erb :server, :locals => {:func => params[:func]}
end
And then in my server.erb file I have this:
if (func == "myFunc1")
myFunc1
elsif (func == "myFunc2")
myFunc2
etc...
The ruby functions called in server.erb are defined.
Now I want to define a new function, and I want to pass a variable to it. So what I want in my server.erb is this:
def myNewFunc(param1)
# do stuff with param1
end
How do I pass param1 to sinatra?
Note: The parameter I want to pass in is just an integer between 0 and 6.
You don't have to pass params as locals, you can se them anywhere in your code – if that is what you mean.

HAML - if / elsif construction

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

Resources