rept
rept

Reputation: 2246

How to write this more succinctly?

Is there a construction in RoR that would allow me to write this:

@budget_project_amounts.detect {|attributes| attributes['id'] == 659 }.attributes['amount'] if !@budget_project_amounts.detect {|attributes| attributes['id'] == 659 }.nil?

more succinctly? Without the use of a variable...

Edit: This part

@budget_project_amounts.detect {|attributes| attributes['id'] == 659 }

is irrelevant. It could be anything. I sometimes have a situation like this one, where I first need to check if something is nil, before being able to continue to get a value under it (in case it's not nil and I'm looking for a way to do this without having to repeat the first part completely. I hope this makes sense.

Upvotes: 2

Views: 61

Answers (4)

tokland
tokland

Reputation: 67910

You can write it compactly using the abstraction map_detect:

require 'facets/enumerable/map_detect'
amount = amounts.map_detect { |attrs| attrs['amount'] if attrs['id'] == 659 }

Upvotes: 0

xlembouras
xlembouras

Reputation: 8295

You can use a simple

@budget_project_amount.
  select{ |x| x[:id] == 1 && x[:amount].present?}.
  map{|x| x[:amount] }.
  first

It will return the first amount or nil

PS I guess that ids are unique...

Upvotes: 0

Nicola Miotto
Nicola Miotto

Reputation: 3706

You could try with short circuits, for instance (to mention your example):

(bp = @budget_project_amount.find { |b| b['id'] == 12 }) && bp['amount']

The result of the expression is either nil or the amount you are searching for. Less than this I cannot think of anything.

It's called short-circuit because if the first expression evaluates to false, than the second one is not evaluated, avoiding calling the method on a nil object.

Upvotes: 2

pdobb
pdobb

Reputation: 18037

Avoiding the use of a variable... you can start by trying to eliminate the if condition at the end by using try:

@budget_project_amount.detect {|attributes| attributes['id'] == 659 }
                      .try(attributes).try(:[], 'amount')

However, just because this is less code doesn't really make it better code. The problem with your original approach and this approach is that both rely on type checking (for NilClass) to get the job done. And type checking is a code smell.

I like to get around type checking by using Naught. With the right null object setup you can do this instead:

Maybe(@budget_project_amount.detect {|attributes| attributes['id'] == 659 }).attributes[:amount].to_f

Basically, forget about type checking, just do what you wanted to do and make sure the end result is the type of object you're looking for (with .to_f or .to_i or whatever).

For the above example to work, I define my null objects like this:

require 'naught'

NullObject = Naught.build do |config|
  config.define_explicit_conversions
  config.define_implicit_conversions
  config.black_hole

  if $DEBUG
    config.traceable
  else
    config.singleton
  end
end

This provides explicit conversions (for .to_f) and is a black hole (which allows multiple methods to be called in a chain regardless of where a nil appears in that chain).

Upvotes: 3

Related Questions