Process throwing "Unexpected return" error - ruby

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

Related

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

Return from context above

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.

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.

Can not pass a DBI::DatabaseHandle object as an argument to a ruby gem method

I'm new to ruby so forgive me in advance if this is a silly question. I've googled for answers but nothing relevant comes up and it seems the answer should be obvious.
I'm attempting to pass a DBI::DatabaseHandle as function argument and I'm getting a wrong "number of arguments" error when I run the function. Here's my code...
require 'rubygems'
require 'dbi'
class CmsTest
def self.get_dbi_connection(hostname, user, password)
connection = DBI.connect("DBI:OCI8:" + hostname, user, password)
return connection
end
def self.query(connection, sql)
puts connection
puts sql
begin
request = connection.prepare("#{query}")
request.execute
fetched = []
request.fetch do |row|
fetched << row.to_h
end
request.finish
return fetched
rescue DBI::DatabaseError => e
log "An error occurred"
log "Error code: #{e.err}"
log "Error message: #{e.errstr}"
ensure
end
end
end
So my code that calls this looks like so...
require 'rubygems'
require 'cms_test'
connection = CmsTest.get_dbi_connection('foo', 'bar', 'fubar')
CmsTest.query(connection, "<some sql query>")
So the first argument is a DBI::DatabaseHandle object and the second is some sql query string. When I run that I get this...
`query': wrong number of arguments (0 for 2) (ArgumentError)
This even though the query signature contains two arguments and and I'm passing the method two arguments. The really weird thing for me is that if I put and exit statement anywhere in the method body after the puts it will show that the method did indeed receive 2 arguments...
#<DBI::DatabaseHandle:0x007fa2a316c9f0>
select licensor_id, licensor_name from cf_licensor
I can't make any sense of this. Please help.
You have a method named query:
def self.query(connection, sql)
and then inside query you try to call query:
request = connection.prepare("#{query}")
# -- method call ---------------^^^^^
You probably want to use sql there and there's no need for string interpolation:
request = connection.prepare(sql)

Proper Assert_Raise Unit Testing and Use of Exception Class

I am working on Exercise 49 of Learn Ruby the Hard Way
The exercise asks to write a unit test for each function provided. One of the items I am testing is if a proper exception is raised. It is suggested that we use assert_raise for this purpose.
Here is the code I am testing:
class ParserError < Exception
end
Pair = Struct.new(:token, :word)
def peek(word_list)
begin
word_list.first.token
rescue
nil
end
end
def match(word_list, expecting)
word = word_list.shift
if word.token == expecting
word
else
nil
end
end
def skip_word(word_list, token)
while peek(word_list) == token
match(word_list, token)
end
end
def parse_verb(word_list)
skip_word(word_list, :stop)
if peek(word_list) == :verb
return match(word_list, :verb)
else
raise ParserError.new("Expected a verb next.")
end
end
And here is the test, for the function parse_verb:
def test_parse_verb
list_one = [Pair.new(:verb, 'go'), Pair.new(:noun, 'king')]
assert_equal(parse_verb(list_one), Pair.new(:verb, 'go'))
list_two = [Pair.new(:noun, 'player') ,Pair.new(:verb, 'go'), Pair.new(:noun, 'king')]
assert_raise(ParserError.new("Expected a verb next.")) {parse_verb(list_two)}
end
When I run the test, it fails and here is the message I get:
Larson-2:test larson$ ruby test_sentence.rb
Loaded suite test_sentence
Started
.F..
Finished in 0.001204 seconds.
1) Failure:
test_parse_verb(SentenceTests) [test_sentence.rb:36]:
[#<ParserError: Expected a noun or direction next.>] exception expected, not
Class: <ParserError>
Message: <"Expected a verb next.">
---Backtrace---
/Users/larson/Ruby/projects/ex48/lib/sentence.rb:45:in `parse_verb'
test_sentence.rb:36:in `block in test_parse_verb'
---------------
4 tests, 7 assertions, 1 failures, 0 errors, 0 skips
Test run options: --seed 40627
Based on my understanding of the assert_raise function, this test should pass, is there something wrong with the way I am using it?
If anybody would like a full source code of all the files I am working with I it is available here
assert_raise expects one or more exception classes as its parameters, rather than an instance of the required exception.
It also returns the exception raised so if you want to assert the message (or any other properties) you can do that separately. So try replacing:
assert_raise(ParserError.new("Expected a verb next.")) {parse_verb(list_two)}
with:
exception = assert_raise(ParserError) {parse_verb(list_two)}
assert_equal("Expected a noun or direction next.", exception.message)
For some reason, the answer given above didn't work for me (i'm using Ruby 2.0.0).
I has to wrap the Error class name in a String for it to work:
assert_raise("RuntimeError") {
# some code to trigger the error
}

Resources