Using .match() inside of a block - ruby

I want to read a file and create an array of twitter handles. The file is a random collection of trending tweets, a snippet would be:
#David_Cameron #britishchambers #BCCConf Mention of pay rises in the
NHS. But what about all the redundancies to enable these pay rises?
in which case the code should return
["#David_Cameron", "#britishchambers"]
Placing /#\w+/.match(word)[0] works in irb, but as soon as I put it in a block with each or map:
def read_file(file_path)
text = File.open(file_path, 'r'){ |f| f.read}.split(" ")
text.each{|word| /#\w+/.match(word)[0] }
end
then I receive the error:
NoMethodError: undefined method `[]' for nil:NilClass
What am I doing wrong? Also, if I can do this inside the file.open block, that would be preferable.

What am I doing wrong?
By placing [] after /#\w+/.match(word), you are assuming that word always matches /#\w+/, thereby returning a MatchData object, which is not true. For example #BCCConf, does not match /#\w+/, in which case /#\w+/.match(word) is nil. [] is not defined on nil.

def read_file(file_path)
text = File.read(file_path)
text.scan(/#\w+/).flatten
end
Read the file, then use #scan to extract all the occurrences.

Related

Variable does not equal variable when it should in ruby

I am making my first web server, but When i use the if statement to compare user input from html file with a string, they just skip it even if it is true.
require 'socket'
require 'pry'
class Parser
def parse(req, clients)
save = File.new("requests.txt", "w+")
save.write(req.chomp)
save.close
words = IO.readlines("requests.txt").last(1)
if words == "Username=test&Password=1234"
success(clients)
end
#binding.pry
puts words
end
end
def success(succ_client)
success_file = File.read("templates/success.html")
stuff = "HTTP/1.1 200\r\n" + success_file.force_encoding('UTF-8')
succ_client.puts(stuff)
end
def response(cli)
file = File.read("templates/passpage.html")
content = "HTTP/1.1 200\r\n" + file.force_encoding('UTF-8')
cli.puts(content)
end
serv_sock = TCPServer.new('10.0.2.15', 8080)
loop {
client = serv_sock.accept()
requests = Parser.new
requests.parse(client.readpartial(2043), client)
response(client)
client.close
puts "Connected"
}
I tried using #compact!, nil?, and using pry to decode to find whats the issue, but I just cant find the problem, when i puts the words variable it puts the correct value but its just not the right one I guess. I tried decoding the words but that still didn't work unless i did it wrong.
It has been 5 days on this problem and this is my first ruby program, and web-server, So ill appreciate any help I can get with this to move forward in life.
There are several issues with your code.
Primarily, your call to IO.readlines("requests.txt").last(1) returns an Array of zero or one strings. However, you are comparing it to a single string. Since the string is not equal to the array, the comparison fails.
Likely, you want to use IO.readlines("requests.txt").last instead. This returns the last element or the array or nil if the array returned by the readlines method is empty.
Please have a look at the documentation of the Array#last method to learn more about the returned types of this method.
In addition to that, even if your if statement eventually matches, your intended call to the success method fill fail, because you have defined it in the global main object, rather than your Parser class.
Also, when you eventually call success from your parse method, you will be returning two responses as you also call the response method later. You may want to rethink the way you select the correct responses...

Ruby's `find` method won't work on code block that returns an array. Gets a NilClass error

My question involves Ruby's find method and why it works in one situation but not in another. NOTE: I'm using this method in a Cucumber step definition written in Ruby.
This code block works the way I want it to:
Then(/^I expect the bolt service children for CrossrefDepositsDaf to include only: IngestUpdateWorkflow$/) do
#boltworkflowservice ||= BoltWorkflowService.new
Watir::Wait.until(60) do
#job_family = #boltworkflowservice.get_workflows_job_family(#job_id).json
#job_family.find { |job| job['type'] == "IngestUpdateWorkflow" }
end
expect(#job_family.find{|job| job['type'] == "IngestUpdateWorkflow"}.nil?).to eq(false), "IngestUpdateWorkflow child not found"
end
But I wanted to rewrite this code block to get rid of the instance variable and reduce the block by one line. I tried the following:
Then(/^I expect the bolt service children for CrossrefDepositsDaf to include only: IngestUpdateWorkflow$/) do
#boltworkflowservice ||= BoltWorkflowService.new
Watir::Wait.until(60) do
puts #boltworkflowservice.get_workflows_job_family(#job_id).json
#boltworkflowservice.get_workflows_job_family(#job_id).json.find {|job| job['type'] == "IngestUpdateWorkflow"}
end
expect(#job_family.find{|job| job['type'] == "IngestUpdateWorkflow"}.nil?).to eq(false), "IngestUpdateWorkflow child not found"
end
I just added the puts to see what the code would return. According to the puts, it returns an array that contains three hash objects but I still get an error. Here is what I get when I try to run the second block:
[{"id"=>914295, "type"=>"CrossrefDepositsDaf", "subType"=>"", "parentId"=>0, "state"=>"SUCCESS", "subState"=>"SUCCESS", "dataPointer"=>"s3://sequoia-install/app-resources/test.cirrostratus.org/workflows/CrossrefDepositsDaf/2015/7/29/148c7ebc-3da4-4650-a22e-2e8962d448fc", "created"=>"2015-07-29 01:58:14", "metadata"=>[]}, {"id"=>914296, "type"=>"CrossRefDeposits", "subType"=>"BoltCatDaf", "parentId"=>914295, "state"=>"SUCCESS", "subState"=>"SUCCESS", "dataPointer"=>"s3://sequoia-install/app-resources/test.cirrostratus.org/workflows/CrossRefDeposits/2015/7/29/3a703736-d4a2-456e-baa9-7bf2c0f4a0e2", "created"=>"2015-07-29 01:58:17", "metadata"=>[]}, {"id"=>914297, "type"=>"IngestUpdateWorkflow", "subType"=>"CrossRefDeposits", "parentId"=>914296, "state"=>"SUCCESS", "subState"=>"CREATED", "dataPointer"=>"s3://sequoia-install/app-resources/test.cirrostratus.org/workflows/IngestUpdateWorkflow/2015/7/29/9282591b-f93c-49d6-b656-4afbd14156ef", "created"=>"2015-07-29 01:58:23", "metadata"=>[]}]
NoMethodError: undefined method `find' for nil:NilClass
./features/step_definitions/catdaf_integration_crossref_deposits_steps.rb:42:in `/^I expect the bolt service children for CrossrefDepositsDaf to include only: IngestUpdateWorkflow$/'
C:\Users\Dugan23\workspace\cloudy_clouds\cucumber\features\content_management\daf\integration\crossref_deposits.feature:11:in `And I expect the bolt service children for CrossrefDepositsDaf to include only: IngestUpdateWorkflow'
1 scenario (1 failed)
6 steps (1 failed, 5 passed)
9m30.221s
Process finished with exit code 1
Can somebody please explain to me why the second block of code doesn't work? The puts shows that I should be feeding the find method an array so why does it say that I"m trying to run the find method on a nil:NilClass. Thank you in advance for any and all of your help.
NOTE: When I added a puts statement to the first block of code to print out #job_family it returned the same exact array as the second block of code. Does this mean that in the second block of code Ruby is trying to run the find method before evaluating the code before it? I tried to remedy this by adding parentheses as follows, but to no avail:
(#boltworkflowservice.get_workflows_job_family(#job_id).json).find {|job| job['type'] == "IngestUpdateWorkflow"}
In the second version, you have avoided using #job_family inside the block, but still you are referring to it on the expect, which obviously would be nil unless you have defined it elsewhere

Understanding strange output in multidimensional array

I am new to ruby and I was trying to iterate over a 2d array. I made a mistake in my code. From that mistake, I noticed some unexpected output.
s = [["ham", "swiss"], ["turkey", "cheddar"], ["roast beef", "gruyere"]]
i = 0;
s.each{
|array| so = array[i] # pin
puts so[i]
}
Due to #pin, if i = 0, output is h t r. i = 1 gives w h r. i > 1 gives an error:
C:/Ruby.rb in `block in <main>': undefined method `[]' for nil:NilClass (NoMethodError)
from C:/Ruby.rb:3:in `each'
from C:/Ruby.rb:3:in `<main>'
If I use |array| so = array # pin, then my code does not produce strange output. I'll just fix the remaining stuff to make my code iterate for all values that 'i' can have.
Please explain this.
PS: Working code is here
s = [["ham", "swiss"], ["turkey", "cheddar"], ["roast beef", "gruyere"]]
s.each{
|array| so = array
array.each{
|str| puts str
}
}
For each type of sandwich, when i is 0, so is the 1st element, which is the meat. so[0] is the first letter of the meat.
When i is 1, which is the 2nd element, which is the cheese. so[1] is the second letter of the cheese.
When i is 3, there is no third component to the sandwich. so so is nil. so[2] is asking for the nil[2].
nil is a class, like everything in ruby. But nil does not implement the [] method, as arrays and other classes that implement the Enumerable module do.
Since nil does not support the [] method, then you get the undefined method error.
Even operations that are built into other languages, like +, [], and == are methods that can be overridden in Ruby.
To understand exactly what's happening, try this bit of code:
class NilClass
def [] (i)
nil
end
end
Executing that will open up the existing NilClass, and add a method called []. Then you can do nil[1] and it will return nil. Changing an existing class like this is known as monkey patching in the Ruby world.
When you ask for so[2] you are actually asking for the third element, and if it doesn't exist, you'll get an error.
I recommend structuring your blocks like so:
s.each do |pair|
puts pair
end
Note the do/end instead of {} and the placement of the iteration variable inline with the each. Also note that this is equivalent to your "working code" so you don't need the extra iterator in this case.

Performing same action for each value in array in Ruby

I have a text file of stock symbols, each symbol is on its own line. In Ruby, I have created an array from the text file like so:
symbols = []
File.read('symbols.txt').each_line do |line|
symbols << line.chop!
end
For each symbol in the array, I want to read from a json file (ex. MSFT.json) and perform a number of calculations (all of that is now working) and then do the same thing for the next symbol in the array.
When attempting to "call" and perform calculations on the first item in the array I did this:
json = File.read("#{symbols[0]}.json")
#...calculations come after this
This worked fine, and it did run through the whole program for the first symbol, but of course doesn't go on to perform the same steps for the remaining symbols (I know thats because I specified an index in the array].
Now that I know that the program works for a single symbol, I now want it to run on all the symbols in the array...so after the first block, I tried adding: symbols.each do, and removed the [0] from the File.read line (and added end at the end of the calculations). I was hoping it would loop through everything between the "do" and "end" for each symbol. That didn't work.
Then I tried adding this after the first block:
def page(symbols, i)
page[i]
end
And changing the File.read line to: json = File.read("#{page[i]}.json)
But that didn't work either.
Any help is appreciated. Thanks a lot
You can simply use .each instead of an iterator index:
symbols.each do |symbol|
json = File.read("#{symbol}.json")
# do some calculation for symbol
end
No need to iterate twice:
open('symbols.txt').lines.each do |line|
symbol = line.strip
json = File.read("#{symbol}.json")
# process json
end

Ruby - Calling a method from a loop within another method

Here's the problem...
I have a method that I'm calling to strip out characters and convert strings to floats.
def convert_to_float(currency)
return currency.gsub(/regex/, "").to_f
end
I have another method that is receiving string values. What I'm wanting to do is iterate those received strings through the convert_to_float method instead of applying the gsub to each line. Here is what I've got... is this even possible with the way I'm doing this?
def verify_amounts(total,subtotal,tax)
arrayoftotals = [total,subtotal,tax]
arrayoftotals.each do |convert_to_float|
end
ftotal = arrayoftotals[0]
raise "ftotal must be a Float" unless ftotal.kind_of? Float
end
So far its raising the fault stating that the type is not a float which is telling me that the do each loop isn't converting the values.
Help.
Thanks!!!
Sounds like you're looking for map:
arrayoftotals = [total, subtotal, tax].map { |x| convert_to_float(x) }
or, since convert_to_float is a method in the same class as verify_amounts, you could use the Object#method method to write it like this:
arrayoftotals = [total, subtotal, tax].map(&method(:convert_to_float))
For example, this:
class Pancakes
def convert_to_float(currency)
currency.gsub(/[^\d.]/, '').to_f
end
def verify_amounts(total, subtotal, tax)
arrayoftotals = [total, subtotal, tax].map(&method(:convert_to_float))
puts arrayoftotals.inspect
end
end
Pancakes.new.verify_amounts('where1.0', '2.is0', '3.0house')
will give you [1.0, 2.0, 3.0] on the standard output.
Upon closer inspection there are two things going wrong here.
Your syntax to pass a method as an iterative function is wrong.
arrayoftotals.each do |convert_to_float|
end
Works out to be an empty block where the local variable is called convert_to_float. The syntax you're looking for is:
arrayoftotals.each (&method (:convert_to_float))
This passes a Proc object referring to the method convert_to_float as your block.
You are not updating the values within arrayoftotals. So even if convert_to_float was being called, it wouldn't do anything.
Either change gsub to gsub! to destructively sanitize your strings in place, or use map! instead of each to replace each element in the array with the results of calling the function on it. map! is a better choice because it means you won't have to adjust every other usage of convert_to_float.
Putting it all together:
def convert_to_float(currency)
return currency.gsub(/regex/, "").to_f
end
def verify_amounts(total,subtotal,tax)
arrayoftotals = [total,subtotal,tax]
arrayoftotals.map! (&method (:convert_to_float)
ftotal = arrayoftotals[0]
raise "ftotal must be a Float" unless ftotal.kind_of? Float
end

Resources