Ruby - no implicit conversion of Array into String - ruby

I am getting an error when executing my test.
Failure/Error: expect(industry_sic_code).to include page.sic_code
TypeError:
no implicit conversion of Array into String
# ./spec/os/bal/company/company_filter_clean_harbors_industries_stub.rb:62:in `block (2 levels) in <top (required)>'
The Method:
def sic_code
subtables = #b.table(:class => 'industry-codes').tables(:class => 'industry-code-table')
subtables.each do |subtable|
if subtable.tbody.h4.text == "US SIC 1987:"
subtable.tr.next_siblings.each do |tr|
codes = tr.cell
puts codes.text.to_s
end
end
end
end
The Test:
it 'Given I search for a random Clean Harbors Industry' do
#Pick a random clean industry from the file
data = CSV.foreach(file_path, headers: true).map{ |row| row.to_h }
random = data.sample
random_industry = random["Class"]
industry_sic_code = random["SIC Code"]
end
it 'Then the result has the expected SIC code' do
page = DetailPage.new(#b)
page.view
expect(industry_sic_code).to include page.sic_code
end
I have tried to implicitly change each variable to a string but it still complain about the array issue.
When I include some puts statments, I get some really wonky responses. The method itself returns the expected result.
When I used the method in the test I end up with the code gibberish below.
here are the sic codes from the method
5511
Here are the codes from the test
#<Watir::Table:0x00007fa3cb23f020>
#<Watir::Table:0x00007fa3cb23ee40>
#<Watir::Table:0x00007fa3cb23ec88>
#<Watir::Table:0x00007fa3cb23ead0>
#<Watir::Table:0x00007fa3cb23e918>
#<Watir::Table:0x00007fa3cb23e738>
#<Watir::Table:0x00007fa3cb23e580>

Your sic_code method returns subtables array, that's why you have this error. It doesn't matter that the method puts something, every method in ruby implicitly returns result of its last line, in your case it is subtables.each do ... end, so you have an array.
You need to explicitly return needed value. Not sure if I correctly understood what are you doing in your code, but try something like this:
def sic_code
subtables = #b.table(:class => 'industry-codes').tables(:class => 'industry-code-table')
result = [] # you need to collect result somewhere to return it later
subtables.each do |subtable|
if subtable.tbody.h4.text == "US SIC 1987:"
subtable.tr.next_siblings.each do |tr|
codes = tr.cell
result << codes.text.to_s
end
end
end
result.join(', ')
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

Ruby - reading from .csv and creating objects out of it

I have .csv file with rows of which every row represents one call with certain duration, number etc. I need to create array of Call objects - every Call.new expects Hash of parameters, so it's easy - it just takes rows from CSV. But for some reason it doesn't work - when I invoke Call.new(raw_call) it's nil.
It's also impossible for me to see any output - I placed puts in various places in code (inside blocks etc) and it simply doesn't show anything. I obviously have another class - Call, which holds initialize for Call etc.
require 'csv'
class CSVCallParser
attr_accessor :io
def initialize(io)
self.io = io
end
NAMES = {
a: :date,
b: :service,
c: :phone_number,
d: :duration,
e: :unit,
f: :cost
}
def run
parse do |raw_call|
parse_call(raw_call)
end
end
private
def parse_call(raw_call)
NAMES.each_with_object({}) do |name, title, memo|
memo[name] = raw_call[title.to_s]
end
end
def parse(&block)
CSV.parse(io, headers: true, header_converters: :symbol, &block)
end
end
CSVCallParser.new(ARGV[0]).run
Small sample of my .csv file: headers and one row:
"a","b","c","d","e","f"
"01.09.2016 08:49","International","48627843111","0:29","","0,00"
I noticed a few things that isn't going as expected. In the parse_call method,
def parse_call(raw_call)
NAMES.each_with_object({}) do |name, title, memo|
memo[name] = raw_call[title.to_s]
end
end
I tried to print name, title, and memo. I expected to get :a, :date, and {}, but what I actually got was [:a,:date],{}, and nil.
Also, raw_call headers are :a,:b,:c..., not :date, :service..., so you should be using raw_call[name], and converting that to string will not help, since the key is a symbol in the raw_call.
So I modified the function to
def parse_call(raw_call)
NAMES.each_with_object({}) do |name_title, memo|
memo[name_title[1]] = raw_call[name_title[0]]
end
end
name_title[1] returns the title (:date, :service, etc)
name_title[0] returns the name (:a, :b, etc)
Also, in this method
def run
parse do |raw_call|
parse_call(raw_call)
end
end
You are not returning any results you get, so you are getting nil,
So, I changed it to
def run
res = []
parse do |raw_call|
res << parse_call(raw_call)
end
res
end
Now, if I output the line
p CSVCallParser.new(File.read("file1.csv")).run
I get (I added two more lines to the csv sample)
[{:date=>"01.09.2016 08:49", :service=>"International", :phone_number=>"48627843111", :duration=>"0:29", :unit=>"", :cost=>"0,00"},
{:date=>"02.09.2016 08:49", :service=>"International", :phone_number=>"48622454111", :duration=>"1:29", :unit=>"", :cost=>"0,00"},
{:date=>"03.09.2016 08:49", :service=>"Domestic", :phone_number=>"48627843111", :duration=>"0:29", :unit=>"", :cost=>"0,00"}]
If you want to run this program from the terminal like so
ruby csv_call_parser.rb calls.csv
(In this case, calls.csv is passed in as an argument to ARGV)
You can do so by modifying the last line of the ruby file.
p CSVCallParser.new(File.read(ARGV[0])).run
This will also return the array with hashes like before.
csv = CSV.parse(csv_text, :headers => true)
puts csv.map(&:to_h)
outputs:
[{a:1, b:1}, {a:2, b:2}]

Ruby return with double quotes

Hi I have a string passed back from rspec.
It should show
"alias/public_html/ab1/ab2/"
but I am getting "\"alias/public_html/ab1/ab2/\""
I am getting the rspec error below:
WebServer::HttpdConf#alias_path returns the aliased path
Failure/Error: expect(httpd_file.alias_path('/ab/')).to eq 'alias/public_html/ab1/ab2/'
expected: "alias/public_html/ab1/ab2/"
got: "\"alias/public_html/ab1/ab2/\""
(compared using ==)
# ./spec/lib/config/httpd_conf_spec.rb:90:in `(root)'
And here is my actual program file
def alias_path(path)
#hash_httpd['Alias'][path]
end
Please help
EDIT
Sorry, I am new to RUby, here is the httpd_file
def initialize(httpd_file_content)
#hash_httpd = Hash.new
httpd_file_content.each_line do | line |
#commands = line.split
if #commands.length == 2
#hash_httpd[#commands[0]] = #commands[1]
else
if !#hash_httpd.has_key?(#commands[0])
al = Hash.new
#hash_httpd[#commands[0]] = al
else
al = #hash_httpd[#commands[0]]
end
al[#commands[1]] = #commands[2]
end
end
end
If you are sure that your alias_path output will be "alias/public_html/ab1/ab2/", then you can just modify your alias_path method definition by removing the quotes (if any) from the returned path:
def alias_path(path)
#hash_httpd['Alias'][path].gsub('"', '')
end

RSpec 2 and 3 How to create a helper that will generate a bunch of examples?

I'm trying to create helper to test JSON responses in uniform and nice way.
For more descriptive and specific failures I want to generate one example per JSON atom.
Example syntax:
RSpec.describe "some JSON API View" do
setup_objects
before { render }
describe "response" do
subject { rendered }
it_conforms_to_json(
id: 27,
email: "john.smith#example.com",
name: "John",
profile_description: %r{professional specialist},
nested: {
id: 37,
status: "rejected"
}
)
end
end
So this snippet will be an equivalent to:
RSpec.describe "some JSON API View" do
setup_objects
before { render }
describe "response" do
subject { rendered }
it 'has equal value at object["id"]' do
expect(subject["id"]).to eq(27)
end
it 'has equal value at object["email"]' do
expect(subject["email"]).to eq("john.smith#example.com")
end
it 'has equal value at object["name"]' do
expect(subject["name"]).to eq("John")
end
it 'has matching value at object["profile_description"]' do
expect(subject["profile_description"]).to match(%r{professional specialist})
end
it 'has equal value at object["nested"]["id"]' do
expect(subject["nested"]["id"]).to eq(37)
end
it 'has equal value at object["nested"]["status"]' do
expect(subject["nested"]["status"]).to eq("rejected")
end
end
end
I was able to achieve that easily with this snippet:
module JsonHelper
extend self
def it_conforms_to_json(json)
generate_examples_for(json)
end
private
def generate_examples_for(json, opts)
with_prefix = opts.fetch(:with_prefix, [])
if json.is_a?(Hash)
json.each do |key, new_json|
new_prefix = with_prefix + [key.to_s]
generate_examples_for(new_json, opts.merge(with_prefix: new_prefix))
end
elsif json.is_a?(Array)
raise NotImplemented.new("Arrays are not allowed yet")
elsif json.is_a?(String) || json.is_a?(Numeric)
it "is expected to have equal value at json[\"#{with_prefix.join('"]["')}\"]" do
value = JSON.parse(subject)
with_prefix.each { |key| value = value[key.to_s] }
expect(value).to eq(json)
end
end
end
end
And just enabling it by extending it: rspec_config.extend JsonHelper
Obvious problem starts when you think about pretty failure messages:
They show backtrace including location of it "is expected...bla-bla-bla
They exclude location of it_conforms_to_json(...) call
First is fixable by adding backtrace exclusion/clean pattern, but it results in a full backtrace because everything is filtered.
Second and previous is fixable by wrapping expect statement in begin..rescue, mangling with backtrace by prepending it with file_path:line of it_conforms_to_json(...) call and re-raising modified exception.
After first 2 problems are being resolved, the new one arises:
"Unable to find matching line from the backtrace"
Suspected method is find_first_non_rspec_line (or something similar), that finds first line in a backtrace of it call (not exception backtrace) by applying default rspec exclusion regex'es.
It is fixable by mangling with RSpec internals, i.e.:
For rspec 2: monkey patch method that applies this regex'es
For rspec 3: undefine constant LIB_REGEX and define it again adding this helper files to this regex
For rspec 3 (recent minor/patch versions): the same, but with IGNORE_REGEX
Code of this fixup becomes shitty and unreadable, and it will be hell to maintain, because it depends on rspec inner implementation, that can change from minor/patch version to version. Who interested in reading this ugly thing here
More that that, it acts differently for rspec 2 and rspec 3.
rspec 2, perfectly what is required:
Failures:
1) A json response is expected to have equal value at json["id"]
Failure/Error: it_conforms_to_json(
expected: 37
got: 25
(compared using ==)
# ./spec/simple_with_fail_spec.rb:10:in `block in <top (required)>'
2) A json response is expected to have equal value at json["name"]
Failure/Error: it_conforms_to_json(
expected: "Smith"
got: "John"
(compared using ==)
# ./spec/simple_with_fail_spec.rb:10:in `block in <top (required)>'
rspec 3, slightly off, but still acceptable:
Failures:
1) A json response is expected to have equal value at json["id"]
Failure/Error: expect(value).to eq(json)
expected: 37
got: 25
(compared using ==)
# ./spec/simple_with_fail_spec.rb:10:in `block in <top (required)>'
# ./lib/rspec/json_expectations/expectations.rb:32:in `block in generate_examples_for'
2) A json response is expected to have equal value at json["name"]
Failure/Error: expect(value).to eq(json)
expected: "Smith"
got: "John"
(compared using ==)
# ./spec/simple_with_fail_spec.rb:10:in `block in <top (required)>'
# ./lib/rspec/json_expectations/expectations.rb:32:in `block in generate_examples_for'
So here are the questions:
Is there any built-in public-API means of achieving it?
It seems I have problems with naming here, it is not expectations, but what...?
So, it is a bit ugly now (first iteration, though), but works. No mangling with RSpec internals, using only public RSpec API, with nice error messages, without even cleaning a backtrace:
module RSpec
module JsonExpectations
class JsonTraverser
def self.traverse(errors, expected, actual, prefix=[])
if expected.is_a?(Hash)
expected.map do |key, value|
new_prefix = prefix + [key]
if actual.has_key?("#{key}")
traverse(errors, value, actual["#{key}"], new_prefix)
else
errors[new_prefix.join("/")] = :no_key
false
end
end.all?
elsif expected.is_a?(String) || expected.is_a?(Numeric)
if actual == expected
true
else
errors[prefix.join("/")] = {
actual: actual,
expected: expected
}
false
end
else
raise NotImplementedError, "#{expected} expectation is not supported"
end
end
end
end
end
RSpec::Matchers.define :include_json do |expected|
match do |actual|
unless expected.is_a?(Hash)
raise ArgumentError, "Expected value must be a json for include_json matcher"
end
RSpec::JsonExpectations::JsonTraverser.traverse(
#include_json_errors = {},
expected,
JSON.parse(actual)
)
end
# RSpec 2 vs 3
send(respond_to?(:failure_message) ?
:failure_message :
:failure_message_for_should) do |actual|
res = []
#include_json_errors.each do |json_path, error|
res << %{
json atom on path "#{json_path}" is missing
} if error == :no_key
res << %{
json atom on path "#{json_path}" is not equal to expected value:
expected: #{error[:expected].inspect}
got: #{error[:actual].inspect}
} if error.is_a?(Hash) && error.has_key?(:expected)
end
res.join("")
end
end
It allows this syntax now:
it "has basic info about user" do
expect(subject).to include_json(
id: 37,
email: "john.smith#example.com",
name: "Smith"
)
end
It generates errors like this:
F
Failures:
1) A json response has basic info about user
Failure/Error: expect(subject).to include_json(
json atom on path "id" is not equal to expected value:
expected: 37
got: 25
json atom on path "name" is not equal to expected value:
expected: "Smith"
got: "John"
# ./spec/simple_with_fail_spec.rb:11:in `block (2 levels) in <top (required)>'
Finished in 0.00065 seconds
1 example, 1 failure
Failed examples:
rspec ./spec/simple_with_fail_spec.rb:10 # A json response has basic info about user
Works for both RSpec 2 and 3.
If somebody interested it is here https://github.com/waterlink/rspec-json_expectations

What's causing this Ruby "can't convert Mongo::Cursor into Integer" error?

I am using the Mongo Ruby driver and have this block of Ruby code before and after line 171 in my code, which is apparently the source of the error below it (the query.each line is line 171):
query = get_callback.call( "databases", id )
if query.count > 0
puts query.count.inspect + " results: " + query.inspect
res = {}
query.each do |result|
puts result.inspect
end
else
puts "No results" + res.inspect
res = {}
end
The error:
1 results: <Mongo::Cursor:0x3fc15642c154 namespace='myproj.databases' #selector={"_id"=>BSON::ObjectId('4fe120e4a2f9a386ed000001')} #cursor_id=>
TypeError - can't convert Mongo::Cursor into Integer:
/Users/myuser/.rvm/gems/ruby-1.9.3-p194/gems/bson-1.6.4/lib/bson/byte_buffer.rb:156:in `pack'
/Users/myuser/.rvm/gems/ruby-1.9.3-p194/gems/bson-1.6.4/lib/bson/byte_buffer.rb:156:in `put_int'
/Users/myuser/.rvm/gems/ruby-1.9.3-p194/gems/mongo-1.6.4/lib/mongo/cursor.rb:603:in `construct_query_message'
/Users/myuser/.rvm/gems/ruby-1.9.3-p194/gems/mongo-1.6.4/lib/mongo/cursor.rb:466:in `send_initial_query'
/Users/myuser/.rvm/gems/ruby-1.9.3-p194/gems/mongo-1.6.4/lib/mongo/cursor.rb:459:in `refresh'
/Users/myuser/.rvm/gems/ruby-1.9.3-p194/gems/mongo-1.6.4/lib/mongo/cursor.rb:128:in `next'
/Users/myuser/.rvm/gems/ruby-1.9.3-p194/gems/mongo-1.6.4/lib/mongo/cursor.rb:291:in `each'
/Users/myuser/Code/myproj/my_file.rb:171:in `block in initialize'
My query object: {"_id"=>BSON::ObjectId('4fe120e4a2f9a386ed000001')}
I have not the faintest idea what's causing this. I've verified the object I'm finding exists and the query.count shows that there's a result in my Mongo::Cursor.
I've not found any examples of the issue on Google and every Mongo/Ruby on the web I've found uses an each iterator just like I do. Anyone know what's the cause of this error? I notice I also get it when trying to use to_a to cast the collection to a JSON-usable object.
For what it's worth, here's the relevant part of byte_buffer.rb is below. The line with << is line 156.
def put_int(i, offset=nil)
#cursor = offset if offset
if more?
#str[#cursor, 4] = [i].pack(#int_pack_order)
else
ensure_length(#cursor)
#str << [i].pack(#int_pack_order)
end
#cursor += 4
end
This happens when you pass nil to a Ruby Mongo driver limit() method.

Resources