Working with hashes in ruby on rails - ruby

My apologizes if this has been covered before; I searched and searched but I did not find an answer...
I have the following hash:
input = '{"names":[{"name":"a1","id":1},{"name":"b2","id":2}]}'
I'd like to extract and display the values- one per line. When I run it from rails console, I get the correct results:
>> r1 = ActiveSupport::JSON.decode(input)
=> {"names"=>[{"name"=>"a1", "id"=>1}, {"name"=>"b2", "id"=>2}]}
>> r1["names"].each do |x|
?> puts "#{x["name"]}"
>> end
a1
b2
=> [{"name"=>"a1", "id"=>1}, {"name"=>"b2", "id"=>2}]
THe question is how do I replicate this behavior in my rails application? I tried the following method, but only one value is return in the browser:
module PageHelper
def testcall()
input = '{"names":[{"name":"a1","id":1},{"name":"b2","id":2}]}'
r1 = ActiveSupport::JSON.decode(input)
r1["names"].each do |a|
return "Name: #{a["name"]}\n"
end
end
TIA!

returning terminates the loop after the first element. Try appending the output to a string (including newlines \n in-between) in the loop, and then return that string after the loop.

As #Irfy said, this is happening because of return statement. Following code works,
module PageHelper
def testcall()
input = '{"names":[{"name":"a1","id":1},{"name":"b2","id":2}]}'
r1 = ActiveSupport::JSON.decode(input)
r1["names"].map{|hash| hash["name"]}
end
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 + recursive function + defining global variable

I am pulling bitbucket repo list using Ruby. The response from bitbucket will contain only 10 repositories and a marker for the next page where there will be another 10 repos and so on ... (they call it pagination)
So, I wrote a recursive function which calls itself if the next page marker is present. This will continue until it reaches the last page.
Here is my code:
#!/usr/local/bin/ruby
require 'net/http'
require 'json'
require 'awesome_print'
#repos = Array.new
def recursive(url)
### here goes my net/http code which connects to bitbucket and pulls back the response in a JSON as request.body
### hence, removing this code for brevity
hash = JSON.parse(response.body)
hash["values"].each do |x|
#repos << x["links"]["self"]["href"]
end
if hash["next"]
puts "next page exists"
puts "calling recusrisve with: #{hash["next"]}"
recursive(hash["next"])
else
puts "this is the last page. No more recursions"
end
end
repo_list = recursive('https://my_bitbucket_url')
#repos.each {|x| puts x}
Now, my code works fine and it lists all the repos.
Question:
I am new to Ruby, so I am not sure about the way I have used the global variable #repos = Array.new above. If I define the array in function, then each call to the function will create a new array overwriting its contents from previous call.
So, how do the Ruby programmers use a global symbol in such cases. Does my code obey Ruby ethics or is it something really amateur (yet correct because it works) way of doing it.
The consensus is to avoid global variables as much as possible.
I would either build the collection recursively like this:
def recursive(url)
### ...
result = []
hash["values"].each do |x|
result << x["links"]["self"]["href"]
end
if hash["next"]
result += recursive(hash["next"])
end
result
end
or hand over the collection to the function:
def recursive(url, result = [])
### ...
hash["values"].each do |x|
result << x["links"]["self"]["href"]
end
if hash["next"]
recursive(hash["next"], result)
end
result
end
Either way you can call the function
repo_list = recursive(url)
And I would write it like this:
def recursive(url)
# ...
result = hash["values"].map { |x| x["links"]["self"]["href"] }
result += recursive(hash["next"]) if hash["next"]
result
end

How do I make an array of arrays out of a CSV?

I have a CSV file that looks like this:
Jenny, jenny#example.com ,
Ricky, ricky#example.com ,
Josefina josefina#example.com ,
I'm trying to get this output:
users_array = [
['Jenny', 'jenny#example.com'], ['Ricky', 'ricky#example.com'], ['Josefina', 'josefina#example.com']
]
I've tried this:
users_array = Array.new
file = File.new('csv_file.csv', 'r')
file.each_line("\n") do |row|
puts row + "\n"
columns = row.split(",")
users_array.push columns
puts users_array
end
Unfortunately, in Terminal, this returns:
Jenny
jenny#example.com
Ricky
ricky#example.com
Josefina
josefina#example.com
Which I don't think will work for this:
users_array.each_with_index do |user|
add_page.form_with(:id => 'new_user') do |f|
f.field_with(:id => "user_email").value = user[0]
f.field_with(:id => "user_name").value = user[1]
end.click_button
end
What do I need to change? Or is there a better way to solve this problem?
Ruby's standard library has a CSV class with a similar api to File but contains a number of useful methods for working with tabular data. To get the output you want, all you need to do is this:
require 'csv'
users_array = CSV.read('csv_file.csv')
PS - I think you are getting the output you expected with your file parsing as well, but maybe you're thrown off by how it is printing to the terminal. puts behaves differently with arrays, printing each member object on a new line instead of as a single array. If you want to view it as an array, use puts my_array.inspect.
Assuming that your CSV file actually has a comma between the name and email address on the third line:
require 'csv'
users_array = []
CSV.foreach('csv_file.csv') do |row|
users_array.push row.delete_if(&:nil?).map(&:strip)
end
users_array
# => [["Jenny", "jenny#example.com"],
# ["Ricky", "ricky#example.com"],
# ["Josefina", "josefina#example.com"]]
There may be a simpler way, but what I'm doing there is discarding the nil field created by the trailing comma and stripping the spaces around the email addresses.

`[]': can't convert String into Integer (TypeError) Ruby

I am trying to iterate over a JSON parsed hash table (that has nested Array's of hashes) and insert into a Text Table . The JSON parsed code that I am trying to iterate over is:
{"server"=>{"security_groups"=>[{"name"=>"default"}], "adminPass"=>"LhXEPMkYmqF7", "id"=>"82b7e32b-f62b-4106-b499-e0046250229f", "links"=>[{"href"=>"http://10.30.1.49:8774/v2/89fc0b9d984d49fba5328766e923958f/servers/82b7e32b-f62b-4106-b499-e0046250229f", "rel"=>"self"}, {"href"=>"http://10.30.1.49:8774/89fc0b9d984d49fba5328766e923958f/servers/82b7e32b-f62b-4106-b499-e0046250229f", "rel"=>"bookmark"}], "OS-DCF:diskConfig"=>"MANUAL"}}
The code I am using to iterate over the top is:
server_table = Text::Table.new do | t |
t.head = ['Server ID', 'Server URL', 'Admin Password']
end
response = JSON.parse(r)
response['server'].each do | serv_info |
server_table.rows << [["#{serv_info['id']}", "#{serv_info['links'][0]['href']}", "#{serv_info['adminPass']}"]]
end
puts server_table
I am getting the error:
/lib/get_token.rb:166:in `[]': can't convert String into Integer (TypeError)
from ./lib/get_token.rb:166:in `create_server'
from ./lib/get_token.rb:165:in `each'
from ./lib/get_token.rb:165:in `create_server'
If I individually use puts to print out each command they work fine, but the iteration does not. The commands that pull the correct info are:
puts response['server']['links'][0]['href']
puts response['server']['id']
puts response['server']['adminPass']
All 3 of those work, but if I try and iterate over them I get the string error. I know it has something to do with .each returning an Array of hashes but I do not fully understand why the PUTS command is working without issue in the script and also in IRB.
Any thoughts?
Each serv_info is a pair of a map represented as an array of 2 elements. Therefore everything after << in your code is just wrong.
The secret to avoid such mistakes is to stop trying to obfuscate your own code.
server_table.rows should contain all possible triples of server ID, link and a password.
response = # { "server" => ...}
server = response['server']
server_id = server['id']
link_infos = server['links']
admin_pass = server['adminPass']
link_infos.each do |link_info|
link = link_info['href']
server_table.rows << [server_id, link, admin_pass]
end
Update
We can easily use this code to process multiple servers
response = # [ {"server" => ...}, ...]
response.each do |server|
... # above code snippet goes here
# or you may extract it into a method and call it here
end
Also I want to mention that irb is really great for dealing with this kind of problems. It is a command line Ruby interpreter and it's great for prototyping. It prints out result of each statement you type and has an autocompletion to help you find required classes/methods. Instead of waiting several hours to get an SO answer to simple question you will get it using irb in a couple of minutes.
Perhaps you mean just
serv_info = response['server']
server_table.rows << [["#{serv_info['id']}", "#{serv_info['links'][0]['href']}", "#{serv_info['adminPass']}"]]
Since response['server'] is a hash not an array.
Instead of using:
server_table.rows << [["#{serv_info['id']}", "#{serv_info['links'][0]['href']}", "#{serv_info['adminPass']}"]]
Try:
server_table.rows += [["#{serv_info['id']}", "#{serv_info['links'][0]['href']}", "#{serv_info['adminPass']}"]]
Or:
server_table.rows << ["#{serv_info['id']}", "#{serv_info['links'][0]['href']}", "#{serv_info['adminPass']}"]

trying to find the 1st instance of a string in a CSV using fastercsv

I'm trying to open a CSV file, look up a string, and then return the 2nd column of the csv file, but only the the first instance of it. I've gotten as far as the following, but unfortunately, it returns every instance. I'm a bit flummoxed.
Can the gods of Ruby help? Thanks much in advance.
M
for the purpose of this example, let's say names.csv is a file with the following:
foo, happy
foo, sad
bar, tired
foo, hungry
foo, bad
#!/usr/local/bin/ruby -w
require 'rubygems'
require 'fastercsv'
require 'pp'
FasterCSV.open('newfile.csv', 'w') do |output|
FasterCSV.foreach('names.csv') do |lookup|
index_PL = lookup.index('foo')
if index_PL
output << lookup[2]
end
end
end
ok, so, if I want to return all instances of foo, but in a csv, then how does that work?
so what I'd like as an outcome is happy, sad, hungry, bad. I thought it would be:
FasterCSV.open('newfile.csv', 'w') do |output|
FasterCSV.foreach('names.csv') do |lookup|
index_PL = lookup.index('foo')
if index_PL
build_str << "," << lookup[2]
end
output << build_str
end
end
but it does not seem to work
Replace foreach with open (to get an Enumerable) and find:
FasterCSV.open('newfile.csv', 'w') do |output|
output << FasterCSV.open('names.csv').find { |r| r.index('foo') }[2]
end
The index call will return nil if it doesn't find anything; that means that the find will give you the first row that has 'foo' and you can pull out the column at index 2 from the result.
If you're not certain that names.csv will have what you're looking for then a bit of error checking would be advisable:
FasterCSV.open('newfile.csv', 'w') do |output|
foos_row = FasterCSV.open('names.csv').find { |r| r.index('foo') }
if(foos_row)
output << foos_row[2]
else
# complain or something
end
end
Or, if you want to silently ignore the lack of 'foo' and use an empty string instead, you could do something like this:
FasterCSV.open('newfile.csv', 'w') do |output|
output << (FasterCSV.open('names.csv').find { |r| r.index('foo') } || ['','',''])[2]
end
I'd probably go with the "complain if it isn't found" version though.

Resources