I'm looking for a good shortcut for Nil checking in my Rails views. I've seen other questions on SO about this, but none seem to simplify this as much as I'd like. What I'd like is a short syntax to return an empty string "" if a particular value is nil, otherwise return the value.
There is a suggestion here which I am inclined to try out. It basically allows a statement like this:
user.photo._?.url
-- or --
user.photo.url._?
Is this a good idea or is it fraught with peril?
My other option would be to handle nils on my models, but that seems too global.
You should check try method which runs a provided method on the object and returns the value if the object in question is not nil. Otherwise it'll just return nil.
Example
# We have a user model with name field
u = User.first
# case 1 : u is not nil
u.try(:name)
=> Foo Bar
# case 2 : u is nil
u.try(:name)
=> nil
# in your case
user.photo.try(:url)
For details have a look at this blog post.
The idiomatic Ruby way to accomplish this is the || operator, which will return the value of the right-hand expression if the left-hand expression is nil (or false):
puts(user.photo.url || '')
Any moderately experienced Ruby programmer will understand exactly what that does. If you write a custom _? method, I now have to go look up the purpose of that method and remember what it does and hope that it always does the right thing. I generally find that sticking to idiomatic code is far more beneficial than saving a few keystrokes here and there.
try this:
user && user.photo && user.photo.url.present?
This will not blow up if user is nil or user.photo is nil
What about just using nil?
someObject.nil? # true if someOBj is nil
Or am I misunderstanding what you want?
I suspect your options are problematic, because if photo is nil, both of your statements should return 'Undefined method'.
You shouldn't even need to check for '.nil?'. Since you implied your checking is in the view, and not in the controller, I imagine you are checking an #instance variable, which will always be nil if undefined. So just do:
if #someObject
...
else
...
If you are putting the conditional in your controller, then again, just use an #instance variable and you'll always have at least nil, if undefined.
Related
I am building a basic app that will retrieve a customers data and show it on screen, however some of the fields will be null and so I get a lot of undefined method 'registration_number' for nil:NilClass errors
Is there a better way to to deal with these other than lots of if statements
if #customer.registration_number.blank?
Do something
else
#customer.registration_number
end
Thanks in advance
If you want to actually do something on missing values, you can't avoid a conditional per value. (Especially if that "something" differs from value to value).
If you want to simply ignore missing values, you can use one of the nil-swallowing tricks.
#customer.try(:registration_number)
#customer&.registration_number
#customer.registration_number rescue nil # don't use this one, there are better ways
You can do a try there
#customer.try(:registration_number)
and if there is no value, it will just return nil
In my controller, I used to be able to say:
if params[:business][:branch]
After Rails 4, when I try the same I get:
NoMethodError: undefined method `[]' for nil:NilClass
This is the only way I can find to do it in a single line now.
params.has_key?(:business) ? params[:business].has_key?(:branch_id) : false
Kind of verbose.
There are many possible answers, one of which you proposed in your question.
I really love the Hash#fetch method. It returns the value associated with a Hash key, and optionally allows you to supply a default to return in case the key is missing. With that, you can make a construct like this:
if params.fetch(:business, {}).fetch(:branch, false)
# do stuff
end
This way you don't even need any conditionals or Hash key presence checks in your code.
I am a fan of
if params[:business][:branch].present?
..
end
just because it keeps the params[sym] form so it's easier to read.
You can also use blank? http://api.rubyonrails.org/classes/Object.html#method-i-blank-3F
unless params[:one].blank? && params[:two].blank?
will return true if its empty or nil
also... that will not work if you are testing boolean values.. since
>> false.blank?
=> true
in that case you could use
unless params[:one].to_s.blank? && params[:two].to_s.blank?
Is there a way to use .each so it does not throw an error if the object is nil or empty (without adding an additional nil/blank test?
It seems that if I say phonelist.each do |phone| that if phonelist is empty, then the block should not be executed.
But in my view (haml) I have - #myvar.phonelist.each do |phone| and if phonelist is empty, it throws a NoMethodError.
I run into this a lot, and always workaround by adding an explicit check/branch for .blank? but it seems there should be an easier way to tell .each that empty means do nothing.
You can use the try method to call .each on a nil so it does not throw an error if the object is nil or empty.
phonelist = nil
phonelist.try(:each){|i| puts i}
Simply do the following:
Array(phonelist).each do |phone|
#deal with your phone
end
Array(my_variable) will ensure to return an array if my_variable is nil.
It doesn't create a new Array if my_variable is already an array, so it is safe and light to use it wherever you want !
You're attempting to smack a band-aid on a larger problem.
Ruby has a concept of nil; can't get around it. If you are calling a method on nil, then you are assuming it is valid, i.e., your design assumes it to be valid. So the question really is: where is the hole in your design? Why is your assumption incorrect?
The problem here is not that you cannot call arbitrary methods on an object which does not support it; the problem is that your data is assumed to be valid when obviously that is not always the case.
But in my view (haml) I have - #myvar.phonelist.each do |phone| and if phonelist is empty, it throws a NoMethodError.
No. If phonelist is not an object which implements .each it throws an error. Very different.
You can always initialize it to an empty array if null, i.e., phonelist ||= [], but I would prefer a design which ensures valid data whenever possible.
Can't believe no one has suggested this yet:
(#myvar.phonelist || []).each do |phone|
...
If phonelist is nil, the each will loop on the empty array, executing the block zero times.
HOWEVER, this will still throw an exception if phonelist is not an enumerable (e.g. an array).
If you get phonelist from a hash (e.g. parsed JSON file), you may want to use fetch with [] as the default.
phonelist = my_data.fetch('phonelist', [])
Simply make sure empty phonelist is a [], instead of a nil value.
Alternatively, a nil value is falsey in Ruby, so you can use nil-punning
if phonelist
phonelist.each do |phone|
...
what i've seen done a lot is:
#myvar.phonelist.each do |phone|
# ...
end unless #myvar.phonelist.nil?
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?
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
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.
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.
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.
In such case I use andand gem:
financial_document.assets.andand.length || 0
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
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!
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
This being Ruby, you could add a length method to NilClass and have it always return 0.
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
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
This is one of those things, that maybe so simple I'll never find it because everyone else already knows it.
I've got objects I have to check for nil in my views so I don't dereference a nil:
<%= if tax_payment.user; tax_payment.user.name; end %>
Or I could do this variant:
<%= tax_payment.user ? tax_payment.user.name : '' %>
So this is ok ... for most languages. But I feel like there must be some bit of shiny ruby or railness I'm still missing if this is the best I can do.
What about:
<%= tax_payment.user.name if tax_payment.user %>
You can also try the new Object.try syntax, pardon the pun.
This is in the shiny new Rails 2.3:
tax_payment.try(:user).try(:name)
The Ruby community has put an incredible amount of attention to automating this idiom. These are the solutions I know of:
try in Ruby on Rails
Another try
andand
A safer andand
Kernel::ergo
send-with-default
maybe
_?
if-not-nil
turtles!
method_ in Groovy style
do-or-do-not
The most well-known is probably the try method in Rails. However, it has received some criticism.
In any case, I think Ben's solution is perfectly sufficient.
I've always preferred this approach:
model:
class TaxPayment < ActiveRecord::Base
belongs_to :user
delegate :name, :to=>:user, :prefix=>true, :allow_nil=>true
end
view:
<%= tax_payment.user_name %>
http://apidock.com/rails/Module/delegate
For a little more comprehensive solution, you could check out the Introduce Null Object Refactoring. The basic mechanics of this refactoring is that instead of checking for nil in the client code you instead make sure that the provider never produces a nil in the first place, by introducing a context-specific null object and returning that.
So, return an empty string, an empty array, an empty hash or a special empty customer or empty user or something instead of just nil and then you will never need to check for nil in the first place.
So, in your case you would have something like
class NullUser < User
def name
return ''
end
end
However, in Ruby there is actually another, quite elegant, way of implementing the Introduce Null Object Refactoring: you don't actually need to introduce a Null Object, because nil is already an object! So, you could monkey-patch nil to behave as a NullUser – however, all the usual warnings and pitfalls regarding monkey-patching apply even more strongly in this case, since making nil silently swallow NoMethodErrors or something like that can totally mess up your debugging experience and make it really hard to track down cases where there is a nil that shouldn't be there (as opposed to a nil that serves as a Null Object).
I just do
<%= tax_payment.user.name rescue '' %>
Another option, which makes sense occasionally...
If tax_payment.user returns nil, nil.to_s (an empty string) is printed, which is harmless. If there is a user, it will print the user's name.
You could write a helper method which looks like this:
def print_if_present(var)
var ? var : ""
end
And then use it like this (in the view):
<%= print_if_present(your_var) %>
If the var is nil, it just prints nothing without raising an exception.