Tip #13 - BangBang Transformations!
April 22nd, 2008
Ruby is marvelous, everything evaluates. Which means a lot of the time, you can get away with things like ‘if @user…” and just depend on the existence of the @user var. But what if you just really need a Boolean true or false? Here is a little pattern you can use to do this…
Where two wrongs make a right?
Ruby you have the case that EVERYTHING except nil and false are “truthy” (which means, they evaluate to true in a condition).
So that is why something like this can work in Ruby:
irb(main):001:0> user = "Mikel"
=> "Mikel"
irb(main):002:0> if user
irb(main):003:1> puts "Hello #{user}"
irb(main):004:1> end
Hello Mikel
But the “value” of users in that if statement is still ‘Mikel’
What if you had a method in a rails model like this:
1 2 3 |
def logged_in? true if logged_in_date != nil end |
That says “Return true if the logged_in_date is not nil, otherwise return nil.
The problem with this, is that if the logged_in_date is already nil, the method will return nil, not false. And nil does not equal false in all cases.
A better way to write this would be to use the logical NOT operator, you might know that a ! in front of any variable is a logical NOT operator. This means, it takes whatever the value is and returns the opposite as a true or false. So we get stuff like this:
irb(main):001:0> true => true irb(main):002:0> !true => false irb(main):003:0> 1 == 1 => true irb(main):004:0> !(1 == 1) => false
Easy enough.
So NOT true equals false. But NOT NOT true equals true. And NOT NOT (anything except nil and false) equals true. And NOT NOT anything that is nil or false equals false. Confused? Let me show you the same “logged_in?” method with some ruby goodness:
1 2 3 |
def logged_in? !!logged_in_date end |
What this now says is that if the logged_in_date is nil, then apply NOT to that (which gives you true) and apply NOT to that (which will give you false) so in that case it will return false, which is what you want!
On the other hand, if logged_in_date is ANY value (string, date, anything) then you apply NOT to that and you get false, apply NOT again and you get true. Which is also what you want.
So instead of returning true or nil, that method will now return true or false, just as we expect.
Not bad for two exclamation marks!
blogLater
Mikel
May 13th, 2008 at 03:14 AM
what is wrong with:
def logged_in? logged_in_date != nil end
logged_in_date != nil is something whose value is false if logged_in_date is nil, and true otherwise, so why not just return it. And it seem so more logical and simple to me than this not not.
Well there is a difference between foo != nil and !!foo when foo’s value is false : - the first one is useful when you want to check if the value is initialized, because if its value is false, this mean it have been initialize, and then false is different from nil - the second one test the truth value of foo
May 13th, 2008 at 08:20 AM
Exactly, this method handles it if it is false or nil and always returns true or false.
Mikel
May 16th, 2008 at 10:39 PM
I’m still digesting the !! example, but it seems a bit unnecessary. we love Ruby b/c the first example does work. clients can faithfully check for authenticated users with “do_something if user.logged_in?” b/c false and nil are interchangeable for just this type of scenario.
so, other than possibly changing the line to “true unless logged_in_date.nil?”, I think I’d use it over a double-negation solution.
still a good tip and I’ll probably end up using b/c it does seem “smarter” :)
May 17th, 2008 at 10:21 AM
Luke: I know what you mean. But sometimes (not just logged in?) maybe also checking to see if an attribute or value is set or not.
.blank? does a good job on this though, but then you have to call blank? every where :) Plus, it is a lot more typing :)
Mikel