Reputation: 60179
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
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
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
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
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
Reputation: 14977
In such case I use andand gem:
financial_document.assets.andand.length || 0
Upvotes: 3
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
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
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
Reputation: 9574
This being Ruby, you could add a length method to NilClass and have it always return 0.
Upvotes: 0
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
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).
Upvotes: 7