marcamillion
marcamillion

Reputation: 33795

How do I elegantly handle nil exceptions?

In my /post/index.html view, I have this line:

<%= post.status.upcase %>

Whenever there is a post that has a status with a nil value, I get this error:

NoMethodError at /
undefined method `upcase' for nil:NilClass

How do I safely handle all nil values throughout my app so it doesn't throw an error like this, it just gracefully degrades or even just ignores it?

Do I have to go through every single view and every single object that may have an attribute that can be returned nil and add in exception handling for each?

Is there a "Rails-y" DRY way to handle this?

Upvotes: 1

Views: 9056

Answers (4)

Josh Saint Jacque
Josh Saint Jacque

Reputation: 181

I just wanted to update this thread with one more option: Ruby now (as of 2.3) gives us a safe navigation operator, the &. syntax.

So:

post.status.upcase

Would become:

post.status&.upcase

Similarly to Rail's try method, the whole chain will return nil if it encounters NoMethodError on a nil.

Upvotes: 6

nPn
nPn

Reputation: 16768

How is post defined? Normally in a controller you would set an instance variable such as @post. I will assume that post is somehow derived from a model, in which case what you really want to do is to ensure that you never save a post with a status of nil to your database. You should have a validation on the Post models to ensure that the status is always present, and then check that the Post object is valid when it is created. So something like this:

in your model:

class Post < ActiveRecord::Base
validates :status, presence: true
...

and then in your posts_controller.rb controller:

def create
...
if @post.save
  #handle successful save
else
  # handle unsuccessful save 
end

this way you never end up with bad data in your database.

I guess an other (hacky) way to solve this problem would be to create a method in your model to ensure you never get a null from the status methos, so something like this:

class Post
...
def status
  read_attibute(:status) || ""  # return an empty string is status is null - this is a hack, I should fix this at some point by making sure the a status of nil is never saved to the database!
end

or based on your comment in an above answer, you could always monkey patch active record to never return nil, and instead return some default value for each class.

File activerecord/lib/active_record/base.rb, line 1870

  def read_attribute(attr_name)
    attr_name = attr_name.to_s
    if !(value = @attributes[attr_name]).nil?
      if column = column_for_attribute(attr_name)
        if unserializable_attribute?(attr_name, column)
          unserialize_attribute(attr_name)
        else
          column.type_cast(value)
        end
      else
        value
      end
    else
      nil # return whatever you think is appropriate 
    end
  end

Upvotes: 0

Eugene Tkachenko
Eugene Tkachenko

Reputation: 232

Put this in Application_controller.rb

rescue_from Exception::NoMethodError, :with => :render_error

private
def render_error
render :file => "#{Rails.root}/public/methodnotfound.html", :status => 404, :layout => false
end

And also create the html with the message "methodnotfound.html".

Upvotes: 0

Philip Hallstrom
Philip Hallstrom

Reputation: 19899

Ignoring nil is a bad idea IMHO. It will lead to subtle errors that will be impossible to track down. If, however what you rally want for your example is to output an empty string (or nothing at all) when status is nil you have a couple of options. For what I think you're doing I prefer the first one, followed closely by the second and hate the last two.

post.status.to_s.upcase

post.status.try(:upcase)

post.status.present? ? post.status.upcase : 'NO STATUS. OH NO'

begin
  post.status.upcase
rescue 
  'NO STATUS. OH NO'
end

Upvotes: 5

Related Questions