Search code examples
rubyerror-handlingrescue

Using rescue and ensure in the middle of code


Still new to Ruby - I've had a look at some of the answers to seemingly similar questions but, to be honest, I couldn't get my head around them.

I have some code that reads a .csv file. The data is split into groups of 40-50 rows per user record and validates data in the rows against a database accessed via a website.

A login is required for each record, but once that user has logged in each row in the .csv file can be checked until the next user is reached, at which point the user logs out.

All that's working, however, if an error occurs (e.g. a different result on the website than the expected result on the .csv file) the program stops.

I need something that will a) at tell me which line on the file the error occurred b) log the row to be output when it's finished running, and iii) restart the program from the next line in the .csv file

The code I have so far is below

Thanks in advance,

Peter

require 'csv-mapper'

loginrequired = true

Given(/^I compare the User Details from rows "(.*?)" to "(.*?)"$/) do |firstrow, lastrow|
  data = CsvMapper.import('C:/auto_test_data/User Data csv.csv') do
    [dln, nino, pcode, endor_cd, ct_cd]
  end

#Row number changed because Excel starts at 'row 1' and Ruby starts counting at 'row 0'
(firstrow.to_i-1..lastrow.to_i-1).each do |row|

  @licnum1 = data.at(row).dln
  @licnum2 = data.at(row+1).dln
  @nino = data.at(row).nino
  @postcode = data.at(row).pcode
  @endor_cd = data.at(row).endor_cd
  @ct_cd = data.at(row).ct_cd

#Login only required once for each new user-account
    if 
      loginrequired == true
      logon_to_vdr #def for this is in hooks
      click_on 'P and D'
      loginrequired = false
      end  

#This is the check against the database and is required for every line in the .csv file
check_ctcd #def for this is in hooks

#Need something in here to log errors and move on to the next line in the .csv file

#Compare the ID for the next record and logout if they're different
    if @licnum1 == @licnum2 
        loginrequired = false
        else
    loginrequired = true`enter code here`
    click_on 'Logout'
    end
  end
end

Solution

  • It seems like you need some error logging since you apparently don't know what type of error you're receiving or where. If this script is standalone you can redirect $stderr to file so that you can read what went wrong.

    # put this line at the top of your script
    $stderr = File.open("/path/to/your/logfile.log","a")
    

    When an error occurs, ruby will automatically write the error message, class, and backtrace to the log file you specify so that you can trace back the line where things are not going as expected. (When you run a script from the command line, normally this information will just get blurted back to the terminal when an error happens.)

    For example, on my desktop I created a file log_stderr.rb with the following (line numbers included):

    1   $stderr = File.open("C:/Users/me/Desktop/my_log.log","w")
    2
    3   #require a file which will raise an error to see the backtrace
    4   require_relative 'raise_error.rb'
    5
    6   puts "code that will never be reached"
    

    Also on my desktop I created the file raise_error.rb with the following (to deepen the backtrace for better example output):

    1   # call raise to generate an error arbitrarily
    2   # to halt execution and exit the program.
    3   raise RuntimeError, 'the program stopped working!'
    

    When I run ruby log_stderr.rb from the command line, my_log.log is created on my desktop with the following:

    C:/Users/me/Desktop/raise_error.rb:3:in `<top (required)>': the program stopped working! (RuntimeError)
        from C:/Users/me/Desktop/log_stderr.rb:4:in `require_relative'
        from C:/Users/me/Desktop/log_stderr.rb:4:in `<main>'
    

    If you are working in a larger environment where your script is being called amidst other scripts then you probably do not want to redirect $stderr because this would affect everything else running in the environment. ($stderr is global as indicated by the $ variable prefix.) If this is the case you would want to implement a begin; rescue; end structure and also make your own logfile so that you don't affect $stderr.

    Again, since you don't know where the error is happening you want to wrap the whole script with begin; end

    # at the very top of the script, begin watching for weirdness
    begin
    logfile = File.open("/path/to/your/logfile.log", "w")
    
    require 'csv-mapper'
    
    #. . .
    
    # rescue and end at the very bottom to capture any errors that have happened
    rescue => e
        # capture details about the error in your logfile
        logfile.puts "ERROR:", e.class, e.message, e.backtrace
    
        # pass the error along since you don't know what it is
        # and there may have been a very good reason to stop the program
        raise e
    end
    

    If you find that your error is happening only in the block (firstrow.to_i-1..lastrow.to_i-1).each do |row| you can place the begin; end inside of this block to have access to the local row variable, or else create a top level variable independent of the block and assign it during each iteration of the block to report to your logfile:

    begin
    logfile = File.open("/path/to/your/logfile.log", "w")
    csv_row = "before csv"
    #. . .
    
    (firstrow.to_i-1..lastrow.to_i-1).each do |row|
        csv_row = row
        #. . .
    end
    csv_row = "after csv"
    
    rescue => e
        logfile.puts "ERROR AT ROW: #{csv_row}", e.class, e.message, e.backtrace
        raise e
    end
    

    I hope this helps!