Ruby - Basics
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?
andnil?
- 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