Code fails because variable used in if statement is nil - ruby

I have the following:
def self.parse_categories
categories = #data.css('#refinements ul').first
if categories.css('li:nth-child(1) a span').text == "Pet Supplies"
#categories_hash = {}
categories_category = categories.css('li:nth-child(2) strong').text
categories.css('li').drop(2).each do | categories |
categories_title = categories.css('.refinementLink').text
categories_count = categories.css('.narrowValue').text[/[\d,]+/].delete(",").to_i
#categories_hash[:categories] ||= {}
#categories_hash[:categories][categories_category] ||= {}
#categories_hash[:categories][categories_category][categories_title] = categories_count
end
else
#categories_hash = {}
end
return #categories_hash
end
Now the code works well in a page which has the element: #refinements ul But the code breaks in a page where there isn't. How can I do it so that nothing happens when there is no #refinements ul? Like ignoring the code without throwing an error or just skipping to else?

Like this, for example
def self.parse_categories
categories = #data.css('#refinements ul').first
return unless categories
# carry on
end

The problem is when #data.css('#refinements ul') is nil. Right?
So, try converting it to array and first.
categories = #data.css('#refinements ul').to_a.first

Related

Changing argument inside a function using yield

I am new to the concept of yield and currently practising it.
I was expecting to get ["bread", "JUICY", "bread"]
but I got ["bread", "steak", "bread"].
Can you please help me understand why? And how can I fix my code?
def burger(patty)
if block_given?
yield(patty)
end
return ["bread", patty, "bread"]
end
# TODO: Change 'steak'to 'JUICY'using yield
juicy_burger = burger("steak") do |patty|
patty = "JUICY"
end
p juicy_burger
juicy_burger = burger("steak") do |patty|
patty = "JUICY"
end
Reassignments like this are not propagated to the outer scopes. Once this block returns, patty "reverts" to its initial value.
Solution? Use the overwritten value while it's still in scope.
def burger(patty)
if block_given?
patty = yield(patty)
# ^ shadow the parameter here.
# doesn't have to be a shadow. You can use any name. new_patty = yield(patty)
end
return ["bread", patty, "bread"]
end
juicy_burger = burger("steak") do |patty|
"JUICY #{patty}" # <- simple return here
end
p juicy_burger # >> ["bread", "JUICY steak", "bread"]
The variable is local to the function and you did not save back the value yield returned. The code below will give you ["bread", "JUICY", "bread"]
def burger(patty)
if block_given?
patty = yield(patty) # <-- this is the diff
end
return ["bread", patty, "bread"]
end
# TODO: Change 'steak'to 'JUICY'using yield
juicy_burger = burger("steak") do |patty|
patty = "JUICY"
end
p juicy_burger

Parse JSON like syntax to ruby object

Simple parser which turned out to be much harder than i thought. I need a string parser to convert nested fields to ruby object. In my case api response will only return desired fields.
Given
Parser.parse "album{name, photo{name, picture, tags}}, post{id}"
Desired output or similar
{album: [:name, photo: [:name, :picture, :tags]], post: [:id]}
Any thoughts?
Wrote my own solution
module Parser
extend self
def parse str
parse_list(str).map do |i|
extract_item_fields i
end
end
def extract_item_fields item
field_name, fields_str = item.scan(/(.+?){(.+)}/).flatten
if field_name.nil?
item
else
fields = parse_list fields_str
result = fields.map { |field| extract_item_fields(field) }
{ field_name => result }
end
end
def parse_list list
return list if list.nil?
list.concat(',').scan(/([^,{}]+({.+?})?),/).map(&:first).map(&:strip)
end
end
str = 'album{name, photo{name, picture, tags}}, post{id}'
puts Parser.parse(str).inspect
# => [{"album"=>["name", {"photo"=>["name", "picture", "tags"]}]}, {"post"=>["id"]}]

Using variable declared in one method to open webpage in another method

I am working on a CLI Project and trying to open up a web page by using url variable declared in another method.
def self.open_deal_page(input)
index = input.to_i - 1
#deals = PopularDeals::NewDeals.new_deals
#deals.each do |info|
d = info[index]
#product_url = "#{d.url}"
end
#product_url.to_s
puts "They got me!"
end
def self.deal_page(product_url)
#self.open_deal_page(input)
deal = {}
html = Nokogiri::HTML(open(#product_url))
doc = Nokogiri::HTML(html)
deal[:name] = doc.css(".dealTitle h1").text.strip
deal[:discription] = doc.css(".textDescription").text.strip
deal[:purchase] = doc.css("div a.button").attribute("href")
deal
#binding.pry
end
but I am receiving this error.
`open': no implicit conversion of nil into String (TypeError)
any possible solution? Thank you so much in advance.
Try returning your #product_url within your open_deal_page method, because now you're returning puts "They got me!", and also note that your product_url is being created inside your each block, so, it won't be accessible then, try creating it before as an empty string and then you can return it.
def open_deal_page(input)
...
# Create the variable
product_url = ''
# Assign it the value
deals.each do |info|
product_url = "#{info[index].url}"
end
# And return it
product_url
end
In your deal_page method tell to Nokogiri to open the product_url that you're passing as argument.
def deal_page(product_url)
...
html = Nokogiri::HTML(open(product_url))
...
end

Can mechanize for Ruby filter the contents of a <select> by class?

Sorry, but I didn't find the documentation enlightening at all. Basically, I am trying to iterate through a where some options are not valid. The ones I want have 'class="active"'. Can I do that with mechanize? Here's what I have so far:
class Scraper
def init
mech = Mechanize.new
page = mech.get('url')
#Now go through the <select> to get product numbers for the different flavors
form = page.form_with(:id => 'twister')
select = form.field_with(:name => 'dropdown_selected_flavor_name')
select.options.each do |o|
if (o.text != "")
value = o
end
productNumber = trim_pn(value.to_s[2..12])
puts productNumber
end
end
#Checks validity of product number and removes excess characters if necessary
def trim_pn(pn)
if (pn[0] == ",")
pn = pn[1..-1]
end
return pn
end
end
p = Scraper.new
p.init
All that does is grabs the product number and removes some extra info that I don't want. I thought replacing the .each do with this:
select.options_with(:class => 'active').each do |o|
if (o.text != "")
value = o
end
end
But that throws "undefined method 'dom_class' for Mechanize:Form:Option blah blah." Is there are different way I should be approaching this?

Dynamically check if a field in JSON is nil without using eval

Here's an extract of the code that I am using:
def retrieve(user_token, quote_id, check="quotes")
end_time = Time.now + 15
match = false
until Time.now > end_time || match
#response = http_request.get(quote_get_url(quote_id, user_token))
eval("match = !JSON.parse(#response.body)#{field(check)}.nil?")
end
match.eql?(false) ? nil : #response
end
private
def field (check)
hash = {"quotes" => '["quotes"][0]',
"transaction-items" => '["quotes"][0]["links"]["transactionItems"]'
}
hash[check]
end
I was informed that using eval in this manner is not good practice. Could anyone suggest a better way of dynamically checking the existence of a JSON node (field?). I want this to do:
psudo: match = !JSON.parse(#response.body) + dynamic-path + .nil?
Store paths as arrays of path elements (['quotes', 0]). With a little helper function you'll be able to avoid eval. It is, indeed, completely inappropriate here.
Something along these lines:
class Hash
def deep_get(path)
path.reduce(self) do |memo, path_element|
return unless memo
memo[path_element]
end
end
end
path = ['quotes', 0]
hash = JSON.parse(response.body)
match = !hash.deep_get(path).nil?

Resources