I have a name error and not exactly sure where to define my argument - ruby

I'm making an guitar browsing CLI project. I have the name and url of different guitars like so:
def self.get_electric
doc = Nokogiri::HTML(open("https://reverb.com/c/electric-guitars"))
electrics = []
counter = 0
while counter < doc.css("h2:contains('Popular Electric Guitars')+div.overflowing-row__items ul.tiles.tiles--single-row.tiles--grow.tiles--three-wide li.tiles__tile").length
electric = {
name: doc.css("h2:contains('Popular Electric Guitars')+div.overflowing-row__items ul.tiles.tiles--single-row.tiles--grow.tiles--three-wide li.tiles__tile")[counter].text,
url: doc.css("h2:contains('Popular Electric Guitars')+div.overflowing-row__items ul.tiles.tiles--single-row.tiles--grow.tiles--three-wide li.tiles__tile a.marketing-callout__inner")[counter]["href"]
}
counter += 1
electrics << electric
end
electrics
HiStrung::Guitar.mass_create_electrics(electrics)
end
If user types in 'electric' or 'acoustic' they will get the name of the guitar and the url:
Enter here: electric
1. Fender Telecaster - https://reverb.com/marketplace/electric-guitars?query=telecaster
2. Gibson Les Paul - https://reverb.com/marketplace?query=les%20paul
3. Fender Stratocaster - https://reverb.com/marketplace/electric-guitars?query=stratocaster
4. Gibson SG - https://reverb.com/marketplace?query=sg
5. Fender Jazzmaster - https://reverb.com/marketplace?query=jazzmaster
Now what I want my program to do is when a user types in the number for whichever guitar they want to know more about, it will show the price and description of that guitar. The price and description are in another url.
I'm trying to make a method that grabs the url and get the html for the price and description:
def self.get_electric_info(electric)
url = electric.url
doc = Nokogiri::HTML(url)
end
but i'm getting this name error:
pry(main)> HiStrung::Scraper.get_electric_info(electric)
NameError: undefined local variable or method `electric' for main:Object
from (pry):1:in `__pry__'
Here is my initialize class:
class HiStrung::Guitar
attr_accessor :name, :url
##electrics = []
##acoustics = []
def self.electrics
##electrics
end
def self.acoustics
##acoustics
end
def self.mass_create_electrics(electric_hash)
electric_hash.each do |e_hash|
electric = HiStrung::Guitar.new(e_hash[:name], e_hash[:url])
##electrics << electric
end
end
def self.mass_create_acoustics(acoustic_hash)
acoustic_hash.each do |a_hash|
acoustic = HiStrung::Guitar.new(a_hash[:name], a_hash[:url])
##acoustics << acoustic
end
end
def initialize(name, url)
#name, #url = name, url
end
end

You don't provide the context at the point where you are calling that method, but if you look in your code you'll see you don't have an electric variable defined.
Assign electric to the Guitar object you want information for before you invoke your method.
Do something like this:
electric = HiStrung::Guitar.new("My name", "My url")
HiStrung::Scraper.get_electric_info(electric)
That should fix the problem.

Related

Boolean method not returning in different situations [RUBY]

I'm building a simple web-scraper (scraping jobs from indeed.com) for practice and I'm trying to implement the following method (low_salary?(salary)). The aim is for the method to compare a minimum (i.e. desired) salary, compare it with the offered salary contained in the job object (#salary):
class Job
attr_reader :title, :company, :location, :salary, :url
def initialize(title, company, location, salary, url)
#title = title
#company = company
#location = location
#salary = salary
#url = url
end
def low_salary?(minimum_salary)
return if !#salary
minimum_salary < #salary.split(/[^\d]/)[1..2].join.to_i
end
end
The method works fine when comapring #salary and the min_salary variable given to it, the delete_if appropriately deletes the elements that return true for low_salary? and returns correctly when #salary is nil (indeed listings don't always include the salary so my assumption is that there will be some nil values) in the following test program (Also: I am unsure as to why minimum_salary < #salary works but #salary < minimum_salary doesn't, but this does exactly what I want it to do):
require_relative('job_class.rb')
job = Job.new("designer", "company", "location", "£23,000 a year", "url")
job_results = []
job_results.push(job)
min_salary = 50000
print job.low_salary?(min_salary)
job_results.delete_if { |job| job.low_salary?(min_salary) }
print job_results
However in my scraper program, I get a no method error when calling the method: job_class.rb:16:in "low_salary?": undefined method `join' for nil:NilClass (NoMethodError)
require 'nokogiri'
require 'open-uri'
require_relative 'job_class.rb'
class JobSearchTool
def initialize(job_title, location, salary)
#document = Nokogiri::HTML(open("https://uk.indeed.com/jobs?q=#{job_title.gsub('-', '+')}&l=#{location}"))
#job_listings = #document.css('div.mosaic-provider-jobcards > a')
#salary = salary.to_i
#job_results = []
end
def scrape_jobs
#job_listings.each do |job_card|
#job_results.push(Job.new(
job_card.css('h2 > span').text, #title
job_card.css('span.companyName').text, #company
job_card.css('div.companyLocation').text, #location
job_card.css('span.salary-snippet').text, #salary
job_card['href']) #url
)
end
end
def format_jobs
#job_results.each do |job|
puts <<~JOB
#{job.title} - #{job.company} in #{job.location} :#{job.salary}
Apply at: #{job.url}
---------------------------------------------------------------------------------
JOB
end
end
def check_salary
#job_results.delete_if { |job| job.low_salary?(#salary) }
end
def run
scrape_jobs
check_salary
format_jobs
end
if __FILE__ == $0
job_search_tool = JobSearchTool.new(ARGV[0], ARGV[1], ARGV[2])
job_search_tool.run
end
Obviously something from the scraper programme is influencing the method somehow, but I can't understand what it could be. I'm using the method in the exact same way as the test program, so what difference is causing the method not to return when #salary is nil?
A quick search on the URL you're scraping shows there are job posts that don't have a salary, so, when you get the data from that HTML element and initialize a new Job object, the salary is an empty string, and knowing that "".split(/[^\d]/)[1..2] returns nil, that's the error you get.
You must add a way to handle job posts without a salary:
class Job
attr_reader :title, :company, :location, :salary, :url
def initialize(title, company, location, salary, url)
#title = title
#company = company
#location = location
#salary = salary.to_s # Explicit conversion of nil to string
#url = url
end
def low_salary?(minimum_salary)
return if parsed_salary.zero? # parsed_salary returns always an integer,
# so you can check when is zero,
# and not just when is falsy
minimum_salary < parsed_salary
end
private
def parsed_salary
salary[/(?<=£)(\d|,)*(?=\s)/]
.to_s # converts nil to "" if the regex doesn't capture anything
.tr(",", "") # removes the commas to parse the string as an integer
.to_i # parses the string to its corresponding integer representation
end
end
Notice the regex isn't meant to capture everything, but it works with the salary as rendered in the website.

Ruby - Passing instance variables to a class from another

I have 3 classes: Invoice, Address and Customer (but for this problem, only the Invoice and Address class are relevant)
This is my Invoice class:
class Invoice
attr_reader :billing_address, :shipping_address, :order
def initialize(attributes = {})
#billing_address = attributes.values_at(:billing_address)
#shipping_address = attributes.values_at(:shipping_address)
#order = attributes.values_at(:order)
end
end
and this is my Address class:
class Address
attr_reader :zipcode, :full_address
def initialize(zipcode:)
#zipcode = zipcode
url = 'https://viacep.com.br/ws/' + zipcode.to_s + '/json/'
uri = URI(url)
status = Net::HTTP.get_response(uri)
if (status.code == "200")
response = Net::HTTP.get(uri)
full_address = JSON.parse(response)
#full_address = full_address
else
p "Houve um erro. API indisponível. Favor tentar novamente mais tarde."
#full_adress = nil
end
end
end
And this is my Customer class (not much relevant, but i'm showing for better explanation of the problem)
class Customer
attr_reader :name, :age, :email, :gender
def initialize(attributes = {})
#name = attributes.values_at(:name)
#age = attributes.values_at(:age)
#email = attributes.values_at(:email)
#gender = attributes.values_at(:gender)
end
end
As you can see, my Invoice class has 3 instance variables and my Address class has 2 instance variables.
So, if i test something like that:
cliente = Customer.new(name: "Lucas", age: 28, email: "abc#gmail.com", gender: "masculino")
endereco = Address.new(zipcode: 41701035)
entrega = Invoice.new(billing_address: endereco, shipping_address: endereco)
p endereco.instance_variables
[:#zipcode, :#full_address]
p entrega.shipping_address.instance_variables
[]
My instance variables can be acessed through the variable "endereco", that is an Address object, but can't be acessed through entrega.shipping_address that is also an Address object.
To be more precise, if a try this:
p entrega.shipping_address
I get this return:
[#<Address:0x00000001323d58 #zipcode=41701035, #full_address={"cep"=>"41701-035", "logradouro"=>"Rua Parati", "complemento"=>"", "bairro"=>"Alphaville I", "localidade"=>"Salvador", "uf"=>"BA", "unidade"=>"", "ibge"=>"2927408", "gia"=>""}>]
My full object are being returned, but i can't access the content of my #full_address instance variable.
If a do this:
p entrega.shipping_address.full_address
I get a NoMethodError:
solucao.rb:8:in `<main>': undefined method `full_address' for #<Array:0x000000012d25e8> (NoMethodError)
I'm trying to understand why i can't access the content inside my object if i have the full object. Maybe i'm trying to access in the wrong way, i don't know.
Can someone help ?
values_at returns an array of values (see https://apidock.com/ruby/Hash/values_at
for explanation)
Change
#shipping_address = attributes.values_at(:shipping_address)
into
#shipping_address = attributes[:shipping_address]
And that way #shipping_address will contain an Address object, not an array that contains an Address object
If you take a look at the error, it says
undefined method `full_address' for #<Array:0x000000012d25e8>
You're trying to call full_address on an array. So this means that entrega.shipping_address returns you an array (which it does, of course, take a closer look at the output).
If I were you, I'd look into how shipping_address is implemented. It's a simple attr_reader, so it's backed by an instance variable. Must be you initialize that instance variable to a wrong value (it gets an array instead of an address). Look closely at that initialization code and try to run it in IRB session. You should see the problem.

Assert_equal undefined local variable LRTHW ex52

Hi I made it to the lase exercise os Learn Ruby The Hard Way, and I come at the wall...
Here is the test code:
def test_gothon_map()
assert_equal(START.go('shoot!'), generic_death)
assert_equal(START.go('dodge!'), generic_death)
room = START.go("tell a joke")
assert_equal(room, laser_weapon_armory)
end
And here is the code of the file it should test:
class Room
attr_accessor :name, :description, :paths
def initialize(name, description)
#name = name
#description = description
#paths = {}
end
def ==(other)
self.name==other.name&&self.description==other.description&&self.paths==other.paths
end
def go(direction)
#paths[direction]
end
def add_paths(paths)
#paths.update(paths)
end
end
generic_death = Room.new("death", "You died.")
And when I try to launch the test file I get an error:
generic_death = Room.new("death", "You died.")
I tried to set the "generic_death = Room.new("death", "You died.")" in test_gothon_map method and it worked but the problem is that description of the next object is extremely long, so my questions are:
why assertion doesn't not respond to defined object?
can it be done different way then by putting whole object to testing method, since description of the next object is extremely long...
The nature of local variable is that they are, well, local. This means that they are not available outside the scope they were defined.
That's why ruby does not know what generic_death means in your test.
You can solve this in a couple of ways:
define rooms as constants in the Room class:
class Room
# ...
GENERIC_DEATH = Room.new("death", "You died.")
LASER_WEAPON_ARMORY = Room.new(...)
end
def test_gothon_map()
assert_equal(Room::START.go('shoot!'), Room::GENERIC_DEATH)
assert_equal(Room::START.go('dodge!'), Room::GENERIC_DEATH)
room = Room::START.go("tell a joke")
assert_equal(room, Room::LASER_WEAPON_ARMORY)
end
assert the room by its name, or some other identifier:
def test_gothon_map()
assert_equal(START.go('shoot!').name, "death")
assert_equal(START.go('dodge!').name, "death")
room = START.go("tell a joke")
assert_equal(room.name, "laser weapon armory")
end

Undefined local variable or method 'product'

I am doing a task that requires me add some products together and give a 10% discount providing the total is above £60. I have done the following:
class Checkout
def initialize (rules)
#rules = rules
#cart = []
end
def scan (item)
if product == Product.find(item)
#cart << product.clone
#Clone preserves frozen state whereas .dup() doesn't if use would raise a
#NoMethodError
end
end
def total
#cart = #rules.apply #cart
end
def self.find item
[item]
end
co = Checkout.new(Promotional_Rules.new)
co.empty_cart
co.scan(1)
co.scan(2)
co.scan(3)
puts "Total price: #{co.total}"
puts
co.empty_cart
co.scan(1)
co.scan(3)
co.scan(1)
puts "Total price: #{co.total}"
puts
co.empty_cart
co.scan(1)
co.scan(2)
co.scan(1)
co.scan(3)
puts "Total price: #{co.total}"
puts
However when I run this in irb I get undefined variable or method product. Sounds a bit daft but this should work.
You're using one too many equal signs
def scan (item)
# if product == Product.find(item)
if product = Product.find(item) # <--- should be this
#cart << product.clone
#Clone preserves frozen state whereas .dup() doesn't if use would raise a
#NoMethodError
end
end
Of course, then you'll get a different error since find doesn't exist on Product yet... which I think you're trying to define here:
def self.find item # self should be changed to Product
[item]
end
Then you're going to get an error for apply not existing for Promotional_Rules ...
One of the best ways to debug these errors is follow the stack traces. So for the last error I get the following message:
test.rb:53:in `total': undefined method `apply' for #<Promotional_Rules:0x007f94f48bc7a8> (NoMethodError)
from test.rb:72:in `<main>'
That's basically saying that at line 53 you'll find apply hasn't been defined for #rules which is an instance of Promotional_Rules. Looking at the Promotional_Rules class you've clearly defined that method as apply_to_item and not apply. If you keep following and fixing the rabbit trails like this for stack traces you'll be able to debug your program with ease!

Ruby Name Error - Uninitialized constant

I am doing exercises and am
getting NameError:Unitialized Constant MyUnitTests::Room when running test_ex47.rb.
test_ex47.rb:
require 'test/unit'
require_relative '../lib/ex47'
class MyUnitTests < Test::Unit::TestCase
def test_room()
gold = Room.new("Gold Room", """This room has gold in it you can grab. There's a doo to the north.""")
assert_equal(gold.name, "GoldRoom")
assert_equal(gold.paths, {})
end
def test_room_paths()
center = Room.new("Center", "Test room in the center.")
north = Room.new("North", "Test room in the north.")
south = Room.new("South", "Test room in the south.")
center.add_paths({:north => north, :south => south})
assert_equal(center.go(:north), north)
assert_equal(center.go(:south), south)
end
def test_map()
start = Room.new("Start", "You can go west and down a hole.")
west = Room.new("Trees", "There are trees here, you can go east.")
down = Room.new("Dungeon", "It's dark down here, you can go up.")
start.add_paths({:west => west, :down => down})
west.add_paths({:east => start})
down.add_paths({:up => start})
assert_equal(start.go(:west), west)
assert_equal(start.go(:west).go(:east), start)
assert_equal(start.go(down).go(up), start)
end
end
ex47.rb is located in the lib folder and looks like:
class Room
aatr_accessor :name, :description, :paths
def initialize(name, description)
#name = name
#description = description
#paths = {}
end
def go(direction)
#paths[direction]
end
def add_paths(paths)
#paths.update(paths)
end
end
Error:
Finished tests in 0.000872s, 3440.3670 tests/s, 0.0000 assertions/s.
1) Error:
test_map(MyUnitTests):
NameError: uninitialized constant MyUnitTests::Room
test_ex47.rb:22:in `test_map'
2) Error:
test_room(MyUnitTests):
NameError: uninitialized constant MyUnitTests::Room
test_ex47.rb:6:in `test_room'
3) Error:
test_room_paths(MyUnitTests):
NameError: uninitialized constant MyUnitTests::Room
test_ex47.rb:12:in `test_room_paths'
3 tests, 0 assertions, 0 failures, 3 errors, 0 skips]
The problem here is that you are creating a Room object inside the MyUnitTests class on line 3. Ruby thinks you want to use a class called MyUnitTest::Room, which doesn't exist. You need to use an absolute class reference, like so:
class MyUnitTests < Test::Unit::TestCase
def test_room()
gold = ::Room.new("Gold Room", """This room has gold in it you can grab. There's a doo to the north.""")
assert_equal(gold.name, "GoldRoom")
assert_equal(gold.paths, {})
end
Notice the :: before Room.new on line 3 there? That tells Ruby that you want to create a Room object from the top level name space :)
I hope that answers your question.
EDIT: You'll also need to change your other references to the Room class to ::Room. Sorry, I thought only the top one was a problem because of the indentation. A closer look reveals that the rest need the :: as well.

Resources