In Ruby On Rails By Leigh Halliday December 02, 2014 Leigh Halliday

Confident Ruby by Avdi Grimm reviewed

Overview

Confident Ruby by Avdi GrimmConfident Ruby by Avdi Grimm is a book about taking sheepish and nervous Ruby code and transforming it into confident and straight forward code. It's about focusing on the task at hand in front of you, rather than looking over your shoulder to make sure nobody is sneaking up behind you.

I not only learned a great deal from this book, but I have a new appreciation for organizing the code within methods, and find myself already reaping the benefits from having read it.

Avdi breaks apart a typical method into 4 parts: Collecting input, performing work, delivering results, and handling failure. I'll pick one example from each of the 4 sections to discuss and show an example of what they entail.

Collecting input

This book spends the vast majority of its pages discussing the collection of input. This should be the first part of a method, and is what sets the stage for what follows. If you find yourself checking whether something is nil all the time, or whether it is a string or an array, it could signal that it wasn't dealt with properly at the beginning.

Imagine you have a method that counts the number of emails passed into it. They could arrive as an array, but what if the input comes as a string such as "[email protected]"?

Instead of checking whether the value is an array or a string, using ruby's implicit array conversion cleans this up and allows you to confidently call the Array#size method.

def num_emails(emails)
  Array(emails).size
end

If an array is passed in, it will stay as an array. If a string is passed in, it will be converted to an array with 1 value.

Performing work

This is where the meat of the method is, and what your method's job is. If your method is about parsing a row of CSV data, then that is the what the bulk of the work of your method will be about. That being said, this section of the book is no more than a few pages, and that is because most of the time you already know what work needs to be done, and the problems arise mostly out of not doing the other 3 parts of your method well.

Delivering results

In this section of the book, Avdi Grimm discusses how to return values back to the calling method. I'll quickly show 2 examples of some methodologies he uses.

Represent failure with benign values

Most of the time when you are working with methods, they are receiving values and also returning them back to the caller. If you look at the num_emails method from above, it receives a value called emails, and then is expected to return the number of emails it has received.

But what if the value nil is passed to it? How many emails are in that value? You may consider returning false, or returning nil back, or maybe even raising an exception, but then the caller must be aware that it could receive those values on top of the usual integer value, and it will have to handle those use cases or risk blowing up. In this case, by returning a benign value, 0, you are returning a value that won't hurt anyone, and in fact, makes their lives easier.

By using the Array() conversion method, and then rejecting blank values, both "" and nil will end up as empty arrays, and therefore return the benign value 0.

def num_emails(emails)
  Array(emails).reject{ |email| email.to_s.strip.empty? }.size
end

Call back instead of returning

This technique involves using a callback method instead of returning true or false as a value. Say you are importing a CSV file of new users into the system. You could write a method like this, and when true look up the user in the system to send them a welcome email.

def import_user_row(user_data)
  if valid?(user_data)
    user = insert_user(user_data)
    true
  else
    false
  end
end

Which would need to be called like this:

if import_user_row(user_data)
  user = somehow_find_last_user
  send_welcome_email(user)
end

But why not pass a block to the method which is called when the user is imported successfully?

def import_user_row(user_data, &user_callback)
  if valid?(user_data)
    user = insert_user(user_data)
    user_callback.call(user)
  end
end

This allows you to call the method like so, sending a block which handles what to do on a successful user import:

import_user_row(user_data) do |user|
  send_welcome_email(user)
end

Handling failure

In this section of the book, Avdi discusses what to do when things go wrong. One quick takeaway is to make the narrative of your code more about the work the method needs to perform, rather than the extra code handling failure. One way to easily do this in Ruby is to use the top-level rescue clause rather than explicitly writing out the whole begin/rescue/end syntax.

def take_care_of_business
  # taking care of business
rescue
  # when things go wrong
end

Although minor, it cleans up the method and makes it easier to figure out what is happening at a glance.

Takeaways

Confident Ruby ended up giving me 2 things. It gave me a higher level of appreciation on how to organize code inside methods; how to make methods organized in such a way that they portray a clean and clear narrative of what is happening within them. It also taught me a number of tricks and idiomatic ways in Ruby to make best use of the language. I'd highly recommend Confident Ruby, by Avdi Grimm to anyone looking to improve the way they write code.