Catching line numbers in ruby exceptions - ruby

Consider the following ruby code
test.rb:
begin
puts
thisFunctionDoesNotExist
x = 1+1
rescue Exception => e
p e
end
For debugging purposes, I would like the rescue block to know that the error occurred in line 4 of this file. Is there a clean way of doing that?

p e.backtrace
I ran it on an IRB session which has no source and it still gave relevant info.
=> ["(irb):11:in `foo'",
"(irb):17:in `irb_binding'",
"/usr/lib64/ruby/1.8/irb/workspace.rb:52:in `irb_binding'",
"/usr/lib64/ruby/1.8/irb/workspace.rb:52"]
If you want a nicely parsed backtrace, the following regex might be handy:
p x.backtrace.map{ |x|
x.match(/^(.+?):(\d+)(|:in `(.+)')$/);
[$1,$2,$4]
}
[
["(irb)", "11", "foo"],
["(irb)", "48", "irb_binding"],
["/usr/lib64/ruby/1.8/irb/workspace.rb", "52", "irb_binding"],
["/usr/lib64/ruby/1.8/irb/workspace.rb", "52", nil]
]
( Regex /should/ be safe against weird characters in function names or directories/filenames )
( If you're wondering where foo camefrom, i made a def to grab the exception out :
>>def foo
>> thisFunctionDoesNotExist
>> rescue Exception => e
>> return e
>>end
>>x = foo
>>x.backtrace

You can access the backtrace from an Exception object. To see the entire backtrace:
p e.backtrace
It will contain an array of files and line numbers for the call stack. For a simple script like the one in your question, it would just contain one line.
["/Users/dan/Desktop/x.rb:4"]
If you want the line number, you can examine the first line of the backtrace, and extract the value after the colon.
p e.backtrace[0].split(":").last

Usually the backtrace contains a lot of lines from external gems
It's much more convenient to see only lines related to the project itself
My suggestion is to filter the backtrace by the project folder name
puts e.backtrace.select { |x| x.match(/HERE-IS-YOUR-PROJECT-FOLDER-NAME/) }
And then you can parse filtered lines to extract line numbers as suggested in other answers.

Throwing my $0.02 in on this old thread-- here's a simple solution that maintains all the original data:
print e.backtrace.join("\n")

It is possible that in Ruby 1.9.3 you will be able to get access to not only this information in a more structured, reliable, and simpler way without using regular expressions to cut strings.
The basic idea is to introduce a call frame object which gives access to information about the call stack.
See http://wiki.github.com/rocky/rb-threadframe/, which alas, requires patching Ruby 1.9. In RubyKaigi 2010 (late August 2010) a meeting is scheduled to discuss introducing a frame object into Ruby.
Given this, the earliest this could happen is in Ruby 1.9.3.

Related

How do I delete specific data from a YAML file in Ruby?

FYI I am new to programming :) I am using plain Ruby (not Rails)
I have made a command line app based on some features from The Sims. Users can create Sims (name, gender, life stage, trait) and that info is saved in database.yml.
I am trying to create a method which will allow the user to delete specific Sims from the database. Here's what my YAML file looks like:
---
:id:
:name: Emily
:gender: female
:life_stage: adult
:trait: friendly
---
:id:
:name: Ben
:gender: male
:life_stage: elder
:trait: mean
---
:id:
:name: Josh
:gender: child
:life_stage: adult
:trait: friendly
Here is my Ruby code. (The argument parsed into the method is the name of the Sim to be deleted):
def delete_sim(sim)
log = File.open("../data/database.yml")
YAML::load_stream(log) do |doc|
if sim == doc[:id][:name]
delete.(doc)
puts "You've successfully deleted #{sim}"
else
puts "Error"
break
end
end
end
When I run the code, it displays the else error, which seems to indicate that line 4 isn't right. But what I'm mainly hoping to find out is the correct command to delete data from YAML (line 5). I can't seem to find what I'm looking for in the YAML documentation.
If the solution is obvious to someone here, I'd much appreciate your insight :)
Thanks!
Problem
You have three YAML documents in your file, not a single document. When you load your file as a YAML stream, you actually get a single Array containing three Hash objects. For example:
require 'yaml'
yaml = YAML.load_stream File.read('database.yml')
#=> [{:id=>{:name=>"Emily", :gender=>"female", :life_stage=>"adult", :trait=>"friendly"}}, {:id=>{:name=>"Ben", :gender=>"male", :life_stage=>"elder", :trait=>"mean"}}, {:id=>{:name=>"Josh", :gender=>"child", :life_stage=>"adult", :trait=>"friendly"}}]
yaml.class
#=> Array
yaml.count
#=> 3
yaml.first.class
#=> Hash
You have other problems with your code as well, but understanding the object class that you're working with is really the most important step.
Solution
To remove an item from this type of collection, you'll have to iterate through each element and then write the modified results back out as separate YAML documents. For example:
require 'yaml'
DB = 'database.yml'
def write_database sims
File.open(DB, 'w') { |f| f.puts sims.map(&:to_yaml) }
sims
end
def delete_sim name
sims = YAML.load_stream File.read(DB)
raise ArgumentError, "no sim found: #{name}" if
sims.filter { |sim| sim.dig(:id, :name) == name }.none?
sims.reject! { |sim| sim[:id][:name] == name }
write_database sims
end
delete_sim 'Ben'
#=> [{:id=>{:name=>"Emily", :gender=>"female", :life_stage=>"adult", :trait=>"friendly"}}, {:id=>{:name=>"Josh", :gender=>"child", :life_stage=>"adult", :trait=>"friendly"}}]
delete_sim 'Dave'
#=> ArgumentError (no sim found: Dave)
If you're using this as a database, you should also consider refactoring to a YAML::Store instead of a concatenated stream of YAML documents to take advantage of easier reading and writing, to provide support for transactions, and to handle file locking and prevent race conditions. However, the code above will solve the problem you're currently experiencing.
When I run the code, it displays the else error, which seems to indicate that line 4 isn't right.
Line 4 is correct. However, consider what happens if sim is not 'Emily'?
As soon as the sim that was passed as an argument does not match the name you are looking for, you go into the else branch and then break out of the loop. That means you never look at the second, third, etc. document in your YAML stream.
Either the first sim is the one you are looking for, then you delete it, or the first sim is not the one you are looking for, then you print an error and terminate the loop.
The simplest possible modification I can think of that should fix your code, would be something like this:
def delete_sim(sim)
log = File.open('../data/database.yml')
YAML::load_stream(log) do |doc|
next unless sim == doc[:id][:name]
delete.(doc)
puts "You've successfully deleted #{sim}"
return
end
puts "Error"
end

Metaprogramming Ruby 2 example from the book isn't working when I try it. Trouble shooting assistance

While reading through a chapter of Metaprogramming Ruby 2, I've come across an example in the book that does not seem to work when I execute the code.
array_explorer.rb
def explore_array(method)
code = "['a','b','c'].#{method}"
puts "Evaluating: #{code}"
eval code
end
loop { p explore_array(gets()) }
The code above is designed to illustrate the power of eval. In the next example the book teaches the major flaw of code injections and refactors the code like so to safeguard:
array_explorer.rb
def explore_array(method, *arguments)
['a','b','c'].send(method, *arguments)
end
loop { p explore_array(gets()) }
When I try to run the above code, the file always gives me this error no matter what array method I try to place in.
array_explorer.rb:2:in `explore_array': undefined method `:size (NoMethodError)
' for ["a", "b", "c"]:Array
I've tried taking out the *arguments portion to whittle it down. I tried using a string as input, a symbol as input, etc. This code doesn't work for some reason. Does anyone know why?
gets reads a line from STDIN; a "line" is defined as a string of characters ending with a newline (\n). Thus, you are trying to invoke the method "size\n", which does not exist. Use chomp to get rid of the newline:
loop { p explore_array(gets.chomp) }
It does not matter in the first example, since you are evaluating the code "['a', 'b', 'c'].size\n", which is still valid.

Ruby puts Displaying nil in rescue

I am brand new to Ruby and am coming from a Python background. To help learn the language, I'm porting an existing Python script so I can do a side by side comparison. Thus far, I have a small bit of code and am confused as to why 'nil' prints to the console. Code below:
#!/usr/bin/ruby
require 'date'
backup_dirs = ['/backups/db', '/backups/log']
backup_dirs.each do |backup_dir|
Dir.glob(backup_dir + '/' + '*.*.*.*.*.*').each do |filename|
begin
creation_date = DateTime.strptime(filename.split('.')[3], '%m%d%Y%H%M')
rescue
puts "Skipping #{filename}, invalid file."
end
end
puts 'File is okay.'
end
If the DateTime.strptime method throws an exception, rescue runs and puts prints out the string fine. Except, after it comes a nil. I "googled" and found that puts returns nil. But why does that show only in the rescue area and not in the File is okay. line.
I.e. an example rescue output would be:
Skipping /backups/log/fs.server.dir.999999999999.14.log, invalid file.
nil
And, how do I make it stop displaying that to the console? I'm sure this is a fundamental language thing, just very new to the language so any insight would be much appreciated.
Thanks - Tom
All... sorry. I figured this out. In the actual code I was doing a puts creation_date and that is what was causing the nil to show... as it very well should!
First day learning the language, oops! Did learn a lot about puts though... appreciate all of the answers, sorry for wasting everyones' time.
This is expected behavior. Ruby methods will return the last statement evaluated if return whatever is not specified.
In the sample you have provided you are ending with a puts statement.
puts writes your output then returns nil and subsequently your method returns nil
From irb just run the following two statements to see this in action.
puts 'foo'
'foo'
This is actually because of the #puts method you're calling.
puts(obj, ...) → nil
Writes the given objects to ios as with IO#print. Writes a record separator (typically a
newline) after any that do not already end with a newline sequence. If
called with an array argument, writes each element on a new line. If
called without arguments, outputs a single record separator.
$stdout.puts("this", "is", "a", "test") produces:
this is a test
You can check that in pry/irb:
[9] pry(main)> puts "Hello"
Hello
=> nil

Why should a literal String start out Frozen? (Ruby 2.1)

Following development of Ruby 2.1 I have read about a feature that will probably be added so a developer is allowed to specify that a literal String should start out "frozen".
The syntax looks like this (note the trailing f):
str = "imfrozen"f # str receives a frozen string
In other Ruby documentation/wiki i've read that this feature provides the following benefit:
This allows the VM to use the same String object each time, and
potentially for the same frozen string across many files. It also
obviously provides all the immutability guarantees of a frozen string.
My questions are:
What is the benefit of this?
What is a real world example of when a feature like this would provide value?
How is this different from a symbol ?
Thank you
Suppose you had code like this
array_that_is_very_long.each do |e|
if e == "foo"
...
end
end
In this code, for each iteration over array_that_is_very_long, a new string "foo" is created (and is thrown out), which is a huge waste of resource. Currently, you can overcome this problem by doing:
Foo = "foo"
array_that_is_very_long.each do |e|
if e == Foo
...
end
end
The proposed syntax makes this easier to do as so:
array_that_is_very_long.each do |e|
if e == "foo"f
...
end
end

Using ruby-debug in for i in 0...5

I am learning ruby from 'Programming ruby 1.9'. I am learning to use the ruby-debug so I can understand what is going on underneath. I use rubymine since it integrates ruby-debug19 or something like that (it says I don't have the gem and installs it). Here is the question, I was able to step through the code and explore the variables and the stack. However, when it reaches a for i in 0...5, the debugger says
stack frame not available
I know that ruby don't use for loops much but I'd still like to know if there debug through for loops.
Code:
raw_text = %{
The problem breaks down into two parts. First, given some text as a
string, return a list of words. That sounds like an array. Then, build a
count for each distinct word. That sounds like a use for a hash---we can
index it with the word and use the corresponding entry to keep a count.}
word_list = words_from_string(raw_text)
counts = count_frequency(word_list)
sorted = counts.sort_by {|word, count| count}
top_five = sorted.last(5)
for i in 0...5 # (this is ugly code--read on
word = top_five[i][0] # for a better version)
count = top_five[i][1]
puts "#{word}: #{count}"
end
If you take a look at the Ruby Language Specification (clause 11.5.2.3.4 on p. 91), you will see that
for i in 0...5
word = top_five[i][0]
count = top_five[i][1]
puts "#{word}: #{count}"
end
is syntactic sugar for
(0...5).each do |i|
word = top_five[i][0]
count = top_five[i][1]
puts "#{word}: #{count}"
end
except that no new variable scope is created for the block. So, the code with for will be translated into the code with each and executed as if it were written that way, except that the variables used in the for loop leak into the surrounding scope.
To put it another way: for actually executes each but without allocating a new stack frame for the block. So, the error message is exactly right: there is a call to a block, but somehow there is no stack frame allocated for that block call. That obviously confuses the debugger.
Now, one might argue that this is a bug and that for loops should get special treatment inside the debugger. I guess that so far nobody has ever bothered to fix that bug, since nobody ever uses for loops, precisely because they leak their variables into the surrounding scope and are exactly equivalent to an idiomatic each which doesn't.
What do I mean by "leaking variables"? See here:
(1..2).each do |i|
t = true
end
i
# NameError: undefined local variable or method `i' for main:Object
t
# NameError: undefined local variable or method `t' for main:Object
for i in 1..2
t = true
end
i
# => 2
t
# => true

Resources