The Billy Baldwin of Conditional Assignment
For those of you who don’t know, Billy Baldwin is the lesser-known and generally-less-useful little brother of famed actor Alec Baldwin.
In the world of Ruby’s conditional assignment operators, ||=
is Alec Baldwin; charming and versatile. But not many people know about ||=
’s little brother… the &&=
operator.
Meet Billy
The ||=
conditional assignment operator is popular in Ruby and for good reason. One common usage is for simple memoization. Ryan Bates outlined this technique in his very first RailsCast.
def current_user
@current_user ||= User.find(session[:user_id])
end
The purpose here is to avoid re-finding the user every time the current_user
method is called. The ||=
operator above has the same effect as:
def current_user
@current_user = @current_user || User.find(session[:user_id])
end
If @current_user
is both defined and truthy (not false
or nil
), that value is simply returned. Otherwise the instance variable is set and returned.
Enough about Alec. What about Billy?
The magic of the &&=
operator is that it only sets variables that are… already set! The usefulness of this may not be immediately apparent, but maybe you’ve written code like this:
class Article < ActiveRecord::Base
validates :title, :body, :presence => true
before_save :clean_summary
private
def clean_summary
self.summary = summary.squish
end
end
The problem with this code is that the article summary isn’t required. What if an article’s summary is nil
? We’ll get a NoMethodError
trying to call squish
1 on nil
. Oftentimes, the fix looks like this:
def clean_summary
self.summary = summary && summary.squish
end
which looks eerily similar to our current_user
memoization expansion. So instead, try:
def clean_summary
self.summary &&= summary.squish
end
Sure, it’s a bit of a one-trick pony, but sometimes that one trick is exactly what you need. Have you ever seen Backdraft? Every dog has its day.
1 String#squish changes consecutive whitespace to single spaces.
Comments
Otherwise: thanks for the reminder!
Florian: Absolutely right, thank you! I’d originally written the current_user expansion just like that but ultimately wanted to keep the effect clearer for the sake of comparison to &&=.
First, hilarious title / comparison
Second, I wasn’t aware of this, thanks for sharing!
Dan
You can also use the xor one for :
a ^= true # true
a ^= true # false
a ^= true # true
a ^= true # false
&&= chaining is actually almost like a limited “Maybe Monad” for Ruby. It’s really great when you’re dealing with a long string of computations that may fail, you can just write them in a really naive fashion and avoid writing if(x != nil) guards.
I think this syntax is more clear:
self.summary = summary.squish if summary.present?
But I hadn’t seen the squish method, very cool!
You can also use && in a similar way to mimic #try:
name = user && user.name
In rails you can just do:
self.summary = summary.try(:squish)
But yes, quite good :)
Fun! How about this?
array_of_methods.inject(obj, :try)
In Objective-C just go ahead and call summary.squish (actually [summary squish]). If summary is nil, it will do nothing. No need to pre-test if (summary != nil) or have null pointer exception handling. Surprising how useful this behavior actually is and how quickly you accept it.
Heya pretty much started programming in the ruby programming
language so I am quite a bit of a newcommer!
However, have found your internet site very interesting and instructive.
Appreciate it!
Thanks for share such a useful information with us. Keep sharing such kinds of posts. I really like reading this one.