Ruby skips items from list tasks - ruby

I am trying to make an app which if give the option to type, it types false then it skips the certain element from the list and it jumps to the next executing the same task.
That is the basic idea of the following code:
string["items"].each do |item|
p continue.to_s + "<- item"
begin
Anemone.crawl("http://" + item["displayLink"] + "/") do |anemone|
anemone.on_every_page do |page|
if continue.chomp.to_bool == false
raise "no more please"
end
request = Typhoeus::Request.new(page.url, followlocation: true)
response = request.run
email = /[-0-9a-zA-Z.+_]+#[-0-9a-zA-Z.+_]+\.[a-zA-Z]{2,4}/.match(response.body)
if email.nil?
else
p email
begin
continue = Timeout::timeout(2) do
p "insert now false/nothing"
gets
end
rescue Timeout::Error
continue = "true"
end
end
end
end
rescue
continue = true
next
end
p "---------------------------------------------------------"
end
As the code shows, if the user types false when prompted the app should skip the item and go to the next one. However what it does is: when the user types false the app skips the current item and then doesn't execute any of the code that should be executed for all of the other items except the printing ( the second line of code );
Here is how the output looks like:
$ruby main.rb
"1"
"true<- item"
#<MatchData "support#keycreative.com">
"insert now false/nothing"
false
"true<- item"
"true<- item"
"true<- item"
As I'm doing my best to show after false is entered the code does skip the certain item from the list but it also never ever executes code for the other items as it should since it is an each loop
First I thought that maybe the continue is false however as you can see from the output the continue is true which makes me wonder why does ruby skip my code?
UPDATE
Here is where the to_bool method comes from:
class String
def to_bool()
return true if self == "true"
return false if self == "false"
return nil
end
end

In your last rescue statement add:
rescue => e
puts e.message
continue = true
next
end
and inspect the output. Most likely your code is throwing an exception other than "no more please" (I expect undefined method to_bool for true:TrueClass). Note that using exception for skipping the loop element is a terrible idea. Why can't you just get rid of this rescue and do:
if continue.chomp.to_bool == false
continue = true
next
end
There are a lot of things in this code which makes it very un-ruby-like. If you want to improve it please paste it to StackExchange CodeReview page. (link in the comment).
UPDATE:
My bad, you are in nested loop, so the if statement won't work. You might look at sth similar to raise/rescue bit, namely throw/catch, see example here: How to break from nested loops in Ruby?. I still think you should post it to codereview though for refactoring advises.
As to your actual code (without refactoring). You are calling to_bool method on continue, and in your rescue block you assign true instead of 'true'. Hence your to_bool method raises exception which is then rescued same way as 'no more please' exception.

Related

How can i test this method RSpec

I have this method - choose_option
enter image description here
and this help methods
enter image description here
Question, how can i test and take green light simpelcov.
allow(adapter).to receive(:gets).and_return('my_guess')
except(adapter).to receive(:adapter_my_guess)
But this don' work
Error:
expected: 1 time with any arguments
received: 2 times
Because my wrapper all time call choose_option seems like recursion
def adapter_my_guess(guess)
#game.instance_variable_set(:#secret_code, %w[1 2 3 4])
if #game.attempts.positive?
puts show_guess_result(#game.my_guess(guess))
else
puts I18n.t(:lose_game, secret_code: #game.instance_variable_get(:#secret_code))
end
end
def wrapper(method_for_wrap)
puts method_for_wrap
#game.win == false ? choose_option : Finishe.new(#game, #statistics).finishe
end
def choose_option
option = input(I18n.t(:start_game_options, option1: OPTION_1, option2: OPTION_2, exit: EXIT))
case option
when OPTION_1 then wrapper(adapter_my_guess(input(I18n.t(:puts_guess, exit: OR_EXIT))))
when OPTION_2 then wrapper(adapter_give_hints)
else
raise(StandardError, WRONG_OPTION)
end
rescue StandardError => e
puts e
choose_option
end
except(adapter).to receive(:adapter_my_guess) says adapter will receive one and only one call to adapter_my_guess. It's received two.
If that is correct, change your expectation to expect more calls by adding at_least(1).time.
If that is not correct, we'd need to know more about your code and test.
Some notes...
Do not use instance_variable_get and instance_variable_set in production code. Grabbing at other object's internal variables tangles up the code and makes it hard to change the object without unforeseen consequences. Make secret_code a proper accessor method.
choose_option does three things. It gets the option, it interprets the option, and it decides if it needs to get the option again. Split it into three methods, one to get the option, one to interpret the option, and one to put them together. That will be more flexible and easier to test.
Catching every StandardError is too broad. With the methods split, the option is an argument to a method. You can change the exception to be a more specific ArgumentError.
def get_option
input(I18n.t(:start_game_options, option1: OPTION_1, option2: OPTION_2, exit: EXIT))
end
def do_option(option)
case option
when OPTION_1 then wrapper(adapter_my_guess(input(I18n.t(:puts_guess, exit: OR_EXIT))))
when OPTION_2 then wrapper(adapter_give_hints)
else
raise(ArgumentError, WRONG_OPTION)
end
end
def choose_option
do_option(get_option)
rescue ArgumentError
choose_option
end
Now each can be unit tested without having to mock the whole process. For example, all we need to test in choose_option is whether it gets an option, tries to do something with it, and retries.
describe '#choose_option' do
context 'with a good option' do
it 'gets and does the option once' do
option = double
expect(adapter).to receive(:get_option)
.and_return(option)
expect(adapter).to receive(:do_option)
.with(double)
adapter.choose_option
end
end
context 'with a bad option' do
it 'gets and does the option again' do
good_option = double
bad_option = double
# This will raise an ArgumentError.
expect(adapter).to receive(:do_option)
.with(bad_option)
.and_call_original
# This will not.
expect(adapter).to receive(:do_option)
.with(good_option)
# First use the bad option, then the good one.
expect(adapter).to receive(:get_option)
.and_return(bad_option, good_option)
adapter.choose_option
end
end
end
We don't need to know what do_option nor get_option does to test choose_option, they can be totally mocked. do_option and get_option can be unit tested separately.
describe '#do_option' do
subject { adapter.do_option(option) }
context 'with option 1' do
let(:option) { described_class.OPTION_1 }
it 'guesses' do
expect(adapter).to receive(:adapter_my_guess)
subject
end
end
context 'with option 2' do
let(:option) { described_class.OPTION_2 }
it 'gives hints' do
expect(adapter).to receive(:adapter_give_hints)
subject
end
end
context 'with the wrong option' do
let(:option) { "basset hounds got long ears" }
it 'raises' do
expect { subject }.to raise_error(ArgumentError)
end
end
end
That's the basic approach.

Ruby script ignores conditional and breaks method without throwing errors

I have this (I believe) straight and easy method meant to verify if a certain string only includes numbers and isn't empty.
class String
def is_number?
puts "Here it's working, 1"
if self.scan(/\D/).empty? and self != ""
return true
puts "true"
else
return false
puts "false"
end
puts "Here it's working, 2"
end
end
"asd".is_number?
puts "Here it's working, 3"
The result is quite astonishing to me:
The method works until before the conditional. At that point it doesn't go with the "then" nor the "else" options (which, up to today, I never thought to be an option too), and instead breaks the method. Then, it proceeds to the following command. Finally, at the end of the program it sits there without throwing any error.
I honestly don't know how to proceed at this point.
When you used return in a method it will not execute any code after that, if you are expecting true/false to print you should put it above the return statement
def is_number?
puts "Here it's working, 1"
if self.scan(/\D/).empty? and self != ""
puts "true"
return true
else
puts "false"
return false
end
puts "Here it's working, 2"
end
Note:- "Here it's working, 2" statement will never execute as there will be return statement before that.
it doesn't go with the "then" nor the "else" options
No, this is not what happens here, as described in the answer from Salil.
For the future, if you formulate a hypothesis about your code, you should prove or disprove it. Not for us, for yourself. Else how do you know this is actually what is happening?
For example, something like this would reliably verify that the control does indeed enter one of the conditional branches.
if self.scan(/\D/).empty? and self != ""
#return true
#puts "true"
raise "error from if branch"
else
#return false
#puts "false"
raise "error from else branch"
end

Return from a loop from a different method

My understanding is you can exit from a program with a return. How do you return from a loop? When I run return_method as in the following, I want to exit the loop with "RETURNING" returned.
def return_method
return "RETURNING"
end
loop do
puts "Enter:"
answer = gets.chomp
if answer == 'run'
return_method
end
break if answer == 'y'
end
break doesn't work within my method.
A typical way to escape from nested loops of from nested method calls is to use catch ... throw.
RETURNING = "RETURNING"
def return_method
throw RETURNING
end
catch(RETURNING) do
loop do
puts "Enter:"
answer = gets.chomp
if answer == 'run'
return_method
end
break if answer == 'y'
end
end
It's generally not the case that a method call forces the calling method to do something as abrupt as return. That's what exceptions are for, they will bubble up if not caught, but that's seriously heavy-handed for this sort of thing. Instead make your method return a truthy value if you want to break the loop:
def return_method
puts "RETURNING"
true
end
loop do
puts "Enter:"
answer = gets.chomp
case (answer)
when 'run'
break if return_method
when 'y'
break
end
end

Rspec mocking block on stub not being yielded

So i have two calls to a method called retry_with_timeout that takes a block and executes until the block returns true or a value other than nil (i.e false will result in a loop) or until a timeout occurs
Sample class:
def do_the_thing
retry_with_timeout(10, 5) do
case something
when 1
false
when 2
false
else
raise
end
end
retry_with_timeout(30, 10) do
case something_else
when 1
false
when 2
false
when 3
true
else
raise
end
end
end
Spec class:
it "should pass when the thing is 3" do
model = test_model #creates a double and stubs all of the necessary common methods
t_model.stub(:retry_with_timeout).with(10, 5).ordered
t_model.stub(:retry_with_timeout).with(30, 10).and_yield().ordered
expect { t_model.do_the_thing }.to be(true)
end
I get an error because the '3' case isn't in the first block, thus the 'else' is called...
I need to skip the first and evaluate in the second block.... I have tried EVERYTHING and I am LOSING MY MIND!!!! Can anyone help me?
Ok, so I've answered my own question... Turns out there are some features that aren't documented... In order to return then yield, one must do it the following way:
t_model.stub(:some_method).and_return("Cool", "Awesome", and_yield(foo))
#Just for informations' sake
t_model.stub(:some_other_method).and_return("FOO", "BAR", raise_error)
its added as a return item for some reason and ISN'T DOCUMENTED ANYWHERE!!!!!

How do I check for text inside a <div>?

I am trying to access some text that is located in a DIV.
I need to check to see if the page holds the text so I can return a true or false.
The code I am using is below:
cancel = browser.text.include?("Current Cancelled")
if cancel == true
puts "Line item cancelled"
else
puts "****Line item not cancelled****"
end
But it returns false every time.
Here is a code snippet of what I am looking into:
I'd really recommend using Nokogiri to parse the content.
require 'nokogiri'
doc = Nokogiri::HTML('<div><span class="label">Current</span>Cancelled</div>')
doc.at('//div/span[#class="label"]/../text()').text # => "Cancelled"
(doc.at('//div/span[#class="label"]/../text()').text.downcase == 'cancelled') # => true
!!(doc.at('//div/span[#class="label"]/../text()').text.downcase['cancelled']) # => true
Something like one of the two bottom statements will get you a usable true/false.
The probable reason this isn't working is because the string you're testing for contains a newline character and a non breaking space.
This could work...
if browser.div(:text, /Current.*Cancelled/).exists?
puts "Line item cancelled"
else
puts "****Line item not cancelled****"
end
or
if browser.text =~ /Current.*Cancelled/
puts "Line item cancelled"
else
puts "****Line item not cancelled****"
end
etc.
Watir's Browser object has now the #elements_by_xpath method...
See Watir's API
Just pin-point your DIV and ask for its #text method. Pretty much like what the Tin Man suggests but without requiring nokogiri.
AFIK Watir uses internally exactly for the purpose of locating elements (it's a dependency gem that Watir installs) anyway.
I believe the fact that the text is inside a table is causing this problem.
You might consider drilling into the table doing:
cancel = browser.table(:class, 'basic-table').each { |row|
test = row.text.include?("Current Cancelled")
return test if test == true
}
Wow. That makes sense. I wonder how I
could split these up and get them to
combine for my check.
Okay, here's a really quick draft:
div = browser.table(:class, 'basic-table').div(:text, /Cancelled/)
cancel = div.exist? and div.span(:index, 1).text == 'Current'
if cancel
puts "Line item cancelled"
else
puts "****Line item not cancelled****"
end
You could also combine a few of the regular expression approaches below (mostly those from Kinofrost), with the idea of narrowing it down to looking just inside a single cell within the table. That should be faster, and less prone to a false alert should the words 'Current' and 'Cancelled' occur in that order with anything between them, elsewhere on the page.
if browser.table(:class, 'basic-table').cell(:text, /Current.*Cancelled/).exists?
puts "Line item cancelled"
else
puts "****Line item not cancelled****"
end

Resources