Simplify multiple nil checking in Rails
Rails has object.try(:method)
:
if @parent.try(:child).try(:grand_child).try(:attribute).present?
do_something
http://api.rubyonrails.org/classes/Object.html#method-i-try
Ruby on rails, multiple check for nil attributes
You are chaining the .try()
, which fails after the try(:age_id)
:
- It tries to call
age_id
on the@user
object - if
@user.nil?
# => returnsnil
- if
@user.age_id != nil
# => returns aFixnum
- Then you call the method
try(:customer)
on a Fixnum which obviously fails # => returnsnil
etc.
An example from the IRB console:
1.9.3p448 :049 > nil.try(:nothing).try(:whatever).try(:try_this_also).nil?
=> true
If you want to test that all of these attributes are not nil, use this:
if @user.present?
if @user.age_id.presence && @user.customer.presence && @user.country.presence
# they are all present (!= nil)
else
# there is at least one attribute missing
end
end
Check nil on multiple variables
By using none?
you can have if
instead of unless
:
if [a,b,c,d,e,f,g].none?(&:nil?)
Come to think of it, this can be reduced to simply:
if [a,b,c,d,e,f,g].all?
if you don't mind treating false
the same as nil
Is there a more efficient way of writing the following?
I think a better question is, "Is there a more expressive way of writing..."
How to simplify big conditions
First of all, some of your logic doesn't make sense:
def search(search, compare, year, rain_fall_type)
if search == 'All'
if rain_fall_type == 'All'
all
else
# rain_fall_type != 'All'
if year == 'All'
if rain_fall_type == "None"
where('Sector = ? OR Sector = ? OR Sector = ?', "Primary", 'Secondary', 'Tertiary').order('id')
else
where(Sector: rain_fall_type).order('id')
end
else
# in rain_fall_type != 'All' branch, so meaningless 'if'
if rain_fall_type == "All"
order("#{year} ")
elsif rain_fall_type == "None"
where('Sector = ? OR Sector = ? OR Sector = ?', "Primary", 'Secondary', 'Tertiary').order('id')
else
where(Sector: rain_fall_type).order("#{year} ")
end
end
end
elsif compare != "None"
# both are same, so meaningless 'if'
if year == 'All'
where('Sector = ? OR Sector = ?', rain_fall_type, compare).order(:id)
else
where('Sector = ? OR Sector = ?', rain_fall_type, compare).order(:id)
end
else
# search != 'All'
if rain_fall_type == 'All'
all.order('id')
else
if year == 'All'
if rain_fall_type == "None"
where('Sector = ? ', search).order('id')
else
where('Sector = ? ', rain_fall_type).order('id')
end
else
if rain_fall_type == "None"
# in search != 'All' branch, so meaningless 'if'
# AND both are same, so again meaningless 'if'
if search == "All"
where('Sector = ? ', search).order('id')
else
where('Sector = ? ', search).order('id')
end
else
where('Sector = ? ', rain_fall_type).order('id')
end
end
end
end
end
There's more like that and I won't point it all out because we're throwing all that if
stuff out, anyway.
Ultimately, we're going to defer the querying to the end of the method, like this:
def search(search, compare, year, rain_fall_type)
...
@query = all
@query = @query.where(Sector: @sectors) if @sectors
@query = @query.order(@order) if @order
@query
end
That way, you take all of your where
and order
statements, and do them only once at the end. That saves a lot of typing right there. See the comment from muistooshort for why (Sector: @sectors)
works.
So, the trick is setting @sectors
and @order
. First, I'm going to assign the input variables to instance variables because I like it like that (and to avoid confusion between the variable @search
and the method search
):
def search(search, compare, year, rain_fall_type)
@search, @compare, @year, @rain_fall_type = search, compare, year, rain_fall_type
...
@query = all
@query = @query.where(Sector: @sectors) if @sectors
@query = @query.order(@order) if @order
@query
end
Now, this answer is going on too long already, so I won't drag you through all the gorey details. But, adding in a couple of helper methods (sectors_to_use
, and order_to_use
) and substituting them in for @sectors
and @order
, you basically end up with this:
def search(search, compare, year, rain_fall_type)
@search, @compare, @year, @rain_fall_type = search, compare, year, rain_fall_type
@query = all
@query = @query.where(Sector: sectors_to_use) if sectors_to_use
@query = @query.order(order_to_use) if order_to_use
@query
end
private
def sectors_to_use
return [@rain_fall_type, @compare] if @search != 'All' && @compare != 'None'
unless @rain_fall_type == 'All'
if @rain_fall_type == 'None'
@search == 'All' ? ['Primary', 'Secondary', 'Tertiary'] : [@search]
else
[@rain_fall_type]
end
end
end
def order_to_use
return nil if (@search == 'All') && (@rain_fall_type == 'All')
return @year if (@search == 'All') && !(@year == 'All')
return :id
end
That's less than half the lines of code, over a thousand fewer characters, and a whole lot fewer ifs
.
What is a better way to check for a nil object before calling a method on it?
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
.
How to simplify big conditions
First of all, some of your logic doesn't make sense:
def search(search, compare, year, rain_fall_type)
if search == 'All'
if rain_fall_type == 'All'
all
else
# rain_fall_type != 'All'
if year == 'All'
if rain_fall_type == "None"
where('Sector = ? OR Sector = ? OR Sector = ?', "Primary", 'Secondary', 'Tertiary').order('id')
else
where(Sector: rain_fall_type).order('id')
end
else
# in rain_fall_type != 'All' branch, so meaningless 'if'
if rain_fall_type == "All"
order("#{year} ")
elsif rain_fall_type == "None"
where('Sector = ? OR Sector = ? OR Sector = ?', "Primary", 'Secondary', 'Tertiary').order('id')
else
where(Sector: rain_fall_type).order("#{year} ")
end
end
end
elsif compare != "None"
# both are same, so meaningless 'if'
if year == 'All'
where('Sector = ? OR Sector = ?', rain_fall_type, compare).order(:id)
else
where('Sector = ? OR Sector = ?', rain_fall_type, compare).order(:id)
end
else
# search != 'All'
if rain_fall_type == 'All'
all.order('id')
else
if year == 'All'
if rain_fall_type == "None"
where('Sector = ? ', search).order('id')
else
where('Sector = ? ', rain_fall_type).order('id')
end
else
if rain_fall_type == "None"
# in search != 'All' branch, so meaningless 'if'
# AND both are same, so again meaningless 'if'
if search == "All"
where('Sector = ? ', search).order('id')
else
where('Sector = ? ', search).order('id')
end
else
where('Sector = ? ', rain_fall_type).order('id')
end
end
end
end
end
There's more like that and I won't point it all out because we're throwing all that if
stuff out, anyway.
Ultimately, we're going to defer the querying to the end of the method, like this:
def search(search, compare, year, rain_fall_type)
...
@query = all
@query = @query.where(Sector: @sectors) if @sectors
@query = @query.order(@order) if @order
@query
end
That way, you take all of your where
and order
statements, and do them only once at the end. That saves a lot of typing right there. See the comment from muistooshort for why (Sector: @sectors)
works.
So, the trick is setting @sectors
and @order
. First, I'm going to assign the input variables to instance variables because I like it like that (and to avoid confusion between the variable @search
and the method search
):
def search(search, compare, year, rain_fall_type)
@search, @compare, @year, @rain_fall_type = search, compare, year, rain_fall_type
...
@query = all
@query = @query.where(Sector: @sectors) if @sectors
@query = @query.order(@order) if @order
@query
end
Now, this answer is going on too long already, so I won't drag you through all the gorey details. But, adding in a couple of helper methods (sectors_to_use
, and order_to_use
) and substituting them in for @sectors
and @order
, you basically end up with this:
def search(search, compare, year, rain_fall_type)
@search, @compare, @year, @rain_fall_type = search, compare, year, rain_fall_type
@query = all
@query = @query.where(Sector: sectors_to_use) if sectors_to_use
@query = @query.order(order_to_use) if order_to_use
@query
end
private
def sectors_to_use
return [@rain_fall_type, @compare] if @search != 'All' && @compare != 'None'
unless @rain_fall_type == 'All'
if @rain_fall_type == 'None'
@search == 'All' ? ['Primary', 'Secondary', 'Tertiary'] : [@search]
else
[@rain_fall_type]
end
end
end
def order_to_use
return nil if (@search == 'All') && (@rain_fall_type == 'All')
return @year if (@search == 'All') && !(@year == 'All')
return :id
end
That's less than half the lines of code, over a thousand fewer characters, and a whole lot fewer ifs
.
Simplify multiple if else condition in rails model
def set_status_of_day(late_policy, early_departure_policy)
case [late_policy.warning_on_late, early_departure_policy.warning_on_late]
when ["Half Day", "Half Day"] then "Absent"
when ["Half Day", "Present"], ["Half Day", "Early Departure"], ["Late", "Early Departure"] then "Half Day"
when ["Present", "Present"] then "Present"
.
.
.
end
end
I added the second when-line like Cary Swoveland suggested in the comments. The commas in the when clause work like an or-conjuction.
See case in the docs for mor information.
ActiveRecord::Relation issue checking for nil? -- Rails 3.1
use blank?
, nil?
is true, if it's really nil
(single instance of NilClass
), but your second example always will return an Array, maybe empty, if there are no results, but an Array nonetheless. blank?
checks for empty arrays, empty strings, nil and false values.
If you have problems with blank?
not behaving as expected you can check for first.nil?
Related Topics
Get Chromes Console Log via Ruby Webdriver
Why Doesn't Relative_Require Work on Ruby 1.8.6
Load Two Ruby Modules/Gems with the Same Name
How to Change the Default Value of a Struct Attribute
Why Does Ruby Builder::Xmlmarkup Add Inspect Tag to Xml
Why Does a Rails App on Heroku Serve Assets via All.CSS and Locally via Individual Files
How to Serialize an Object Using Tcpserver Inside
When Do You Need a Require in a Rails Gemfile
How to Rename or Move Rails's Readme_For_App
Rvm and Osx Lion - Rvm 'Forgets' Gemsets on System Restart
Ruby on Rails: Confirmation Page for Activerecord Object Creation
How to Stringize/Serialize Ruby Code
How to Include Blank Field in Select_Tag
Convert Hash Keys to Lowercase -- Ruby Beginner
How to Set a Variable from a Helper Method for Inclusion in a SASS SCSS Stylesheet
How to Use Same Browser Window for Automated Test Using Selenium-Webdriver (Ruby)
How to Rescue from a Require "Gem_Name" When the Gem Is Not Installed