Level Up Your Ruby Skillz: Writing Compact Code 👏👏👏

Level Up Your Ruby Skillz: Writing Compact Code 👏👏👏

Level Up Your Ruby Skillz: Writing Compact Code - The following article will take your skills to the next level and learn how to deal with applications that must operate under large loads... 🚀🚀🚀🚀🚀

We have all been there, we issue a Pull Request(PR) that we feel is super tight and awesome. It gets stamped "Approved", but there is a catch. Our code isn't actually as tight as we thought. We open up our PR to a bunch of very nice suggestions on how we could tighten up our code even more.

This is VERY common when you are starting out and it is actually how I learned many of these tricks. Thanks to a lot of very good senior devs reading and commenting on my code from the very beginning, I have learned how to write pretty succinct code. But I will let you in on a secret, it never ends. I have been doing this for 7 years and I STILL get suggestions for how I can write better code.

Below are some common ways that you can take basic Ruby logic and tighten it up with some of the syntactic sugar. Syntactic sugar is syntax within a programming language, like Ruby, that is designed to make things easier to read or to express.

  • Booleans
  • Variable Assignment
  • Guard Clauses
  • Calling Methods
  • Safety Navigator
  • Hashes and Arrays

Booleans

Ternary Operator

Let's start with some basic boolean logic in order to set a variable. If y is even we want to set x equal to 1, if y is odd we want to set x equal to 2. Here is how we are taught to do it when starting Ruby.

if y.even?
  x = 1
else
  x = 2
end

The above gives us two assignment statements. This means if we have to change our variable x, we have to make the change in two places, which is not ideal. One way we could simplify the code is by doing this:

x = if y.even?
  1
else 
  2
end

Now, we only are making our x assignment once, which is a little more simple. But even with the above, we are taking up 5 lines to do it. Let's see how we can take it down to one line.

x = y.even? ? 1 : 2

? is called a Ternary operator. The Ternary operator logic uses "(condition) ? (true return value) : (false return value)" statements to shorten your if/else structure. It first evaluates an expression as a true or false value and then executes one of the two given statements depending upon the result of the evaluation. Here is the syntax:

test-expression ? if-true-expression : if-false-expression

This can be extremely useful when it comes to tightening up logic statements.

Inline Booleans

In addition to the slick ternary operator, let's look at another way we can condense some boolean logic into a single line. Here is a basic method with some boolean logic.

def say_number_type(number)
  if number.even?
    puts "I am even!"
  end
end

To make this more compact we can write that boolean like this:

def say_number_type(number)
  puts "I am even!" if number.even?
end

The same can be done with unless

def say_number_type(number)
  puts "I am even!" unless number.odd?
end

Variable Assignment

Let's say we have a scenario where we want to assign a variable to another based on if the other variable is present. For example, we have variable aand b below. If a is present(not nil) we want to set x equal to a. If a is nil, we want to set x equal to b. We could do something like this.

if a.nil?
  x = b
else 
  x = a
end

Or, given what we learned above we could do

x = a.nil? ? b : a

However, when dealing with possible nil values we can make this even more succinct by using the pipe operator like this:

x = a || b

If a is not nil, that assignment statement will assign the value of a to x. If a is nil, it will assign the value of b to x. Checkout the console examples below.

irb(main)> a = 1
=> 1
irb(main)> b = 2
=> 2
irb(main)> x = a || b
=> 1

irb(main)> a = nil
=> nil
irb(main)> x = a || b
=> 2

We can use the pipe operator between two variables and we can use it with an equals. Let's say we want to set x equal to a but only if x is nil. If x is not nil we want to preserve the value of x. In order to accomplish this we would do:

x ||= a

Here is a console example showing exactly how this works:

irb(main)> x = 1
=> 1
irb(main)> a = 2
=> 2
irb(main)> x ||= a
=> 1
irb(main)> x = nil
=> nil
irb(main)> x ||= a
=> 2

This is telling ruby to assign the value of a to x only if x is nil. This can be helpful when you are expecting a default but can't rely on it being there. Let's say you have the below method

def add_one(number)
  number + 1
end

If number is nil then we will get a NoMethodError when we try to add one to it. We can avoid raising an error by doing something like this

def add_one(number)
  number ||= 0
  number + 1
end

number ||= 0 will ensure that if number is not defined our method will not raise an error because it will be set to 0. If you want to take this one step further, you can put the assignment operator IN with the argument! 🤯

def add_one(number = 0)
  number + 1
end

When you are calling this method, if you do not pass it a number, ruby will set number equal to 0.

irb(main)> add_one
=> 1
irb(main)> add_one(1)
=> 2

Guard Clause

A Guard Clause is a premature exit from a method if certain conditions are met. Guard clauses can help you condense your method logic and write more optimized code. Let's look at an example of when we might use one. Say you have the below method tester

def tester(arg)
  if arg == 1
    # logic
  else
   'error'
  end
end

In this case, if our argument, arg, is not equal to 1 then we will return a string that says 'error'. We can simplify this tester method by using a guard clause which is shown below.

def tester(arg)
 return 'error' unless arg == 1 # Guard Clause
 # logic
end

When we start our method we immediately check if the value of arg is equal to 1. If it is, we return immediately. No need to fumble around with everything else in the method since we know what we want to do. This can be especially helpful when it comes to optimizing code. For example, keep an eye out for situations like this:

def tester(arg)
  user = User.find(123)
  event = Event.find(123)

  if arg == 1
    # logic
  else
    'error'
  end
end

Here, we are gathering data and executing logic before we even get to our if/else boolean. In the end, if arg is not equal to 1, then we just wasted a lot of time and resources gathering data we didn't need. A guard clause can ensure that none of these resources are wasted by checking your condition in the first line of your method.

One more tidbit I want to add is if you want to use a guard clause to return nil based on a condition, you can do this:

def tester(arg)
  return unless arg == 1
  # logic
end

In the above case, return alone is the same as if you wrote return nil.

def tester(arg)
  # nil is unnecessary 
  return nil unless arg == 1
  # logic
end

In Ruby, however, you don't need to explicitly define nil in this case. The first example, return unless arg == 1 is how you will want to write it and how you will likely see it written by others.

Calling Methods

Let's say you are working with a set of objects that respond to a method and you want to collect the result of that method using a method like map. Normally, you would do something like this:

a = [1, 2, 3, 4]
string_list = a.map{|obj| obj.to_s}
# string_list = ["1", "2", "3", "4"]

We can simplify this even more using a little syntactic sugar and still get the same result.

string_list = a.map(&:to_s)
# string_list = ["1", "2", "3", "4"]

Any method that our set of objects, in this case integers, respond to can be called on each object using &:method syntax. If you want to understand what Ruby is doing under the hood, I recommended giving this blog post a read.

Safety Navigator

How many of you have written code like this before? ✋ I definitely have!

def friends_name(user)
  return unless user.present?
  friend = user.friend
  if friend.present?
    friend.name
  end
end

There is a lot of presence checking going on here just to ensure we are never calling a method on a nil object and getting the dreaded # NoMethodError. Rather than doing all of this checking, we can use Ruby's Safety Navigator! The Safety Navigator will simply return nil if the method is called on a nil object. This means we can simplify our method to this:

def friends_name(user)
  user&.friend&.name
end

If any object in that chain is missing we will simply get nil back. Here is another simple console example that will hopefully help further clarify how the safety navigator works.

irb> [].last.to_s
=> ""
irb> [].last.even?
NoMethodError: undefined method `even?' for nil:NilClass
    from (irb):3
    from /usr/bin/irb:11:in `<main>'
irb> [].last&.even?
=> nil

Hashes and Arrays

Brackets vs do/end

In my Hashes and Arrays tutorials I chose to use a lot of do/end block notation to help make it clear what logic was taking place. However, bracket notation is a great way to write more compact code. As we saw in the tutorials, it is a great way to chain together a bunch of logic.

result = [1, 2, 3, 4].map{|n| n + 2}.select{|n| n.even?}.reject{|n| n == 6} 
# result = [4]

Now you might be thinking given all these options, how do I know what to use?! Usually, a good rule of thumb is:

  • If one line of code is being executed in the block then use brackets.
  • If your logic is more than one line, use do/end notation.

Single line bracket example:

a = [1, 2, 3]
a.map{|number| number + 10}

Multi line do/end example:

a = [1, 2, 3]
a.map do |number|
  if number.even?
    puts "I am even"
    number/2
  else
    puts "I am odd"
    number
  end
end

Keep in mind that some places or people might have a different style. Always be aware when you are starting at a new place what others are doing. You will likely want to follow their lead when you are starting out. As you gain more experience you will develop your own style and habits.

Chaining

Really quick I want to loop back to the chaining example I used above:

result = [1, 2, 3, 4].map{|n| n + 2}.select{|n| n.even?}.reject{|n| n == 6} 
# result = [4]

This code is pretty straight forward, which is why chaining each step is a great choice to make it more compact. However, there will be times you actually might want to split them up for readability if the logic in each step is complicated. Here is an example of how you would split it up for readability.

plus_two_array = [1, 2, 3, 4].map{|n| n + 2}
even_numbers_array = plus_two_array.select{|n| n.even?}
remove_the_sixes_array = even_numbers_array.reject{|n| n == 6} 

Each step and its result are clearly defined which can make more complicated logic easier to follow and understand. The reason I want to point this out is because there will be times when writing the most compact code is not best for a situation. All these strategies are great, but be aware there will be times when you might want to sacrifice compact code for readability.

But Wait, There's More!

Obviously, there are tons more ways you can write more concise and compact code. This is only a very small list, but I think it is a great place to start. If you have found other tricks that have helped you improve your code feel free to drop them in the comments. As always, if you have questions or anything needs clarification please don't hesitate to ask!

Originally published by **Molly Struve ***at *dev.to

==================================================

Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter

Ruby on Rails REST API: The Complete Guide

How To Install Ruby On Rails On Windows

The Complete Ruby on Rails Developer Course

Learn Ruby on Rails from Scratch

Testing Ruby with RSpec: The Complete Guide

Build a Crypto Currency Portfolio App With Ruby on Rails

Ruby On Rails Web Development: To-Do List App

Advanced Ruby Programming: 10 Steps to Mastery

Complete Ruby Programmer

Angular 9 Tutorial: Learn to Build a CRUD Angular App Quickly

What's new in Bootstrap 5 and when Bootstrap 5 release date?

Brave, Chrome, Firefox, Opera or Edge: Which is Better and Faster?

How to Build Progressive Web Apps (PWA) using Angular 9

What is new features in Javascript ES2020 ECMAScript 2020

Ruby on Rails vs PHP

Understanding the pros and cons of Ruby on Rails versus PHP is important when deciding how to create your business-critical applications.