AdamNYC
AdamNYC

Reputation: 20415

Simplify multiple nil checking in Rails

How should I write:

if @parent.child.grand_child.attribute.present?
  do_something

without cumbersome nil checkings to avoid exception:

if @parent.child.present? && @parent.child.grandchild.present? && @parent.child.grandchild.attribute.present?

Thank you.

Upvotes: 2

Views: 2578

Answers (10)

Macpeters
Macpeters

Reputation: 126

All these answers are old, so I thought I should share more modern options.

If you are getting an association that might not exist:

@parent&.child&.grand_child&.attribute

if you are reaching into a hash for a key that might not exist:

hash = {
 parent_key: {
   some_other_key: 'a value of some sort'
 },
 different_parent_key: {
   child_key: {
     grand_child: {
       attribute: 'thing'
     }
   }
 }
}
hash.dig(:parent_key, :child_key, :grandchild_key)

Either of these will gracefully return nil if child, grandchild, or attribute don't exist

Upvotes: 0

Benjamin Crouzier
Benjamin Crouzier

Reputation: 41895

You could do this:

Optional = Struct.new(:value) do
  def and_then(&block)
    if value.nil?
      Optional.new(nil)
    else
      block.call(value)
    end
  end

  def method_missing(*args, &block)
    and_then do |value|
      Optional.new(value.public_send(*args, &block))
    end
  end
end

Your check would become:

if Optional.new(@parent).child.grand_child.attribute.present?
  do_something

Source: http://codon.com/refactoring-ruby-with-monads

Upvotes: 0

Saurabh Udaniya
Saurabh Udaniya

Reputation: 478

Hi Think you can use a flag variable here with rescue option

flag = @parent.child.grand_child.attribute.present? rescue false

if flag
do_something
end

Upvotes: 0

Bohdan
Bohdan

Reputation: 8408

I suppose you can do it using a delegate method as a result you'll have sth like

@parent.child_grand_child_attribute.present?

Upvotes: 0

moritz
moritz

Reputation: 25757

You coult just catch the exception:

begin
  do something with parent.child.grand_child.attribute
rescue NoMethodError => e
  do something else
end

Upvotes: 0

Thong Kuah
Thong Kuah

Reputation: 3283

If the attribute you are checking is always the same, create a method in @parent.

def attribute_present?
  @parent.child.present? && @parent.child.grandchild.present? && @parent.child.grandchild.attribute.present?

end

Alternatively, create has_many :through relationship so that @parent can get to grandchild so that you can use :

@parent.grandchild.try(:attribute).try(:present?)

Note: present? is not just for nil, it also checks for blank values, ''. You can just do @parent.grandchild.attribute if it's just nil checking

Upvotes: 0

klochner
klochner

Reputation: 8125

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

Upvotes: 4

sawa
sawa

Reputation: 168101

You can slightly reduce it by assigning the intermediate values to some local variable:

if a = @parent.child and a = a.grandchild and a.attribute

Upvotes: 3

ian
ian

Reputation: 12251

For fun, you could use a fold:

[:child, :grandchild, :attribute].reduce(@parent){|mem,x| mem = mem.nil? ? mem : mem.send(x) } 

but using andand is probably better, or ick, which I like a lot and has methods like try and maybe.

Upvotes: 2

Sergio Tulentsev
Sergio Tulentsev

Reputation: 230336

You could use Object#andand.

With it your code would look like this:

if @parent.andand.child.andand.grandchild.andand.attribute

Upvotes: 3

Related Questions