Return from context above - ruby

This question is little complicated to formulate but I will do my best. Trough our code we have snippets such as
response = do_something()
return response unless response.ok?
I was think of writing wrapper method which would remove need for this step, and it would look something like this
def rr(&block)
response = yield
unless response.ok?
# somehow do return but in context above (magic needed here)
end
response
end
After that I would be able to minimize code from above to be
response = rr { do_something() }
Seems impossible but this is Ruby so maybe there is a way?

The correct way to return across multiple layers of the stack when something goes wrong (which appears to be what you are trying to do) is to raise an exception:
class RequestFailedException < StandardError; end
def rr(&block)
response = yield
unless response.ok?
raise RequestFailedException, "Response not okay: #{response.inspect}"
end
response
end
Usage:
def do_lots_of_things()
rr { do_something }
rr { do_something_else }
rr { another_thing }
end
begin
do_lots_of_things
rescue RequestFailedException => e
# Handle or ignore error
end

Wouldn't you want to just write a wrapper that does exactly that? Functionally it seems you're just ignoring non-ok? responses:
def rr
response = yield
response.ok? ? response : nil
end
Maybe I'm missing something here but I don't see why you'd need to force a return in another context, something that's not even possible anyway.

Related

How to use a dynamic number of yield statements in Ruby

def within_page_frame
application_tab = window_opened_by { click_link('Application Info') }
within_window application_tab do
within_frame find_by_id('ApplicationInfo') do
yield
end
end
end
it 'should view the web page', :smoke do
visit_home_page p
application_tab = window_opened_by { click_link('Application Info') }
within_page_frame {expect(find_by_id('home page').to be}
end
Here is code that is currently working. I am using the method "within_page_frame" to avoid repeating code in other specs in my test suite.
What I would like to do is be able to pass in multiple expect statements without having to specify the exact number of yield statements in the within_page_frame method. Is there a way to have a dynamic number of yield statements in my method so that I can pass in any number of expect statements?
Like this?
within_page_frame do
expect(find_by_id('home page')).to be
expect(find_by_id('something else')).to be
end

Process throwing "Unexpected return" error

I am trying to create a Chef recipe that will read attributes from a Role, and one of those attributes will contain Bash code that needs to be executed on the server.
Hence, I created the below process:
def proc_test(command)
proc = Proc.new { |command| command_out = Mixlib::ShellOut.new(command) ; command_out.run_command ; return command_out.stdout + command_out.stderr }
proc.call(command)
end
which, when run like this:
node.set['attributes']['nested_attribute'] = proc_test("hostname")
works. But if I attempt to do this:
command="hostname"
node.set['attributes']['nested_attribute'] = proc_test(command)
it throws this error:
LocalJumpError
--------------
unexpected return
Can someone help me with this?
If, instead of using the return keyword, I use put, I get the same error.
I don't understand why you think you need a Proc at all. Just use the normal shell_out! helper method or a bash resource.
The problem is that return can't be called from Ruby procs. Just skip return to avoid the error:
def proc_test(command)
proc = Proc.new do |command|
command_out = Mixlib::ShellOut.new(command)
command_out.run_command
command_out.stdout + command_out.stderr
end
proc.call(command)
end

Foreach loop in XML generator not breaking

I am trying to generate XML, but the loop isn't breaking. Here is a part of the code:
#key = 0
#cont.each do |pr|
xml.product {
#key += 1
puts #key.to_s
begin
#main = Nokogiri::HTML(open(#url+pr['href'], "User-Agent" => "Ruby/#{RUBY_VERSION}","From" => "foo#bar.invalid", "Referer" => "http://www.ruby-lang.org/"))
rescue
puts "rescue"
next
end
puts pr['href']
puts #key.to_s
break //this break doesn't work
#something else
}
end
Most interesting is that in the final generated XML file, break worked. The file contains only one product, but on the console #key was printed fully, which means the foreach loop doesn't break.
Could it be a Nokogiri XML-specific error, because of open brackets in the head of the loop?
In general I think how you're going about trying to generate the XML is confused. Don't convolute your code any more than necessary; Instead of starting to generate some XML then aborting it inside the block because you can't find the page you want, grab the pages you want first, then start processing.
I'd move the begin/rescue block outside the XML generation. Its existence inside the XML generation block results in poor logic and questionable practices of using next and break. Instead I'd recommend something like this untested code:
#main = []
#cont.each do |pr|
begin
#main << Nokogiri::HTML(
open(#url + pr['href'])
)
rescue
puts 'rescue'
next
end
end
builder = Nokogiri::XML::Builder.new do |xml|
xml.root {
xml.products {
#main.each do |m|
xml.product {
xml.id_ m.at('id').text
xml.name m.at('name').text
}
end
}
}
end
puts builder.to_xml
Which makes it easy to see that the code is keying off being able to retrieve a page.
This code is untested because we have no idea what your input values are or what your output should look like. Having valid input, expected output and a working example of your code that demonstrates the problem is essential if you want help debugging a problem with your code.
The use of #url + pr['href'] isn't generally a good idea. Instead use the URI class to build up the URL for you. URI handles encoding and ensures the URI is valid.

Assign variable as either output of equation OR nil if equation can't run (circumventing NoMethodError)

I'm saving an object as such:
#rentalrequest = RentalRequest.new do |rr|
rr.delivery_start = Time.zone.parse(request_params[:deliverydate] + " " + request_params[:deliverytime_start]).utc
...
end
Every once in a while, my front end validation fails, and somehow, the form is posted even though deliverydate and deliverytime_start are blank. In this case, the controller breaks with a NoMethodError because this statement doesn't make sense:
Time.zone.parse("")
However, rather than having to write a rescue for when this happens, I feel like it's so much easier if I can just say rr.delivery_start = nil if Time.zone.parse doesn't work. That way, the back end validation on the #rentalrequest object kicks in and serves as a rescue.
But I'm not sure how to write the rr.delivery_start = nil if Time.zone.parse doesn't work (like... if any part of it doesn't work)
Thoughts?
How about checking if those params exist instead?
#rentalrequest = RentalRequest.new do |rr|
rr.delivery_start = nil
if request_params[:deliverydate].present? && request_params[:deliverytime_start].present?
rr.delivery_start = Time.zone.parse(request_params[:deliverydate] + " " + request_params[:deliverytime_start]).utc
end
...
end
Your calculation is in the wrong place, your model should be maintaining its own state by itself. Something like this perhaps:
class RentalRequest < ActiveRecord::Base
before_validation :set_delivery_start
private
def set_delivery_start
# Presumably validations will catch these conditions...
return if(!deliverydate || !deliverytime_start)
self.delivery_start = ... # whatever calculation matches the deliverydate and delivertime_start types goes here
end
end
and then you'd have validations to ensure that all three delivery values made sense.
You can require those parameters if new block is in controller.
def request_params
params
.require(:rental_request)
.permit(..., :deliverydate, :deliverytime_start)
.require(:deliverydate, :deliverytime_start)
end

Mocking popen3 block form ruby

I am developing some test cases in Ruby using rspec.
I am attempting to mock the popen3 function.
However, while still keeping the blocking form, I am unable to capture the expected output information:
Class MyClass
def execute_command
Open3.popen3(command) do |stdin, stdout, stderr, wait_thr|
output['wait_thr'] = wait_thr.value
while line = stderr.gets
output['stderr'] += line
end
end
return output
end
end
To mock out the function, I am doing the following:
it 'should do something'
response = []
response << 'stdin'
response << 'stdout'
response << 'test'
response << 'exit 0'
# expect
allow(Open3).to receive(:popen3).with(command).and_yield(response)
# when
output = myClassInstance.execute_script
#then
expect(output['wait_thr'].to_s).to include('exit 0')
Mocking out the function doesn't enter the "do" code and I'm left with an empty data structure.
I was wondering how I could properly do this?
Thanks!
To add some more context to Chris Reisor's answer, this is the approach that worked for me:
I have a piece of code that reads as shown here.
Open3.popen2e(*cmd) do |_, stdout_and_stderr, wait_thr|
while (line = stdout_and_stderr.gets)
puts line
end
raise NonZeroExitCode, "Exited with exit code #{wait_thr.value.exitcode}" unless wait_thr.value.success?
end
And my testing setup looks like shown below.
let(:wait_thr) { double }
let(:wait_thr_value) { double }
let(:stdout_and_stderr) { double }
before do
allow(wait_thr).to receive(:value).and_return(wait_thr_value)
allow(wait_thr_value).to receive(:exitcode).and_return(0)
allow(wait_thr_value).to receive(:success?).and_return(true)
allow(stdout_and_stderr).to receive(:gets).and_return('output', nil)
allow(Open3).to receive(:popen2e).and_yield(nil, stdout_and_stderr, wait_thr)
end
I think you needed to put "*response" instead of "response."
allow(Open3).to receive(:popen3).with(command).and_yield(*response)
That will send 4 string args to and_yield ("arity of 4"), rather than one arg which is an array.

Resources