2

How can you construct an array in Ruby which only uses a variable if it exists, and otherwise uses nil in its place. With the conditional logic inside the array constructor.

Simplified Example:

a = 1
c = 3

arr = [a, b || nil, c]

I've tried a number of different way but they are not working so I feel like I'm missing something fundamental here.

  1. (b || nil)
  2. b ? b : nil
  3. b.nil? ? nil : b

Is this possible?

Context: This array constructor is used inside a loop used by multiple different models. Some models have the b attribute and some do not, making it difficult extrapolate the logic outside.

4
  • 1
    Why would b not exist? That's really an unusual thing to see in Ruby code. Commented Sep 30, 2016 at 21:42
  • You say both "uses a variable if it exists" and "Some models have the b attribute and some do not". The first says that b is a variable, the second says that b is a method call, which is it? Commented Sep 30, 2016 at 21:49
  • The real example is more similar to @instance.attribute_b. The method with this logic iterates over query results for several different models (separately); some of the models do not have an attribute_b. Commented Sep 30, 2016 at 21:49
  • You shouldn't try to embed the conditional logic into the creation of the array; That way leads to madness at 3AM when a stressed coworker needs to fix a production bug. Instead, move the condition outside the array definition so it is clear what is happening, then define the array. Or, at a minimum, split the array into separate lines so there's some vertical space making it easier for the brain to process. Commented Sep 30, 2016 at 22:27

5 Answers 5

3

Yea, you can use defined? method. It return "local-variable" if variable exist, if not it will return nil.

arr = [a, defined?(b) ? b : nil, c]
Sign up to request clarification or add additional context in comments.

4 Comments

arr = [a, defined?(b) ? b : nil, c] is a little bit cleaner
This is the correct answer, as b is not guaranteed to be defined.
@CDub I understood this question exactly in this way.
The problem here is that b may not be defined but calling the b method could still work, consider a model that uses method_missing to create accessors as needed.
2

Given that your b is actually a model attribute which may or may not be supported by the current self, then b is not a variable at all, b is a method call and you're not interested in whether or not the "variable" exists, you're interested in whether or not self responds to b. Hence you want to use respond_to?:

arr = [a, respond_to?(:b) ? b : nil, c]

or perhaps:

arr = [a, respond_to?(:b, true) ? b : nil, c]

if you want to allow b to be a non-public method.

defined?(b) should also work in this case (unless b's accessor method is automatically created via method_missing) but it will be rather puzzling to anyone looking at your code. Using respond_to? to see if an object has a method/attribute would be more idiomatic.

Using respond_to? when faced with method_missing of course assumes that both method_missing and respond_to? have been overridden but that should be a relatively safe assumption.

Comments

0

This will do the trick:

arr = [a, b = b || nil, c]

Example 1:

a = 1
c = 3

arr = [a, b = b || nil, c]
p arr #=> [1, nil, 3]

Example 2:

a = 1
b = 2
c = 3

arr = [a, b = b || nil, c]
p arr #=> [1, 2, 3]

4 Comments

can be shortened to arr = [a, b ||= nil, c]
@Doon yes good point, I miss these things when trying to post a speedy answer. I will however not take up your recommendation as another answer has this already, but thank you.
@sagarpandya82 - go ahead and update it. You won't offend me. :)
In a code review I'd say that arr = [a, b = b || nil, c] is not easily read. While in C or Perl that sort of construct would fly, in Ruby we try to be more obvious. Breaking it into separate lines would help or preassign b then reference it in the array.
0

You can do:

arr = [a, (b ||= nil), c]

Example:

a = 1
b = nil
c = 3

> arr = [a, (b ||= 33), c]
=> [1, 33, 3]

b = 2

> arr = [a, (b ||= 33), c]
=> [1, 2, 3]

If b may not be defined, you'll need to use defined?:

> arr = [a, (defined?(b) ? b : nil), c]
=> [1, nil, 3]

3 Comments

It's not necessary to enclose the expression inside parenthesis, the commas will suffice, however for readability I'd put each element in the array on a separate line so it's easy to see what is happening.
While not necessary, on a single line, it's much easier to read and determine logic vs. simply a variable...
Yes, but it's questionable whether it should be a single line at all. Breaking it into multiple lines evaluates to the same thing, won't reduce speed but will improve readability.
0

Ugh. Don't write code like that. Instead break it into more understandable pieces:

a = 1
c = 3

b ||= nil

arr = [a, b, c]

Or, use vertical space to help the brain see what's going on:

arr = [
  a,
  b ||= 3,
  c
]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.