views:

262

answers:

9

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?

+6  A: 

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.

Dave W. Smith
+7  A: 

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
Joel Meador
A: 

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

bendin
A: 

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
Lukas Stejskal
+2  A: 

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.

Swanand
+5  A: 

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.

Jonathan Lonowski
+4  A: 

In such case I use andand gem:

financial_document.assets.andand.length || 0
klew
A: 

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
railsninja
+2  A: 

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

Scott
try was backported in Ruby 1.8 for Rails 2.3 - see http://railscasts.com/episodes/152-rails-2-3-extras
Omar Qureshi