Ruby Hash Fetch Issue with Sass - ruby

I'm at a loss. Hoping somebody with more Ruby experience will tell me what is going on.
I'm calling a custom Sass function from my stylesheet like so:
$color: user_var('color')
The custom function looks like this:
module Sass::Script::Functions
def user_var(param_name)
puts options[:custom]
puts options[:custom].fetch('color')
puts options[:custom].fetch(param_name)
end
end
The result of puts options[:custom] (as expected) is this:
{"color"=>#eeeeee, "header"=>20px}
The result of puts options[:custom].fetch('color') is #eeeeee
BUT... the result of puts options[:custom].fetch(param_name) results in a "key not found: 'color' (KeyError)".
Completely baffled here.

my guess is that param_name is a symbol and the color key in the options hash is defined as a string.
this should work.
options[:custom].fetch("#{param_name}")

Related

Can you pass a block of code that returns an error to a method?

I often find myself dealing with these kind of scenarios:
require 'nokogiri'
require "open-uri"
url = "https://www.random_website.com/contains_info_I_want_to_parse"
nokodoc = Nokogiri::HTML(open(url))
# Let's say one of the following line breaks the ruby script
# because the element I'm searching doesn't contain an attribute.
a = nokodoc.search('#element-1').attribute('href').text
b = nokodoc.search('#element-2').attribute('href').text.gsub("a", "A")
c = nokodoc.search('#element-3 h1').attribute('style').text.strip
What happens is that I'll be creating about 30 variables all searching for different elements in a page, and I'll be looping that code over multiple pages. However, a few of these pages may have an ever-so-slightly different layout and won't have one of those div. This will break my code (because you can't call .attribute or .gsub on nil for example). But I can never guess which line before-hand.
My go-to solution is usually surround each line with:
begin
line #n
rescue
puts "line #n caused an error"
end
I'd like to be able to do something like:
url = "https://www.random_website.com/contains_info_I_want_to_parse"
nokodoc = Nokogiri::HTML(open(url))
catch_error(a, nokodoc.search('#element-1').attribute('href').text)
catch_error(b, nokodoc.search('#element-2').attribute('href').text.gsub("a", "A"))
catch_error(c, nokodoc.search('#element-3 h1').attribute('style').text.strip)
def catch_error(variable_name, code)
begin
variable_name = code
rescue
puts "Code in #{variable_name} caused an error"
end
variable_name
end
I know that putting & before each new method works:
nokodoc.search('#element-1')&.attribute('href')&.text
But I want to be able to display the error with a 'puts' in my terminal to see when my code gives an error.
Is it possible?
You can't pass your code as a regular argument to a method because it'll be evaluated (and raise an exception) before it gets passed to your catch_error method. You could pass it as a block--something like
a = catch_error('element_1 href text') do
nokodoc.search('#element-1').attribute('href').text
end
def catch_error(error_description)
yield
rescue
puts "#{error_description} caused an error"
end
Note that you can't pass a to the method as variable_name: it hasn't been defined anywhere before calling that method, so you'll get an undefined local variable or method error. Even if you define a earlier, it won't work correctly. If your code works without raising an exception, the method will return the right value but the value won't get stored anywhere outside the method scope. If there is an exception, variable_name will have whatever value a had before the method (nil if you defined it without setting it), so your error message would output something like Code in caused an error. That's why I added an error_description parameter.
You could also try logging the message and backtrace if you didn't want to have to specify an error description every time.
a = catch_error(nokodoc) do |doc|
doc.search('#element-1').attribute('href').text
end
def catch_error(doc)
yield doc
rescue => ex
puts doc.title # Or something else that identifies the document
puts ex.message
puts ex.backtrace.join("\n")
end
I made one additional change here: passing the document in as a parameter so that rescue could easily log something that identifies the document, in case that's important.

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.

Using Ruby to wait for a page to load an element

I currently have a working piece of Ruby that looks like this:
def error_message
browser.span(:id => 'ctl00_cphMainContent_lblMessage').wait_until_present(30) do
not errors.empty?
end
errors
end
However, I'd prefer something more like this:
span(:errors, :id => 'ctl00_cphMainContent_lblMessage')
##farther down##
def error_message
browser.errors.wait_until_present(30) do
etc...
I'm new to Ruby, but how can I do something like this, assuming it's possible?
Typically, you make use of the Watir::Wait.until or <element>.wait_until_present methods.
In this way, you could do something like:
# Earlier in code
#browser = Watir::Browser.start('http://mypage.com/')
# ...
errors = { my_first_error: "You forgot your keys in the car!" }
check_for_errors(error[:my_first_error])
# Wherever
def check_for_errors(error, expiry=30)
error_element = #browser.span(:id => 'ctl00_cphMainContent_lblMessage')
error_element(value: error).wait_until_present(expiry)
end
See the watir documentation for more information.

Stock quote gem - retrieval for nonexistent ticker yields nomethod error

I am using the "stock quote" gem (https://github.com/tyrauber/stock_quote) to retrieve stock prices based on user input tickers. While I have a ticker list that is up-to-date, there are some circumstances where the search yields no results. I have this in my code to get the quote:
#companyname = StockQuote::Stock.quote(#ticker).company
#exchange = StockQuote::Stock.quote(#ticker).exchange
#price = StockQuote::Stock.quote(#ticker).last
And it yields this when #ticker = "AKO-A"
undefined method `attributes' for nil:NilClass
file: stock.rb location: block in parse line: 90
Is there anyway to avoid this nomethoderror by making my code more robust (if error then "blank")? Sorry, I am relatively new to ruby and would appreciate any help to point me in the right direction.
Yeah, the problem was definitely with the gem. It was assuming the symbol was accurate and wasn't properly parsing responses for bad symbols.
Sloppy. Rewrote the classes for cleaner code and greater stability. Added in a response_code instance method, which returns 200 or 404, depending upon the validity of the response. Also, a success? or failure? instance method. And, better spec coverage.
Version bumped, and pushed to rubygems.
This is a very common condition with Ruby code, and a common idiom to return nil on a failed search.
However this specific gem is a little flaky when it fails to get a good search result. You can protect yourself against it failing by using a begin ... rescue block.
begin
stock_quote = StockQuote::Stock.quote(#ticker)
rescue StandardError
stock_quote = nil
end
if stock_quote
#companyname = stock_quote.company
#exchange = stock_quote.exchange
#price = stock_quote.last
end
This might not be ideal program flow for you, so you may need to adapt this.
Note StandardError is what gets rescued by default, I didn't need to write that. You could also put NoMethodError in your situation, and usually you want to restrict rescuing exceptions to specific parts of code where you know how to recover from the error, and also only to the types of errors where you are confident that your handling code is doing the right thing.
Here is an example on how use rescue to get around the nonexistent stock symbol problem
require 'stock_quote'
class StockClass
def self.symbol_check(symbol)
StockQuote::Stock.quote(symbol).symbol
end
def self.price_by_symbol(symbol)
StockQuote::Stock.quote(symbol).latest_price
end
def self.write_price_by_symbol(symbol, price)
filename = "#{symbol}.csv"
todays_date = Time.now.strftime('%Y-%m-%d')
File.open(filename, "a") do |file|
file << "#{todays_date}, #{price}\n"
end
end
end
def stock_price_selector(*symbol_array)
symbol_array.each do |stock_name|
begin
stock_check = StockClass.symbol_check(stock_name)
rescue NoMethodError
puts "#{stock_name} is a bogus ticker symbol"
else
stock_price = StockClass.price_by_symbol(stock_name)
stock_written = StockClass.write_price_by_symbol(stock_name, stock_price)
end
end
end
stock_price_selector('AAPL', 'APPL', 'MSFT', 'GOOG')
This will skip the bogus symbol 'APPL' and work for the legtimate ticker symbols.

RSpec mocking an :each block

I want to use RSpec mocks to provide canned input to a block.
Ruby:
class Parser
attr_accessor :extracted
def parse(fname)
File.open(fname).each do |line|
extracted = line if line =~ /^RCS file: (.*),v$/
end
end
end
RSpec:
describe Parser
before do
#parser = Parser.new
#lines = mock("lines")
#lines.stub!(:each)
File.stub!(:open).and_return(#lines)
end
it "should extract a filename into extracted" do
linetext = [ "RCS file: hello,v\n", "bla bla bla\n" ]
# HELP ME HERE ...
# the :each should be fed with 'linetext'
#lines.should_receive(:each)
#parser.should_receive('extracted=')
#parser.parse("somefile.txt")
end
end
It's a way to test that the internals of the block work correctly by passing fixtured data into it. But I can't figure out how to do the actual feeding with RSpec mocking mechanism.
update: looks like the problem was not with the linetext, but with the:
#parser.should_receive('extracted=')
it's not the way it's called, replacing it in the ruby code with self.extracted= helps a bit, but feels wrong somehow.
To flesh out the how 'and_yield' works: I don't think 'and_return' is really what you want here. That will set the return value of the File.open block, not the lines yielded to its block. To change the example slightly, say you have this:
Ruby
def parse(fname)
lines = []
File.open(fname){ |line| lines << line*2 }
end
Rspec
describe Parser do
it 'should yield each line' do
File.stub(:open).and_yield('first').and_yield('second')
parse('nofile.txt').should eq(['firstfirst','secondsecond'])
end
end
Will pass. If you replaced that line with an 'and_return' like
File.stub(:open).and_return(['first','second'])
It will fail because the block is being bypassed:
expected: ["firstfirst", "secondsecond"]
got: ["first", "second"]
So bottom line is use 'and_yield' to mock the input to 'each' type blocks. Use 'and_return' to mock the output of those blocks.
I don't have a computer with Ruby & RSpec available to check this, but I suspect you need to add a call to and_yields call [1] on the end of the should_receive(:each). However, you might find it simpler not to use mocks in this case e.g. you could return a StringIO instance containing linetext from the File.open stub.
[1] http://rspec.rubyforge.org/rspec/1.1.11/classes/Spec/Mocks/BaseExpectation.src/M000104.html
I would go with the idea of stubbing the File.open call
lines = "RCS file: hello,v\n", "bla bla bla\n"
File.stub!(:open).and_return(lines)
This should be good enough to test the code inside the loop.
This should do the trick:
describe Parser
before do
#parser = Parser.new
end
it "should extract a filename into extracted" do
linetext = [ "RCS file: hello,v\n", "bla bla bla\n" ]
File.should_receive(:open).with("somefile.txt").and_return(linetext)
#parser.parse("somefile.txt")
#parser.extracted.should == "hello"
end
end
There are some bugs in the Parser class (it won't pass the test), but that's how I'd write the test.

Resources