There aren’t too many things that bring me down when I’m deep into programming. But there’s always one thing that’ll stop me dead in my tracks: a code smell. In the context of programming, a code smell is something that tells you that your code is…well, a bit off.
Whether you’ve been programming for months or for decades, you’ll run into “code that smells” again and again. Except the better that you get, you’ll anticipate your code smelling, or before you even write it, you’ll know that it’s going to stink. For me, a lot of my code that tends to smell are sections where I’ve duplicated what I’ve written, which is to say that I’ve written something that’s identical or at least very, very similar to another piece of code in my application. I’ve noticed that I’ve started catching myself as I write duplicated code, which is a sign that I’m getting better as a developer – hooray!
But, there are a lot of common code smells, and I definitely still can’t catch all of them in my own code. Generally, if any part of your program has a common code smell, it’s a sign that you need to rethink how your system is structured on a deeper level, and that it’s probably time to start refactoring.
Recently, I’ve noticed that I’ve been sniffing out the same issue in my code: long methods. So I did a bit of investigating and found that there are some cool ways to shorten up your longer methods. Thanks to Ruby magic, we have easy access to methods like to_proc
in the form of “ampersand and object”, or the &: syntax. If you have no clue what those are, don’t worry. I didn’t either! Until I wrote this post, obvs.
A Slim Method Is A Beautiful Method
In our eCommerce bookstore app, we’re storing our Order
amount
totals as BigDecimals. You might remember why BigDecimals are better than floats when it comes to performing accurate monetary calculations. But even though we’re using BigDecimal on the backend, there may be times when we want to convert our decimals back into floats. Like what if we want to start integrating a JavaScript frontend? (yes, I went there). The point is, we should be able to do something like that pretty easily, especially if we wanted to serialize and return a JSON object that has the correct object type.
We’d probably start by iterating through all of our order amounts and turn them into floats, like this:
1 2 3 4 5 6 7 8 9 |
|
Well, it does the job. But…it doesn’t look so great, does it? Let’s cut it down:
1 2 3 4 5 6 7 |
|
Okay, let’s make it a little fancier and throw in the pluck
method, which will query for only the to grab only an Order
’s amount
?
1 2 3 4 5 6 7 |
|
I guess it’s better, but still not as good as it could be. Are you thinking what I’m thinking? There’s gotta be a better way!
A Proc And An Object Walk Into A Bar…
As is the case with most things in programming, if we wish for a better way to do something, we can generally assume that there is! And in our case, the better way to do it is by using something called ampersand and object.
And we can implement it on our amounts_to_floats
method like so:
1 2 3 4 5 6 7 |
|
The result of this will be the exact same thing as the map
we wrote above:
1 2 |
|
So, what happened here? Well, let’s just start with what we know for sure.
We took all the
Order
objects and queried for just theiramounts
, which are inBigDecimal
format.The
pluck
method returns an array of the attributes that you query for, so, which would mean that our array would look something like this:[#<BigDecimal:7f87ed12b2f0,'0.21099E3',18(18)>, #<BigDecimal:7f87ed121700,'0.1505E3',18(18)>, #<BigDecimal:7f87ed119348,'0.2499E2',18(18)>...]
Okay, so then we mapped over all of these amounts, right? And somehow we called
.to_f
on all of them. Which returned the array that we were hoping for:[210.99, 150.5, 24.99, 391.99, 120.25]
All of this begs one question: how on earth did the &
(ampersand) know to call to_f
on each of our objects? And how did map
know what to do with the ampersand that we passed it?
Time to find out.
Procs on Procs
Whenever something seems super daunting, I go back to the basics. Which is exactly what I think we should do here, too. We can all agree that in Ruby is an object, right? And methods are how we send messages to objects. With that in mind, let’s look back at that confusing line of code:
1
|
|
We know Order.pluck(:amount)
to be an array – it’s an object, which means it’s the receiver of our method. That means that .map
is our method. And map
has been passed &:to_f
, which seems to have replaced the usual do end
or {}
blocks that generally accompany the map
iterator.
At this point, if you’re thinking that the &
ampersand is responsible for executing that to_f
method on each of the elements of the array, you’re definitely onto something.
In fact, the &
calls another method on the object: to_proc
. The ampersand calls to_proc
on whatever comes after it. In our case, it’s the to_f
. But it’s not just the plain old method to_f
– it’s actually the symbol, :to_f
.
And we can double check this by looking at the Ruby docs! The to_proc
method is defined on the Symbol
class:
to_proc
Returns a Proc object which responds to the given method by sym.
So all the &
is doing is: :to_f.to_proc
. It turns the symbol into a proc, and whatever object you pass to it, it’ll call that method on that object. You’re basically telling the map
function, Hey, I already have this method I want to call on every single item you’re iterating over. So when you invoke yourself on the receiving object, run this code that I’m passing you.
Because :to_f
isn’t actually a block, the ampersand tries to make it a block by calling to_proc
on it. This means that you can pass any method to &
in symbol form, and it will convert it to and invoke that proc.
But what if you didn’t want to pass it a defined method in Ruby as a symbol? What if, instead, you wanted to write your own block and pass it to &
explicitly? That would look something like this:
1 2 3 4 |
|
Pretty cool, right? In this case, we created a half_off_sale
proc, that we passed directly to the ampersand. The &
didn’t have to make it a proc (meaning that it didn’t have to invoke to_proc
) since we already passed it one. So when map
iterated through all the amounts, it yielded to our half_off_sale
proc, and ran that code on every single element in our array of amounts.
As you can see, the ampersand and object and to_proc
methods can be pretty powerful – particularly when they’re combined into this handy little shortcut! That doesn’t mean you have to use them all the time, but if you understand the fundamentals of how they work, they can really help clean up your code, especially when it’s pretty smelly.
This is just a primary introduction to the concepts of ampersand and object and to_proc
. There’s definitely a lot more to them, and they can get quite complicated. I’m still learning about them myself! But, I’m going to keep reading about how procs work and, hopefully, there’ll be a forthcoming Technical Tuesdays post about how to pass methods to procs! Until then, keep sniffin’ out those code smells, my friends!
tl;dr?
- The
&
(ampersand) can be used with an iterator likemap
, and will callto_proc
on whatever symbol you pass it, unless you pass it a block explicitly. - More examples and explanations on these two StackOverflow posts.
- Here’s a great blog post on all the ways you can use
&
to manipulate ActiveRecord models. - If you’re still curious about different ways to pass around procs, check out this post, which is what I used when I was learning about them!