/ ruby

Ruby Tips for Beginners

Ruby can be a bit intimidating for newcomers. The standard library is huge and the syntax provides many ways to do simple things. Give the same task to 5 new ruby developers and you'll get 5 different solutions. But if you're just starting out with a new language it's usually focus you're after, not variety. Here are some ways to find it when making your first steps in ruby.

Use Rubocop

First and most importantly you need someone (or even better something) to tell you about the better, more "ruby" ways to write code. That something is called rubocop.

Rubocop is a static code analyzer that uses a rules file to find bad code and suggests improvements. It integrates well with IDEs and can also run from command line.

I'll share my rubocop settings in just a moment, but before that let's have a glimpse at its output. Here's an example ruby code that reads a number from the user and prints the sum of its digits:

# WARNING Bad code - Don't copy-paste it
while true
  puts "Please select a number or type e to exit"
  userInput = gets.chomp

  if userInput == "e"
    break
  else
    sum = 0

    userInput.each_char.map {|c|
      sum += c.to_i
    }

    puts "Sum of digits of #{userInput} is #{sum}"
  end
end

Now see what rubocop has to say on the above code:

Inspecting 1 file
W

Offenses:

a.rb:1:1: C: Use Kernel#loop for infinite loops.
while true
^^^^^
a.rb:1:7: W: Literal true appeared in a condition.
while true
      ^^^^
a.rb:2:8: C: Prefer single-quoted strings when you don't need string interpolation or special symbols.
  puts "Please select a number or type e to exit"
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
a.rb:3:3: C: Use snake_case for variable names.
  userInput = gets.chomp
  ^^^^^^^^^
a.rb:5:3: C: Use a guard clause instead of wrapping the code inside a conditional expression.
  if userInput == "e"
  ^^
a.rb:5:19: C: Prefer single-quoted strings when you don't need string interpolation or special symbols.
  if userInput == "e"
                  ^^^
a.rb:10:29: C: Avoid using {...} for multi-line blocks.
    userInput.each_char.map {|c|
                            ^
a.rb:10:29: C: Space between { and | missing.
    userInput.each_char.map {|c|
                            ^^

1 file inspected, 8 offenses detected

Nice! let's go over the offenses list and see what we can learn.

Using Kernel#loop for infinite loops

The Kernel module is included by class Object, so its methods are available in every Ruby object. Its documentation is available here:
https://ruby-doc.org/stdlib-2.4.2/libdoc/open-uri/rdoc/Kernel.html

The loop function takes a block and Repeatedly executes it.

Using Kernel#loop for infinite loops instead of while true is more specific and shows better style. The rule is taken from ruby's style guide which you can read here:
https://github.com/bbatsov/ruby-style-guide

After changing we get:

loop do
  puts "Please select a number or type e to exit"
  userInput = gets.chomp

  if userInput == "e"
    break
  else
    sum = 0

    userInput.each_char.map {|c|
      sum += c.to_i
    }

    puts "Sum of digits of #{userInput} is #{sum}"
  end
end

Quoting Style

Moving on in the style guide we find the following quoting style recommendation: "Prefer single-quoted strings when you don't need string interpolation or special symbols"
(There's actually an alternative option, but I prefer this one).

This rule is easy to follow and improves readability of our code, since we can easily understand the intention of the author.

Fix and get:

loop do
  puts 'Please select a number or type e to exit'
  userInput = gets.chomp

  if userInput == 'e'
    break
  else
    sum = 0

    userInput.each_char.map {|c|
      sum += c.to_i
    }

    puts "Sum of digits of #{userInput} is #{sum}"
  end
end

Snake Case in Variable Names

Moving from Java, JavaScript or C++ ? You should know that rubysts prefer the snake case naming convention. Most ruby libraries you'll use and most language built in functions follow this convention.

Fix and we get:

loop do
  puts 'Please select a number or type e to exit'
  user_input = gets.chomp

  if user_input == 'e'
    break
  else
    sum = 0

    user_input.each_char.map {|c|
      sum += c.to_i
    }

    puts "Sum of digits of #{userInput} is #{sum}"
  end
end

Guard Clauses

A guard clause is a short if statement that'll leave the block in one branch. Its main use is to un-indent the code inside the if. In the example code rubocop noticed the main code of the block is inside if, but the true branch just breaks out of the block. So we can restructure the above and get more aligned code:

loop do
  puts 'Please select a number or type e to exit'
  user_input = gets.chomp

  break if user_input == 'e'
  sum = 0

  user_input.each_char.map {|c|
    sum += c.to_i
  }

  puts "Sum of digits of #{userInput} is #{sum}"
end

Using The Right Block Sign

Ruby has {...} and do...end to denote blocks. By convention the first is more suitable for one-line statements and the second for multi-line blocks.

Fixing the last rubocop error will therefore lead us to:

loop do
  puts 'Please select a number or type e to exit'
  user_input = gets.chomp

  break if user_input == 'e'
  sum = 0

  user_input.each_char.map do |c|
    sum += c.to_i
  end

  puts "Sum of digits of #{userInput} is #{sum}"
end

Which looks much nicer than the initial version. And this was practically a free upgrade: Just do what rubocop says.

By listening to rubocop and studying what each comment means we can improve almost any ruby code.

Searching For Shortcuts

Ruby has great support for shorter versions of code snippets, and it's well worth looking for them.

In the above example converting all characters to numbers and then calculating their sum total is a simple case of map and reduce:

loop do
  puts 'Please select a number or type e to exit'
  user_input = gets.chomp
  break if user_input == 'e'

  sum = user_input.split('').map(&:to_i).reduce(0, &:+)
  puts "Sum of digits of #{user_input} is #{sum}"
end

Now I assume many developers new to ruby would argue that the previous version, with the variable sum explicitly calculated, is easier to read and maintain. I can see why you'd think that. Ruby's shortcuts seem confusing at first but the more you use them the more you'll get used to them. This is both required (if you ever intend to read other people's code) and useful when writing and maintaining your own.

Installing Rubocop

Rubocop is installed just like any other ruby library as a gem:

gem install rubocop

Afterwards you'll want to create a .rubocop.yml file in your project's root directory or in your homepage to specify which rules and rule parameters you want to use. Here's my .rubocop.yml file which you can use as a starter:

Rails:
  Enabled: true

AllCops:
  TargetRubyVersion: 2.3

# Commonly used screens these days easily fit more than 80 characters.
Metrics/LineLength:
  Enabled: false

# Too short methods lead to extraction of single-use methods, which can make
# the code easier to read (by naming things), but can also clutter the class
Metrics/MethodLength: 
  Max: 30

Metrics/AbcSize:
  # The ABC size is a calculated magnitude, so this number can be a Fixnum or
  # a Float.
  Max: 20
  
Style/FrozenStringLiteralComment:
  Enabled: false

# The guiding principle of classes is SRP, SRP can't be accurately measured by LoC
Metrics/ClassLength:
  Max: 1500

# No space makes the method definition shorter and differentiates
# from a regular assignment.
Style/SpaceAroundEqualsInParameterDefault:
  EnforcedStyle: no_space

# Single quotes being faster is hardly measurable and only affects parse time.
# Enforcing double quotes reduces the times where you need to change them
# when introducing an interpolation. Use single quotes only if their semantics
# are needed.
# Style/StringLiterals:
#   EnforcedStyle: double_quotes

# We do not need to support Ruby 1.9, so this is good to use.
Style/SymbolArray:
  Enabled: false

# Most readable form.
Style/AlignHash:
  EnforcedHashRocketStyle: table
  EnforcedColonStyle: table

# Mixing the styles looks just silly.
Style/HashSyntax:
  Enabled: false
  # EnforcedStyle: ruby19_no_mixed_keys

# has_key? and has_value? are far more readable than key? and value?
Style/DeprecatedHashMethods:
  Enabled: false

# String#% is by far the least verbose and only object oriented variant.
Style/FormatString:
  EnforcedStyle: percent

Style/CollectionMethods:
  Enabled: true
  PreferredMethods:
    # inject seems more common in the community.
    reduce: "inject"

Style/TrailingCommaInLiteral:
  Enabled: false

# Either allow this style or don't. Marking it as safe with parenthesis
# is silly. Let's try to live without them for now.
Style/ParenthesesAroundCondition:
  AllowSafeAssignment: false
Lint/AssignmentInCondition:
  AllowSafeAssignment: false

# A specialized exception class will take one or more arguments and construct the message from it.
# So both variants make sense. 
Style/RaiseArgs:
  Enabled: false

# Indenting the chained dots beneath each other is not supported by this cop,
# see https://github.com/bbatsov/rubocop/issues/1633
Style/MultilineOperationIndentation:
  Enabled: false

# Fail is an alias of raise. Avoid aliases, it's more cognitive load for no gain.
# The argument that fail should be used to abort the program is wrong too,
# there's Kernel#abort for that.
Style/SignalException:
  EnforcedStyle: only_raise

# Suppressing exceptions can be perfectly fine, and be it to avoid to
# explicitly type nil into the rescue since that's what you want to return,
# or suppressing LoadError for optional dependencies
Lint/HandleExceptions:
  Enabled: false

Style/SpaceInsideBlockBraces:
  # The space here provides no real gain in readability while consuming
  # horizontal space that could be used for a better parameter name.
  # Also {| differentiates better from a hash than { | does.
  SpaceBeforeBlockParameters: false

# No trailing space differentiates better from the block:
# foo} means hash, foo } means block.
Style/SpaceInsideHashLiteralBraces:
  EnforcedStyle: no_space

# { ... } for multi-line blocks is okay, follow Weirichs rule instead:
# https://web.archive.org/web/20140221124509/http://onestepback.org/index.cgi/Tech/Ruby/BraceVsDoEnd.rdoc
Style/BlockDelimiters:
  Enabled: false

Style/DoubleNegation:
  Enabled: false

# do / end blocks should be used for side effects,
# methods that run a block for side effects and have
# a useful return value are rare, assign the return
# value to a local variable for those cases.
Style/MethodCalledOnDoEndBlock:
  Enabled: true

# Enforcing the names of variables? To single letter ones? Just no.
Style/SingleLineBlockParams:
  Enabled: false

# Shadowing outer local variables with block parameters is often useful
# to not reinvent a new name for the same thing, it highlights the relation
# between the outer variable and the parameter. The cases where it's actually
# confusing are rare, and usually bad for other reasons already, for example
# because the method is too long.
Lint/ShadowingOuterLocalVariable:
  Enabled: false

# Check with yard instead.
Style/Documentation:
  Enabled: false 

# This is just silly. Calling the argument `other` in all cases makes no sense.
Style/OpMethod:
  Enabled: false 

# There are valid cases, for example debugging Cucumber steps,
# also they'll fail CI anyway
Lint/Debugger:
  Enabled: false

# Style preference
Style/MethodDefParentheses:
  Enabled: false

Style/AndOr:
# Whether `and` and `or` are banned only in conditionals (conditionals)
# or completely (always).
  EnforcedStyle: conditionals