Reputation: 123662
In kotlin and C#, you can assign a variable, or else if the value is nil, you can throw an exception using the ?:
and ??
operators.
For example, in C#:
var targetUrl = GetA() ?? throw new Exception("Missing A");
// alt
var targetUrl = GetA() ?? GetB() ?? throw new Exception("Missing A or B");
Is this possible in ruby? If so, how?
Basically, what I want to do is this
target_url = @maybe_a || @maybe_b || raise "either a or b must be assigned"
I'm aware I can do this
target_url = @maybe_a || @maybe_b
raise "either a or b must be assigned" unless target_url
but I'd like to do it in a single line if possible
Upvotes: 3
Views: 2635
Reputation: 114248
Basically, what I want to do is this
target_url = @maybe_a || @maybe_b || raise "either a or b must be assigned"
You have to add parentheses to raise
to make your code work:
x = a || b || raise("either a or b must be assigned")
It would be "more correct" to use the control-flow operator or
instead of ||
: (which makes the parentheses optional)
x = a || b or raise "either a or b must be assigned"
This is Perl's "do this or die" idiom which I think is clean and neat. It emphases the fact that raise
doesn't provide a result for x
– it's called solely for its side effect.
However, some argue that or
/ and
are confusing and shouldn't be used at all. (see rubystyle.guide/#no-and-or-or)
A pattern heavily used by Rails is to have two methods, one without !
which doesn't provide error handling:
def a_or_b
@maybe_a || @maybe_b
end
and one with !
which does:
def a_or_b!
a_or_b || raise("either a or b must be assigned")
end
Then call it via:
target_url = a_or_b!
Upvotes: 3
Reputation: 84443
Another way to do what you want with an extremely minimal code change is to use the lower-precedence or
operator, which has a lower precedence than both ||
and =
. For example:
# This is closest to what you want, but violates many style guides.
target_url = @maybe_a || @maybe_b or raise "either a or b must be assigned"
You can also wrap the logical line without changing how it works, e.g.:
# Same code as above, but wrapped for line length
# and to clarify & separate its expressions.
target_url = @maybe_a || @maybe_b or
raise "either a or b must be assigned"
Either way, the code raises a RuntimeError exception as expected. Because of the precedence rules, no parentheses are necessary.
Please note that a number of style guides like this one will tell you to avoid the or
operator altogether, or to use it only for flow control, because it is often the cause of subtle precedence bugs that can be hard to spot at a glance. With that said, the wrapped version is really just an inverted variation of my other answer, and is pretty easy to visually distinguish without the use of parentheses, especially with syntax highlighting enabled. Your mileage and style guide strictness will most certainly vary.
Upvotes: 2
Reputation: 84443
Because most everything in Ruby evaluates to an expression, you can do this as a single logical line by using unless
as a postfix conditional followed by an assignment expression. I've chosen to wrap the line to fit a reasonable line length, but feel free to make it a single line if you really want a "one-liner." For example:
raise "either a or b must be assigned" unless
target_url = @maybe_a || @maybe_b
This will correctly raise RuntimeError, as you are expecting.
Please note that this particular approach will auto-vivify @maybe_a and assign nil
to it. It will also do the same for @maybe_b if @maybe_a evaluates as falsey. While handy, auto-vivification may trip you up later if you're relying on defined? to identify undefined variables elsewhere in your code. The pros and cons of this idiom will therefore depend on your broader intent, but it will certainly get the job done within the scope of the original question.
Upvotes: 0
Reputation: 3280
You could work around it with brackets:
(target_url = @maybe_a || @maybe_b) || raise("either a or b must be assigned")
Upvotes: 1