Ruby Exception Handling

Ruby Exception Handling: NotImplementedError

Next on the docket for our Ruby Exception Handling series, we’re taking a look at the NotImplementedError exception class. NotImplementedError is a subclass descendant of the ScriptError superclass, and occurs when Ruby attempts to utilize a feature or method that isn’t implemented for the current platform or Ruby installation.

In this post we’ll explore the NotImplementedError class, examining where it lands within Ruby’s Exception class hierarchy, how to handle NotImplementedErrors, and best practices to avoid this exception from appearing in the first place!

The Technical Rundown

  • All Ruby exceptions are descendants of the Exception class, or a subclass therein.
  • ScriptError is a direct descendant of the Exception class.
  • NotImplementedError is a direct descendant of the ScriptError class.

When Should You Use It?

Ruby will raise a NotImplementedError whenever it encounters a reference to a method or function that cannot be executed within the current system environment. This is particularly common when executing Ruby in a non-Unix environment, such as Windows, due to the inherit limitations that Windows deals with.

For example, attempting to utilize the fork method within most Windows development environments will result in a NotImplementedError:

def print_exception(exception, explicit)
    puts "[#{explicit ? 'EXPLICIT' : 'INEXPLICIT'}] #{exception.class}: #{exception.message}"
    puts exception.backtrace.join("\n")
end

begin
    pid = Process.fork { sleep 0.5 }
rescue NotImplementedError => e
    print_exception(e, true)
end

We’ve added our basic print_exception() function to cleanup the output from the rescued Exception, but otherwise the code is very simple as we try to utilize the Process.fork() method. As expected, we receive a NotImplementedError output:

[EXPLICIT] NotImplementedError: fork() function is unimplemented on this machine

Depending on the environment you’re executing Ruby within, a plethora of Process methods will run into similar NotImplementedError issues. Process.groups is just one other example:

def print_exception(exception, explicit)
    puts "[#{explicit ? 'EXPLICIT' : 'INEXPLICIT'}] #{exception.class}: #{exception.message}"
    puts exception.backtrace.join("\n")
end

begin
    groups = Process.groups
rescue NotImplementedError => e
    print_exception(e, true)
end

This produces another EXPLICIT (or expected) NotImplementedError:

[EXPLICIT] NotImplementedError: groups() function is unimplemented on this machine

Additionally, since NotImplementedError is a direct descendant of ScriptError, when explicitly rescuing a particular exception class type, we can go up another level and rescue ScriptError instead, which also catches our NotImplementedError:

def print_exception(exception, explicit)
    puts "[#{explicit ? 'EXPLICIT' : 'INEXPLICIT'}] #{exception.class}: #{exception.message}"
    puts exception.backtrace.join("\n")
end

begin
    pid = Process.fork { sleep 0.5 }
rescue ScriptError => e
    print_exception(e, true)
rescue => e
    print_exception(e, false)    
end

This produces another EXPLICIT error, and since we specify that we’re rescuing any ScriptError, our NotImplementedErrorthat is produced is still captured:

[EXPLICIT] NotImplementedError: fork() function is unimplemented on this machine

As we learned from diving into LoadError, it’s important to remember that Ruby’s default Exception class expectation when no explicit class is provided, is to only grab StandardErrors and all descendants therein. Here we’re removing our explicit call to either ScriptError or NotImplementedError within our rescue clause:

def print_exception(exception, explicit)
    puts "[#{explicit ? 'EXPLICIT' : 'INEXPLICIT'}] #{exception.class}: #{exception.message}"
    puts exception.backtrace.join("\n")
end

begin
    pid = Process.fork { sleep 0.5 }
rescue => e
    print_exception(e, false)
end

Since rescue => e is processing only the default StandardErrors, it does not catch our NotImplementedError and thus our print_exception() function never fires. We still get an error and output of course, but rather than rescuing our code to allow further execution, we’d simply be crashing the application:

code.rb:46:in `fork': fork() function is unimplemented on this machine (NotImplementedError)
        from code.rb:46:in `<main>'

These built-in exception classes are helpful ruby exception handling tools! Used with Airbrake’s Ruby Exception Handler your debugging process will be a breeze. Good luck!