Ethan
Ethan

Reputation: 60179

What is a better way to check for a nil object before calling a method on it?

I have this method call I have to use...

financial_document.assets.length

But financial_document.assets could be nil.

I could use...

financial_document.assets.nil? ? '0' : financial_document.assets.length

Is there a less repetitive way to do that?

Upvotes: 7

Views: 1265

Answers (11)

sawa
sawa

Reputation: 168249

You can do it without additional gems. I have been using ||, andand, try, but the following looks simpler. I think it is the ruby way to confirm to Dave's null object pattern.

financial_document.assets.to_a.length

Upvotes: 1

Sohan
Sohan

Reputation: 3805

financial_document.assets.try(:length) || 0

try is a method that will invoke the object's method if its non nil otherwise just return nil. And try on nil methods will always return nil instead of throwing an exception.

http://api.rubyonrails.org/classes/Object.html#method-i-try

This is the Ruby way to do this!

Upvotes: 1

Scott
Scott

Reputation: 7284

A more generic way to solve this class of problems is to add a try method to Object:

  ##
  #   @user.name unless @user.nil?
  # vs
  #   @user.try(:name)
  #
  def try(method, *args, &block)
    return nil unless method
    return nil if is_a?(NilClass) and [:id, 'id'].include?(method)
    self.send(method, *args, &block) if respond_to?(method)
  end

I believe ruby 1.9 already has a try method on Object.

Then financial_document.assets.try(:length).to_i would achieve your desired result. This is because nil.to_i returns 0

Upvotes: 3

nitecoder
nitecoder

Reputation: 5486

Something in the model that returns 0 or the length. This keeps you from having to do a convaluted thing in your view. Things like this can normally be done in the model.

class FinancialDocument

  def assets_length
    assets.length.blank? 0 : assets.length
  end
end

Upvotes: 0

klew
klew

Reputation: 14977

In such case I use andand gem:

financial_document.assets.andand.length || 0

Upvotes: 3

Jonathan Lonowski
Jonathan Lonowski

Reputation: 123563

Personally, I would use the or operator/keyword:

(financial_document.assets or []).length

Either way, .length is called on an array, giving you 0 if nil.

Upvotes: 7

Swanand
Swanand

Reputation: 12426

Case 1:

financial_document and assets have has many relationship. In this case, financial_document.assets always returns an array. So financial_document.assets.size would give you 0 if no matching child entry is found, and size otherwise.

Case 2:

assets is just a method/attribute in financial_document. Then have the assets method return array, so that you can always call .size on it. Just like Joel has pointed out.

Upvotes: 3

Lukas Stejskal
Lukas Stejskal

Reputation: 2562

You can make it a bit shorter:

financial_document.assets ? financial_document.assets.length : '0'

because

financial_document.assets == !financial_document.assets.nil?

but in general, IMHO there's no less repetitive way, only various workarounds. (And this is one of the things I don't like so much in Ruby.) You can make sure that objects aren't null (as other people are suggesting here) - but you can't do that everywhere. You can wrap up the nil-checking code in helper methods or in begin-rescue blocks.

For example, rather than adding length method to nil object (which is IMHO a dirty hack), I'd wrote a helper method - a "length getter":

def fd_length(financial_document)
  financial_document.assets ? financial_document.assets.length : '0'
end

Upvotes: 0

bendin
bendin

Reputation: 9574

This being Ruby, you could add a length method to NilClass and have it always return 0.

Upvotes: 0

user1976
user1976

Reputation:

Dave W. Smith is on the right track.

Check this out: http://www.nach-vorne.de/2007/4/24/attr_accessor-on-steroids

One easy solution would look something like this:

class FinancialDocument
  attr_accessor :assets
  def assets
    @assets ||= Array.new
  end
  ...
end

Upvotes: 8

Dave W. Smith
Dave W. Smith

Reputation: 24966

The less repetitive way of dealing with this is to ensure that financial_document.assets is always a non-null object, by arranging for it to hold an appropriate sentinel value (e.g., an empty collection, or a special object that has degenerate behavior).

See The Null Object Pattern.

Upvotes: 7

Related Questions