ruk·si

💎 Ruby
Basics

Updated at 2016-04-08 06:50

This guide contains all-around tips how to write Ruby code. Related to Ruby on Rails Guide.

General

Installing Ruby. Use Ruby version manager. rvm and rbenv are popular options, I prefer rbenv as I'm using Bundler for gemsets.

# OS X:
# Install Homebrew if you don't have it.
brew update
brew install rbenv ruby-build

rbenv install -l
rbenv install 2.2.2 # Or whatever you want to use
rbenv global 2.2.2  # Ruby version used if project doesn't have version defined

mkdir my-app
cd my-app
rbenv local 2.2.2 # Defines Ruby version for this specific project

Keep lines fewer than 80 characters. Unless there is a good reason of course. If there is not additional logic past the 80 char, it should be fine.

# good, no need to split this kind of line.
ARR = %w(us ca fi se tw pr kr cn ru uk de as df ee re qe ...)

# ...except if there is something like this at the end!
ARR = %w(us ca fi se tw pr kr cn ru uk de as df ee re qe).map { |c| c.upcase }

Use require and load to include other Ruby files.

require 'gem-name'  # Loads the file once in the process lifetime.
load 'gem-name'     # Always loads the file.

Naming

Use snake_case for methods and variables.

Use CamelCase for classes and modules. Keep acronyms like HTTP, RFC, XML uppercase, but only use commonly known acronyms.

Use ANACONDA_CASE for constants.

Methods that return a boolean value should end in ?.

array.empty?

Dangerous methods should end in !. Methods that modify self, the arguments, stops the process etc.

exit!

Name throwaway variables _.

payment, _ = Payment.complete_paypal_payment!(params[:token],
                                              native_currency,
                                              created_at)

Variables

Learn to check variable class/type. Prefer is_a? to instance_of? to allow using subclasses.

foo = "Hello!"
foo.is_a? String         # true
foo.kind_of? String      # true, synonym to is_a?
foo.instance_of? String  # true

class X < String
end

bar = X.new
bar.is_a? String         # true
bar.kind_of? String      # true, synonym to is_a?
bar.instance_of? String  # false
bar.instance_of? X       # true

Use object presence methods.

  • variable.nil?
    • a Ruby method
    • can be used on any object and is true if the object is nil.
  • variable.empty?
    • a Ruby method
    • can be used on strings, arrays and hashes and returns true if length is 0
    • Running .empty? on something that is nil will throw a NoMethodError
  • variable.blank?
    • a Rails method, not in standard Ruby
    • can be used on any object and works like .empty? on strings, arrays and hashes.
    • evaluates true on strings which are non-empty but contain only whitespace.
  • variable.present?
    • a Rails method, not in standard Ruby
    • operates on any object like blank? and nil?
    • is the same ase !variable.blank?

$ identifies a global variable. You rarely need to use this.

@ identifies a class instance variable.

@@ identifies a class variable.

Learn the common variables available everywhere. And how to use them.

$: = default search path (array of paths)
$:.unshift("#{root}/actionpack/lib")
__FILE__ = current sourcefile

Indention

Use 2 space indention.

def greet()
  puts 'Hello World!'
end

Indent when as deep as case.

case
when song.name == "Misty"
  puts "Not again!"
when song.duration > 120
  puts "Too long!"
when Time.now.hour > 21
  puts "It's too late"
else
  song.play
end

kind = case year
       when 1850..1889 then "Blues"
       when 1890..1909 then "Ragtime"
       when 1910..1929 then "New Orleans Jazz"
       when 1930..1939 then "Swing"
       when 1940..1950 then "Bebop"
       else "Jazz"
       end

Indent methods to related public, protected and private definition.

class SomeClass
  # implicit public
  def public_method
    # ...
  end

  private
  def private_method
    # ...
  end
end

Whitespace

Never leave a trailing space.

Use space...

  • around operators or {
  • after ,, : or ;
  • before }
  • around = in default method parameters
sum = 1 + 2
a, b = 1, 2
1 > 2 ? true : false; puts "Hi"
[1, 2, 3].each { |e| puts e }
def some_method(arg1 = :default, arg2 = nil, arg3 = [])

Don't use space...

  • after (, [ or !
  • before ) or ]
  • between method name and opening parenthesis (
some(arg).other
[1, 2, 3].length
!array.include?(element)

Newlines

Each file should end with a blank line.

Use empty lines between function definitions.

def some_method
  result
end

def other_method
  result
end

Add a newline after conditionals, blocks and case statements.

if robot.is_awesome?
  send_robot_present
end

robot.add_trait(:human_like_intelligence)

Break up code into logical paragraphs.

def some_method
  options = get_options
  data = initialize(options)

  data.manipulate!
  data.manipulate_more

  data.result
end

Assignment

Using result of assignment = is acceptable. Just always use parenthesis with =.

# good
if v = array.grep(/foo/) ...

# good
if (v = next_value) == "hello" ...

Use conditional assignment operator ||=. If target is false, nil or undefined, then evaluate second expression and set it to target.

name ||= "Bozhidar"

Never use ||= to initialize boolean variables. It will overwrite if the value is false.

# bad - would set enabled to true even if it was false
enabled ||= true

# good
enabled = true if enabled.nil?

Strings

Use double quotes strings. Interpolation needs then anyway. Better to keep thing consistent.

# bad
name = 'Bozhidar'

# good
name = "Bozhidar"

Use string interpolation.

# bad
email_with_name = user.name + " <" + user.email + ">"

# good
email_with_name = "#{user.name} <#{user.email}>"

Learn the % notation. You can create % definitions with any non-alpha-numeric delimiters e.g. %(), %[], %{}, %~~. I personally use [] for arrays and () for others.

%q() # string, disabled interpolation
%Q() # string, enabled interpolation
%r() # regex, enabled interpolation
%x() # executes a shell command, enabled interpolation
%s() # symbol, disabled interpolation

%i[] # array of symbols, separated by whitespace, disabled interpolation
%I[] # array of symbols, separated by whitespace, enabled interpolation
%w[] # array of strings, separated by whitespace, disabled interpolation
%W[] # array of strings, separated by whitespace, enabled interpolation

# avoid using array notation if the items contain whitespace
%W(#{foo} Bar Bar\ with\ space)

Use %Q() for strings which require interpolation and ".

# bad
"<div class=\"name\">#{name}</div>"

# good
%Q(<tr><td class="name">#{name}</td>)

Use %Q() or %Q() for multiline strings. A lot easier to read and write.

def form_errors
  heading = "Errors:"
  messages = "Get the errors as list items."
  %Q(
    <div id="error_explanation">
      <h2>#{heading}</h2>
      <ul>
        ( #{messages} )
      </ul>
   </div>
  ).html_safe
end

# consider using heredoc syntax if you have a lot of content,
# no need to worry about escaping anything
<<-HTML
  <HTML> <!-- doesn't care, looks for the last HTML-->
  <div id="error_explanation">
    <h2>#{heading}</h2>
    <ul>
      ( #{messages} )
    </ul>
 </div>
HTML

Use append << if you have many string chunks that you have to glue together. + is slower and harder to read.

# good and fast
html = ""
html << "<h1>Page title</h1>"
paragraphs.each do |paragraph|
  html << "<p>#{paragraph}</p>"
end

Documentation

When you document a definition, use TomDoc.

# Public: Duplicate some text an arbitrary number of times.
#
# text  - The String to be duplicated.
# count - The Integer number of times to duplicate the text.
#
# Examples
#
#   multiplex("Tom", 4)
#   # => "TomTomTomTom"
#
# Returns the duplicated String.
def multiplex(text, count)
  text * count
end

Use TODO comments for code that is short-term or "good-enough" solution. Include your name so people can get more details.

# bad
# TODO(RS): Use proper namespacing for this constant.

# bad
# TODO(drumm3rz4lyfe): Use proper namespacing for this constant.

# good
# TODO(Ringo Starr): Use proper namespacing for this constant.

Never comment out code. Commented code should always be removed. You can get it back through the version control.

Functions

Never use () in method definition when it takes no arguments.

def some_method
  # ...
end

def some_method_with_arguments(name, type)
  # ...
end

Avoid default arguments. Use an options hash instead.

# bad
def obliterate(things, gently = true, except = [], at = Time.now)
  # ...
end

# good
def obliterate(things, options = {})
  default_options = {
    :gently => true, # obliterate with soft-delete
    :except => [],   # skip obliterating these things
    :at => Time.now, # don't obliterate them until later
  }
  options.reverse_merge!(default_options)

  # ...
end

Don't use {...} during method invocation if it's the last argument.

# bad
get '/v1/reservations', { :id => 54875 }

# good
get '/v1/reservations', :id => 54875

Use parentheses if method returns a value.

# bad
@current_user = User.find_by_id 1964192

# good
@current_user = User.find_by_id(1964192)

Use parentheses if the first argument to the method uses parentheses.

# bad
put! (x + y) % len, value

# good
put!((x + y) % len, value)

Omit parentheses for a method call if the method accepts no arguments.

# bad
nil?()

# good
nil?

Parentheses are optional if the method doesn't return a value.

# okay
render(:partial => 'foo')

# okay
render :partial => 'foo'

Use keyword arguments for boolean arguments in methods.

# bad
def remove_member(user, skip_membership_check=false)
  # ...
end
remove_member(user, true)

# good
def remove_member(user, skip_membership_check: false)
  # ...
end
remove_member user, skip_membership_check: true

Conditionals

Never use and, or or not. Use &&, || and !.

# bad
(some_condition and other_condition) or my_condition

# good
(some_condition && other_condition) || my_condition

Never use then for multi-line if and unless.

# bad
if some_condition then
  puts 'Hi'
end

# good
if some_condition
  puts 'Hi'
end

# good
if some_condition then puts 'Hi' end

Avoid multi-line conditions. Use variables to make them single-line.

# bad
if @reservation_alteration.checkin == @reservation.start_date &&
   @reservation_alteration.checkout == (@reservation.start_date + @reservation.nights)

  redirect_to_alteration @reservation_alteration
end

# good
checkin_on_time = @reservation_alteration.checkin == @reservation.start_date
end_date = @reservation.start_date + @reservation.nights
checkout_on_time = @reservation_alteration.checkout == end_date
if checkin_on_time && checkout_on_time
  redirect_to_alteration @reservation_alteration
end

Avoid ternary operator ? : except if the expressions are trivial. Most single line conditionals can be considered trivial, at least if you use variables to shorten the variables.

# bad
result = if some_condition then something else something_else end

# bad
result = (some_condition && other_conditional) ? something + 1 : something_else

# good
is_conditional = (some_condition && other_conditional)
result = is_conditional ? something : something_else

Never use multi-line ternary operators. Use if or unless instead.

Never nest ternary operators.

# bad
some_condition ? (other_thing ? other_stuff : nested_result) : something

# good
if some_condition
  other_thing ? other_stuff : nested_result
else
  something
end

Use modifier if or unless when possible.

# bad
if some_condition
  do_something
end

# good
do_something if some_condition

Never use unless with else. Rewrite positive case first.

# bad
unless success?
  puts "failure"
else
  puts "success"
end

# good
if success?
  puts "success"
else
  puts "failure"
end

Avoid unless with multiple conditions.

# bad
unless foo? && bar?
  # ...
end

# okay
if !(foo? && bar?)
  # ...
end

Never use () around the conditional of an if, unless or while.

# bad
if (x > 10)
  # ...
end

# good
if x > 10
  # ...
end

Never use === operator to check types. Use is_a? or kind_of? instead.

String === "hi" # true
"hi" === String # false

Collections

Use %w(...) syntax when you need an array of strings.

# bad
STATES = ["draft", "open", "closed"]

# good
STATES = %w(draft open closed)

Use the array helpers.

# Take random element:
[:foo, :bar, :biz].sample        # => :bar
[:foo, :bar, :biz].sample (2)    # => [:foo, :biz]

Use symbols as hash keys. General rule is to use a symbol when you are defining a name for something and a string when you are defining a value of something.

# bad
hash = { "one" => 1, "two" => 2, "three" => 3 }

# good
hash = { :one => 1, :two => 2, :three => 3 }

Use Set instead of Array when dealing with unique elements.

require 'set'
s1 = Set.new [1, 2]
s1.add("foo")
s1.include?(1)

Hashes

Use rocket syntax for hash literals. JSON style syntax doesn't support string keys that are sometimes needed.

# bad
user = {
  login: "defunkt",
  name: "Chris Wanstrath"
}

# bad
user = {
  login: "defunkt",
  name: "Chris Wanstrath",
  "followers-count" => 52390235
}

# good
user = {
  :login => "defunkt",
  :name => "Chris Wanstrath",
  "followers-count" => 52390235
}

Use multi-line hashes.

hash = {
  :protocol => 'https',
  :only_path => false,
  :controller => :users,
  :action => :set_password,
  :redirect => @redirect_url,
  :secret => @secret
}

Enumerable

Enumerable interface provides really important set of functions that every Ruby programmer should know by heart. Usable with e.g. arrays and hashes.

# turn to array
{ "a" => 1, "b" => 2, "c" => 3 }.to_a
# => [["a", 1], ["b", 2], ["c", 3]]

# turn to hash
[["a", 1], ["b", 2], ["c", 3]].to_h
# => { "a" => 1, "b" => 2, "c" => 3}
# basic iteration
(1..4).each { |n| puts n }
# => 1, 2, 3, 4

(1..4).reverse_each { |n| puts n }
# => 4, 3, 2, 1

# including index to the iteration
hash = {}
%w(cat dog wombat).each_with_index { |item, index| hash[item] = index }
puts hash # => { "cat" => 0, "dog" => 1, "wombat" => 2 }

# looping multiple times on an enumerable
["a", "b", "c"].cycle(2) { |x| puts x }
# => a, b, c, a, b, c.
# basic check
%w[ant bear cat].all? { |word| word.length >= 3 }   #=> true
%w[ant bear cat].all? { |word| word.length >= 4 }   #=> false
%w[ant bear cat].any? { |word| word.length >= 3 }   #=> true
%w[ant bear cat].any? { |word| word.length >= 5 }   #=> false
%w[ant bear cat].include? "cat"                     #=> true
%w[ant bear cat].include? "bat"                     #=> false
%w[ant bear cat].none? { |word| word.length == 5 }  #=> true
%w[ant bear cat].none? { |word| word.length == 4 }  #=> false
%w[ant bear cat].one? { |word| word.length == 4 }   #=> true
%w[ant bear cat].one? { |word| word.length == 3 }   #=> false
%w[ant bear cat].one? { |word| word.length == 5 }   #=> false
# transform elements to array values
(1..4).map { |n| n * n }
#=> [1, 4, 9, 16]

# returns results as an flattened array
[1, 2, 3, 4].flat_map { |e| [e, -e] }
#=> [1, -1, 2, -2, 3, -3, 4, -4]

# counting specific elements
[1, 2, 4, 2].count                     # => 4
[1, 2, 4, 2].count(2)                  # => 2
[1, 2, 4, 2].count { |x| x%2 == 0 }    # => 3

# finding first element that matches
(5..100).find { |n| n % 5 == 0 and n % 7 == 0 }         # => 35
(5..100).find { |n| n == 101 }                          # => nil
(5..100).find( -> { ":(" }) { |n| n == 101 }            # => ":("

# finding index of the first element that matches
(5..100).find_index { |n| n % 5 == 0 and n % 7 == 0 }   # => 30

# finding all elements that match
(5..100).find_all { |n| n % 5 == 0 and n % 7 == 0 }     # => [35, 70]

# max or min element by some standard, give number to get multiple
%w(dog bear horse).max { |a, b| a.length <=> b.length } # => "horse"
%w(dog bear horse).max_by { |x| x.length }              # => "horse"
%w(dog bear horse).min { |a, b| a.length <=> b.length } # => "dog"
%w(dog bear horse).min_by { |x| x.length }                 # => "dog"
%w(dog bear horse).minmax { |a, b| a.length <=> b.length } # => ["dog", "horse"]
%w(dog bear horse).minmax_by { |x| x.length }              # => ["dog", "horse"]
# removing leading elements
[2, 1, 4, 0, 5, 3].drop(3)                  # => [0, 5, 3]
[2, 1, 4, 0, 5, 3].drop_while { |n| n < 3 } # => [4, 0, 5, 3]

# removing trailing elements
[2, 1, 4, 0, 5, 3].take(3)                  # => [2, 1, 4]
[2, 1, 4, 0, 5, 3].take_while { |n| n < 3 } # => [2, 1]

# removing matching elements
(1..10).reject { |n|  n % 3 == 0 }
# => [1, 2, 4, 5, 7, 8, 10]
[4, 5, 6].zip [7, 8, 9]
# => [[4, 7], [5, 8], [6, 9]]
# iterating while passing a value
(1..5).reduce { |memo, n| memo + n }
# => 15

# iterating while passing an object
doubled = (1..4).each_with_object([]) { |n, memo| memo << n * 2 }
# => [2, 4, 6, 8]
%w(rhea kea flea).sort                            # => ["flea", "kea", "rhea"]
%w(rhea kea flea).sort { |a, b| a <=> b }         # => ["flea", "kea", "rhea"]
%w{apple pear fig}.sort_by { |word| word.length}  # => ["fig", "pear", "apple"]
# create groups by returned value
(1..10).group_by { |i| i % 2 }
# => { 1 => [1, 3, 5, 7, 9], 0 => [2, 4, 6, 8, 10] }

# create 2 groups, all that match and rest that don't
(1..10).partition { |n| n > 7 }
# => [[8, 9, 10], [1, 2, 3, 4, 5, 6, 7]]

# operate on consecutive elements
(1..6).each_cons(3) { |a| puts "#{a}, #{a.count}" }
# [1, 2, 3], 3
# [2, 3, 4], 3
# [3, 4, 5], 3
# [4, 5, 6], 3

# operates on element slices
(1..5).each_slice(2) { |a| puts "#{a}" }
# [1, 2]
# [3, 4]
# [5]

# chunks consecutively similar values
[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5].chunk do |n|
    n.even?
end.each do |is_even, a|
  p [is_even, a]
end
# [false, [3, 1]]
# [true,  [4]]
# [false, [1, 5, 9]]
# [true,  [2, 6]]
# [false, [5, 3, 5]]

Never use for. Unless you know exactly how it works and why you are using it. Use iterators.

arr = [1, 2, 3]

# bad
for elem in arr do
  puts elem
end

# good
arr.each { |elem| puts elem }

Blocks

Use {...} for single-line blocks. Avoid do...end.

names = ["Bozhidar", "Steve", "Sarah"]

# bad
names.each do |name| puts name end

# good
names.each { |name| puts name }

Use do...end for multi-line blocks. Avoid {...}.

names = ["Bozhidar", "Steve", "Sarah"]

# bad
names.each { |name|
  puts "Hello"
  puts name
}

# good
names.each do |name|
  puts "Hello"
  puts name
end

Use {...} when chaining.

 # bad
names.select do |name|
  name.start_with?("S")
end.map { |name| name.upcase }

# good
names.select { |name| name.start_with?("S") }.map { |name| name.upcase }

Avoid return.

# bad
def some_method(stuff)
  return stuff.size
end

# good
def some_method(stuff)
  stuff.size
end

Use underscore _ for unused block parameters.

# bad
result = hash.map { |k, v| v + 1 }

# good
result = hash.map { |_, v| v + 1 }

Use pretzel colon shorthand &:. Denotes that the following argument should be treated as a block given to the method. That means that if it's not a Proc object yet, its to_proc method will be called to transform it into one. &method is similar for turning a method to a block.

Post.all.map(&:id)         # this
Post.all.map(&:id.to_proc) # converts to this
Post.all.map { |x| x.id }  # which is kind of like this

array.each(&method(:foo))               # this
array.each { |element| foo(element) }   # coverts to this

Classes

Avoid using class variables @@. They have unusual inheritance behavior. Use class instance variables @.

class Parent
  @@class_var = "parent"

  def self.print_class_var
    puts @@class_var
  end
end

class Child < Parent
  @@class_var = "child"
end

# Notice we are looking at PARENT!
Parent.print_class_var # => "child"

Use self when defining static methods. Easier to refactor than using the class name.

class TestClass

  # bad
  def TestClass.some_method
    # ...
  end

  # good
  def self.some_method
    # ...
  end
end

Use attr_accessor when applicable. It generates accessors for the given instance variable.

class Person
end

person = Person.new
person.name # => no method error

class Person
  attr_accessor :name
  # or...
  # attr_reader :name
  # attr_writer :name
end

person = Person.new
person.name = "Dennis"
person.name # => "Dennis"

Use alias_method.

class Test
  def greet
    puts "Hello!"
  end
  alias_method :hail, :greet
end

t = Test.new
t.greet
t.hail

Prefer self.method to class << self. These are used for creating "static" members. Sometimes it is required though e.g. single accessors and aliased attributes.

class TestClass

  # bad
  class << self
    def first_method
      # ...
    end

    def second_method_etc
      # ...
    end
  end

  # good
  class << self
    attr_accessor :per_page
    alias_method :nwo, :find_by_name_with_owner
  end

  def self.first_method
    # ...
  end

  def self.second_method_etc
    # ...
  end
end

Avoid explicit use of self. self keyword is optional if not on assignment left hand side. Sometimes it is required though e.g. a shadowing variable exists.

class SomeClass
  attr_accessor :message

  def buggy(msg)
    message = msg # doesn't work, just creates a local variable
  end

  def greeting(name)
    self.message = "Hi #{name}" # the right way
  end
end

Modules

Module is a collection of methods and constants. Modules are like classes but are not instantiated and have note inheritance.

Methods in a module may be instance or module methods. Instance methods appear as methods in a class when the module is include, module methods do not.

module Foo
  include Math
  CONST = 1

  def self.module_method
    "MODULE METHOD!"
  end

  def instance_method
    "INSTANCE METHOD!"
  end
end

Foo.class              # Module
Foo.constants          # [:CONST, :DomainError, :PI, :E]
Foo.instance_methods   # [:instance_method]
Foo.module_method      # MODULE METHOD!

class Bar
  include Foo
end
b = Bar.new
b.instance_method      # INSTANCE METHOD!

Attributes are also passed when included. Each class instance has separate values. Note that includes to multiple modules might overwrite attribute accessors with the same name.

module Alpha
  attr_accessor :name
end

class Beta
  include Alpha
end
b = Beta.new
b.name           # nil
b.name = "Andy"  # "Andy"
bb = Beta.new
bb.name = "Eric" # "Eric"
b.name           # "Andy"

Error Handling

Don't use exceptions as flow of control.

# bad
begin
  n / d
rescue ZeroDivisionError
  puts "Cannot divide by 0!"
end

# good
if d.zero?
  puts "Cannot divide by 0!"
else
  n / d
end

Only rescue specific exceptions.

# bad
begin
  # ...
rescue
  # ...
end

# still bad
begin
  # ...
rescue Exception
  # ...
end

# good
begin
  # ...
rescue ZeroDivisionError
  # ...
end

Regular Expressions

Avoid using $1-9. They can be hard to track. Use named groups.

# bad
/(regexp)/ =~ string
...
process $1

# good
/(?<meaningful_var>regexp)/ =~ string
...
process meaningful_var

Use \A and \z to match whole string. ^ and $ match start and end of line.

str = "some injection\nusername"
str[/^username$/]   # matches
str[/\Ausername\z/] # doesn't match

Use %r only for regexps matching more than one /.

# bad
%r(\s+)

# still bad
%r(^/(.*)$)
# should be /^\/(.*)$/

# good
%r(^/blog/2011/(.*)$)

Use x modifier for complex regexps. Then you can add comments and whitespace is ignored.

regexp = %r{
  start         # some text
  \s            # white space char
  (group)       # first group
  (?:alt1|alt2) # some alternation
  end
}x

Sources