2

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.

AdamNYC
  • 19,887
  • 29
  • 98
  • 154
  • Are you doing `nil` check? Then you don't need `present?`. – sawa Jan 04 '12 at 04:49
  • I thought it is equivalent to `if !@parent.child.nil? and !@parent.child.granchild.nil? ... `. Am I right? – AdamNYC Jan 04 '12 at 04:58
  • No `present` is for checking whether it is either `nil` or `empty`. And you usually don't need `nil?` unless you want to distinguish `nil` from `false`. – sawa Jan 04 '12 at 05:01
  • At the very least you should pass this knowledge into the helper. – Mike Silvis Aug 27 '13 at 14:45

10 Answers10

4

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

klochner
  • 8,077
  • 1
  • 33
  • 45
3

You could use Object#andand.

With it your code would look like this:

if @parent.andand.child.andand.grandchild.andand.attribute
Sergio Tulentsev
  • 226,338
  • 43
  • 373
  • 367
  • Thanks, Sergio. I learn something new here. Just wonder if there's a more native way to say it. – AdamNYC Jan 04 '12 at 04:41
  • @AdamNYC If you are expecting a Ruby native way, then you should not tag the question as `ruby-on-rails` as it has `try`, which is not Ruby native. – sawa Jan 04 '12 at 04:52
  • Thanks sawa. Either Ruby or Rails would be fine for my purposes. – AdamNYC Jan 04 '12 at 04:56
3

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
sawa
  • 165,429
  • 45
  • 277
  • 381
  • I quite often use that too, usually with database queries for some reason, stuff like: `if (u = User[id]) && u.admin?`... but for more than two I use a library like ick or andand, or it can start to look messy. +1 for a really useful tip. – ian Jan 04 '12 at 04:55
2

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.

ian
  • 12,003
  • 9
  • 51
  • 107
0

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
Saurabh Udaniya
  • 468
  • 2
  • 17
0

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

Benjamin Crouzier
  • 40,265
  • 44
  • 171
  • 236
0

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

Macpeters
  • 118
  • 1
  • 8
0

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

Thong Kuah
  • 3,263
  • 2
  • 19
  • 29
0

You coult just catch the exception:

begin
  do something with parent.child.grand_child.attribute
rescue NoMethodError => e
  do something else
end
moritz
  • 25,477
  • 3
  • 41
  • 36
0

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

@parent.child_grand_child_attribute.present?
Bohdan
  • 8,298
  • 6
  • 41
  • 51
  • http://avdi.org/devblog/2011/07/05/demeter-its-not-just-a-good-idea-its-the-law/ an article with more info – Bohdan Jan 04 '12 at 11:54