I often have a situation where I want to do some conditional logic and then return a part of the condition. How can I do this without repeating the part of the condition in the true or false expression?
For example:
ClassName.method.blank? ? false : ClassName.method
Is there any way to avoid repeating ClassName.method?
Here is a real-world example:
PROFESSIONAL_ROLES.key(self.professional_role).nil? ?
948460516 : PROFESSIONAL_ROLES.key(self.professional_role)
Assuming you're okay with false being treated the same way as nil, you use ||:
PROFESSIONAL_ROLES.key(self.professional_role) || 948460516
This will return 948460516 if key returns nil or false and the return value of the call to key otherwise.
Note that this will only return 948460516 if key returns nil or false, not if it returns an empty array or string. Since you used nil? in your second example, I assume that's okay. However you used blank? in the first example (and blank? returns true for empty arrays and strings), so I'm not sure.
If you just want to DRY, then you can use a temp variable:
x = ClassName.method
x.blank? ? false : x
x = PROFESSIONAL_ROLES.key(self.professional_role)
x.nil? ? 948460516 : x
If you don't want to use a temp variable, you can use a block:
Proc.new do |x| x.blank? ? false : x end.call(ClassName.method)
Proc.new do |x| x.nil? ? 948460516 : x end.call(PROFESSIONAL_ROLES.key(self.professional_role))
For the cases you describe (where you just want to use the original value when a default-check fails), it'd be straightforward to write a helper method:
def x_or_default(x, defval, checker = :nil?)
if x.send(checker) then defval else x end
end
x_or_default(ClassName.method, false, :blank?)
x_or_default(PROFESSIONAL_ROLES.key(self.professional_role), 94840516)
which is very similar to the || method described, but would also work with your blank? example.
I usually use temporary variables for this sort of thing.
I know this doesn't look too pretty, but it does make things a bit DRYer.
a = "ABC"
b = (temp = a.downcase).length < 3 ? "---" : temp
If you don't want to create temp variable for whatever reason, you could reuse something that already exists like $_.
Related
In Ruby, I am trying to write a line that uses a variable if it has been set, otherwise default to some value:
myvar = # assign it to ENV['MY_VAR'], otherwise assign it to 'foobar'
I could write this code like this:
if ENV['MY_VAR'].is_set? #whatever the function is to check if has been set
myvar = ENV['MY_VAR']
else
myvar = 'foobar'
end
But this is rather verbose, and I'm trying to write it in the most concise way possible. How can I do this?
myvar = ENV['MY_VAR'] || 'foobar'
N.B. This is slightly incorrect (if the hash can contain the value nil) but since ENV contains just strings it is probably good enough.
The most reliable way for a general Hash is to ask if it has the key:
myvar = h.has_key?('MY_VAR') ? h['MY_VAR'] : 'default'
If you don't care about nil or false values (i.e. you want to treat them the same as "not there"), then undur_gongor's approach is good (this should also be fine when h is ENV):
myvar = h['MY_VAR'] || 'foobar'
And if you want to allow nil to be in your Hash but pretend it isn't there (i.e. a nil value is the same as "not there") while allowing a false in your Hash:
myvar = h['MY_VAR'].nil? ? 'foobar' : h['MY_VAR']
In the end it really depends on your precise intent and you should choose the approach that matches your intent. The choice between if/else/end and ? : is, of course, a matter of taste and "concise" doesn't mean "least number of characters" so feel free to use a ternary or if block as desired.
hash.fetch(key) { default_value }
Will return the value if it exists, and return default_value if the key doesn't exist.
This works best for me:
ENV.fetch('VAR_NAME',"5445")
myvar = ENV.fetch('MY_VAR') { 'foobar' }
'foobar' being the default if ENV['MY_VAR'] is unset.
Although it's not relevant in the specific example you gave since you're really asking about hash keys, not variables, Ruby does give a way to check variable definition. Use the defined? keyword (it's not a method, but a keyword since it needs special handling by the interpreter), like so:
a = 1
defined? a
#=> "local-variable"
#a = 2
defined? #a
#=> "instance-variable"
##a = 3
defined? ##a
#=> "class-variable"
defined? blahblahblah
#=> nil
Hence you could do:
var = defined?(var) ? var : "default value here"
As far as I know, that's the only way other than an ugly begin/rescue/end block to define a variable in the way that you ask without risking a NameError. As I said, this doesn't apply to hashes since:
hash = {?a => 2, ?b => 3}
defined? hash[?c]
#=> "method"
i.e. you're checking that the method [] is defined rather than the key/value pair you're using it to access.
Another possible alternative, which will work even if ENV['MY_VAR'] turnsout to be a false value
myvar = ENV['MY_VAR'].presence || 'foobar'
The Demand gem which I wrote allows this to be extremely concise and DRY:
myvar = demand(ENV['MY_VAR'], 'foobar')
This will use ENV['MY_VAR'] only if it is present. That is, it will discard it just if it's nil, empty or a whitespace-only string, giving the default instead.
If a valid value for ENV['MY_VAR'] is falsy (such as false), or an invalid value is truthy (such as ""), then solutions like using || would not work.
I am new guy to Ruby, post the answer I found at 2021, maybe useful for someone.
check if env key exists:
include?(name) → true or false
has_key?(name) → true or false
member?(name) → true or false
key?(name) → true or false
get env with default value:
ENV.fetch(name, :default_val)
ref: https://docs.ruby-lang.org/en/master/ENV.html
myvar = ENV['MY_VAR'].is_set? ? ENV['MY_VAR'] : 'foobar'
This way you keep the .is_set? method.
going through ruby monk and once in a while they throw out a code from the left field with unfamiliar syntaxes:
def compute(xyz)
return nil unless xyz
xyz.map {|a,b| !b.nil? ? a + b : a}
end
Can somebody explain these three uses?
1) The exclamation before the object
2) The additional question mark 3) The colon usage within the lambda
! is a just a not operator.
b.nil? is a method that checks the value for b is nil or not. Returns a boolean.
!b.nil? ? a + b : a is a ternary operation is action. It works like this :
if_this_is_a_true_value ? then_the_result_is_this : else_it_is_this
which is equivalent of saying
if a then b else c end
So relating with above statement if !b.nil? is true answer is a+b else it is a.
Read more here
1)
!, negations - it changes every object except nil and false to false (other objects into true)
2) name? should return false(false and nil) or true value (everything else). In most cases it will be true or false objects.
Methods with with ? at the end suggest that they are predicates. In your example nil? checks if object is nil. In other language you may find something like this: is_nil.
Other examples:
[].empty? # true
3.zero? # false
0.zero? # true
3) The colon in your example is part of a ternary if.
'cond' ? 'true value' : 'false value'
is similar to:
if 'cond'
'true value'
else
'false value'
end
One difference between ?: and if else is precedence:
def foo a
a # just return a
end
foo 2 ? 3 : 4
# => 3
foo if 2 then 3 else 4 end
# error
In the last example Ruby wanted to run function1 if condition but it found function if condition #some garbage, so Ruby raised an error.
The exclamation before the object :
!x means negation of x, if x is true, then !x means false and vice-versa
The additional question mark :
x ? y : z
means that if x is true, then return y, else return z
The colon usage within the lambda
I explained in the 2. above
I'm new to Ruby, and trying to understand why this works (it does seem to, at least according to "the Master"):
def array_of_fixnums?(array)
true unless array.find { |item| item.class != Fixnum }
end
My concern is where the "false-ness" is coming from when the array contains non-fixnum values. Am I right to assume there is no implicit "else false" in the unless statement? In that case I assume it must be coming from the nil value returned by Enumerable#find. Is that correct?
If so, that seems a bit shaky. Might it be better to return false explicitly, like this?
array.find { |item| item.class != Fixnum } ? false : true
Is there another, better way entirely? Thanks for helping me wrap my head around this, and for any "best practice" suggestions.
Your method is returning nil not because find returns nil, but because if your inline conditional does not pass, the method has no explicit return value. This would be more clear if it were not inline, consider:
def array_of_fixnums?(array)
unless array.find { |item| item.class != Fixnum }
return true
end
# otherwise don't explicitly return (implicit nil)
end
While relying on the falsiness of nil will often work, it is problematic in that it does not follow the principle of least surprise. A ? method should return true or false.
But your method has worse problems. It uses confusing logic (a double negative), and itself relies on the falsiness of nil and the truthiness of not nil, to function. Consider what happens if your method were passed [false]. Oops.
The better way would be something like:
array.all? {|n| n.is_a? Fixnum }
The reasoning is that this method does exactly what it says, plainly.
Returning a boolean explicitly, while not necessarily wrong, is superfluous and often considered bad practice. Rather consider the example, which says, in ruby speak, is every one of the values in this array a Fixnum?. The result of that expression is what the method is after; there's no reason to evaluate it then return true|false.
From the find method doc:
Passes each entry in enum to block. Returns the first for which block is not false. If no object matches, calls ifnone and returns its result when it is specified, or returns nil otherwise.
Thus, all you need is:
def array_of_fixnums?(array)
array.find { |item| item.class != Fixnum }
end
If anything is found, that will be returned, otherwise nil will be returned, and the method will evaluate do false.
However, as the item return could be an false or nil (if any item in the list is false or nil) I would recommend that you use the .any? method instead.
def array_of_fixnums?(array)
array.any? { |item| item.class != Fixnum }
end
Let's look at why true unless condition works as it does.
I believe x unless condition was provided as an esthetic improvement to x if !condition, but the two are equivalent (and, in my opinion, it is an improvement). To get rid of double-negatives, I'll just consider x if condition.
One of Ruby's basic tenets is that every statement must return a value (namely, the last expression evaluated). Therefore, x if condition must be evaluated as if condition then x else y end. But what is y? If y evaluated as true or false, x if condition would be meaningless when x is a boolean expression. To see why, consider:
w = (x == 5 if z == 6)
If w were true, we could conclude that x = 5 and z = 6, but if w were false, we wouldn't know whether z = 6 and x != 5, or z != 6 and x = 5 or x != 5. Considering that any value of y other than nil is evaluated as true or false, the only reasonable value for y is nil. That way, if w == nil is be available. Of course, this is still a problem (in other situations) when x could possibly be nil.
I would welcome clarification and elaboration. Perhaps there is also a less convoluted way of making this argument.
Using Ruby I want to evaluate all items in an array, and return true if they all pass a conditional test.
I can do this using e.g. array.all? { |value| value == 2 }
So:
> array=[2,2]
> array.all? { |value| value == 2 }
=> true
> array=[2,3]
> array.all? { |value| value == 2 }
=> false
Great!
But, why does an empty array pass this test?
> array=[]
> array.all? { |value| value == 2 }
=> true
Shouldn't this return false?
And if I need it to return false, how should I modify the method?
This is a vacuous truth. It's the standard interpretation of a universal quantification, i.e. a
collection.all? { |x| some_predicate(x) }
over an empty collection, but it's known to strike people as counter-intuitive when they first see it in a formal setting. One nice way to think about why this is the preferred semantics is to think about how you would implement all?.
To make your test require that the array is non-empty, just do
array.any? && array.all? { |x| x == 2 }
Note that array.any? is fast no matter how large the array, whereas array.all? { |x| x == 2 } can be slow, depending on how big array is and how rare 2 is in it. So put the array.any? first.
Also note, there are degenerate cases where this won't work, for instance if array is [nil] or [false]. If cases like this might come up, replace array.any? with array.any? { true }.
In Ruby you can never loop over an empty collection (array, hashes, etc.), so in your case your block never gets executed. And if the block never gets executed, all? returns true (there is no condition to make the result false).
Read about all? in the Ruby documentation.
You can simply achieve your goal by
!array.empty? && array.all? { |value| value == 2 }
The documentation says : "The method returns true if the block never returns false or nil.."
In the case of an empty array the block never executes and hence the method will always return true. As far as returning false is concerned you'll have to arr.empty?
There is no item in that array that doesn't pass the test. I think you may need to throw in a test for array length.
Just go
!(array.empty? || array.any? {|x| x != 2})
(Which has the added advantage of failing fast—that is, it can be evaluated properly without having to scan the whole array.)
Since there is no item in the array that FAILS that test, it returns true. So just use somehting like:
array.size > 0 and array.all? { |value| value == 2}
Or something like that.
Zeroes, empty collections, empty matrices and such have always been a bit special, if not outright problematic. Greeks knew well why they didn't count 0 among natural integers.
Method all? would be the first to ask you "why are you calling me on an empty array?" What do you mean by "all?", when there is nothing in there? That's a contradiction. And the method does short thinking, and answers true for the reasons outlined in the other three answers. Remember, you are at fault for talking about "all elements" of an empty array to begin with.
As Amit Kumar Gupta writes, it is the standard interpretation of universal quantification. I have no idea why you expect it to be false. Here, you can see it should be true by inference.
Universal quantification is equivalent to conjunction, thus ("<=>" means equivalent):
"for all x in [a, b, c], P(x)" <=> "P(a) and P(b) and P(c)"
Notice that any proposition is equivalent to the conjunction of true and itself, so:
"for all x in [a, b, c], P(x)" <=> "true and P(a) and P(b) and P(c)"
If you lessen the elements in the set to two, you get:
"for all x in [a, b], P(x)" <=> "true and P(a) and P(b)"
and further to one element:
"for all x in [a], P(x)" <=> "true and P(a)"
Now, what happens with the empty set? Naturally,
"for all x in [], P(x)" <=> "true"
By noticing that existential quantification is equivalent to disjunction, you can also see that you should expect false with existential quantification over an empty set.
The source of all? method says that it uses static variable(which is initially set to true) and then performs the AND operation between the static variable value and the result of the iteration finally returns this static variable as a result.
as the array is Empty ruby will never iterate on this empty array and as a result of this all? method will return the static variable which was set to true.
Make sure the array is not empty first.
Then:
array.compact.present? && array.all? {|x| x != 2}
Is there a simple way in Ruby to get a true/false value from something without explicitly evaluating it to true or false
e.g. how would one more succinctly express
class User
def completed_initialization?
initialization_completed == 1 ? true : false
end
end
is there some way to do something along the lines of
initialization_completed.true?
There's obviously not much in it but since I'm in the zen garden of Ruby I might as well embrace it
EDIT (I've updated the example)
This question was extremely badly phrased as was very gently pointed out by #Sergio Tulentsev. The original example (below) does of course evaluate directly to a boolean. I'm still struggling to find an example of what I mean however Sergio's double-negative was in fact exactly what I was looking for.
Original example
class User
def top_responder
responses.count > 10 ? true : false
end
end
> operator already returns boolean value. So it can be just
def top_responder
responses.count > 10
end
To convert arbitrary values to booleans, I offer you this little double-negation trick.
t = 'foo'
!!t # => true
t = 1
!!t # => true
t = 0
!!t # => true
t = nil
!!t # => false
The first negation "casts" value to boolean and inverts it. That is, it will return true for nil / false and false for everything else. We need another negation to make it produce "normal" values.