Ruby 💎
Linting
The team uses RuboCop for automated code checks, and their guidelines largely comply with the default guidelines set by the analyzer.
Formatting
Naming
-
Use
snake_case
for symbols, methods and variables.:someSymbol someVar = 5 def someMethod # some code end
:some_symbol some_var = 5 def some_method # some code end
-
Do not separate numbers from letters on symbols, methods and variables.
:some_sym_1 some_var_1 = 1 def some_method_1 # some code end
:some_sym1 some_var1 = 1 def some_method1 # some code end
-
Use CamelCase for classes and modules.
class Someclass # some code end class Some_Class # some code end
class SomeClass # some code end
-
Use
snake_case
for naming files.HelloWorld.rb
hello_world.rb
-
Use
snake_case
for naming directories.lib/HelloWorld/hello_world.rb
lib/hello_world/hello_world.rb
-
Aim to have just a single class/module per source file. Name the file name as the class/module, but replacing CamelCase with snake_case.
-
Use
SCREAMING_SNAKE_CASE
for other constants.SomeConst = 5
SOME_CONST = 5
-
The names of predicate methods (methods that return a boolean value) should end in a question mark
?
.def is_true end
def true? end
-
The names of method which could raise exception should end in bang
!
.def authenticate raise UserNotAuthenticated unless authenticate_user(current_user) end
def authenticate! raise UserNotAuthenticated unless authenticate_user(current_user) end
Syntax
-
Use the
proc
invocation shorthand when the invoked method is the only operation of a block.names.map { |name| name.upcase }
names.map(&:upcase)
-
Prefer
{...}
overdo...end
for single-line blocks. Avoid using{...}
for multi-line blocks.names = %w[Bozhidar Steve Sarah]
names.each do |name| puts name end
names.each { |name| puts name }
-
Use
.call
explicitly when calling lambdas.lambda.(x, y)
lambda.call(x, y)
-
Use
...
operator when forwarding method arguments to a method.def call(*args, **kwargs, &block) process(*args, **kwargs, &block) end
def call(...) process(...) end
Ruby 3.0 has added support passing leading arguments along with
...
operator which allows writing more flexible methods.def create_github_repo(project_type, ...) if project_type == 'open_source' initialize_public_repo(...) else initialize_private_repo(...) end end
-
When defining an object of any mutable type meant to be a constant, make sure to call
freeze
on it. Common examples are strings, arrays, and hashes.class Color RED = 'red' BLUE = 'blue' GREEN = 'green' ALL_COLORS = [ RED, BLUE, GREEN, ] COLOR_TO_RGB = { RED => 0xFF0000, BLUE => 0x0000FF, GREEN => 0x00FF00, } end
class Color RED = 'red'.freeze BLUE = 'blue'.freeze GREEN = 'green'.freeze ALL_COLORS = [ RED, BLUE, GREEN, ].freeze COLOR_TO_RGB = { RED => 0xFF0000, BLUE => 0x0000FF, GREEN => 0x00FF00, }.freeze end
Conditional Expressions
-
Never negate the conditional of an
if
block. Useunless
instead.if !false then #... end
unless false #... end
-
Never use the return value of
=
in conditionals.if is_branded = array.include? 'brand' #... end
is_branded = array.include? 'brand' if is_branded #... end
-
Prefer using guard clauses over
if/else
and ternary operators.def message if valid? 'success' else 'error' end end def message valid? ? 'success' : 'error' end
def message return 'success' if valid? 'error' end
-
Never use nested conditionals for flow of control.
The general principles boil down to:
- Return immediately if the function cannot do anything more.
- Reduce nesting and indentation in the code by returning early. This makes the code easier to read and requires less mental bookkeeping on the part of the reader to keep track of else branches.
- The core or most important flows should be the least indented.
def compute server = find_server if server client = server.client if client request = client.make_request if request process_request(request) end end end end
def compute server = find_server return unless server client = server.client return unless client request = client.make_request return unless request process_request(request) end
Collections
-
Prefer
%w
to the literal array syntax for an array of raw strings.STATES = ['draft', 'open', 'closed'] # Less Preferred STATES = %w(draft open closed)
STATES = %w[draft open closed]
-
Use symbols instead of strings as hash keys.
hash = { 'one' => 1, 'two' => 2, 'three' => 3 }
hash = { one: 1, two: 2, three: 3 }
-
Use
Hash#key?
instead ofHash#has_key?
andHash#value?
instead ofHash#has_value?
. According to Matz, the longer forms are considered deprecated.hash.has_key?(:test) hash.has_value?(value)
hash.key?(:test) hash.value?(value)
Strings
-
Prefer single quotes over double quotes.
ruby = "Ruby is an interpreted, high-level, general-purpose programming language."
ruby = 'Ruby is an interpreted, high-level, general-purpose programming language.'
-
Prefer string interpolation over appending to strings.
full_name = user.first_name + ' ' + user.last_name
full_name = "#{user.first_name} #{user.last_name}"
-
Prefer symbols over strings where applicable.
person = { 'name' => 'Yukihiro Matsumoto', 'age' => 55 }
person = { name: 'Yukihiro Matsumoto', age: 55 }
class User attr_accessor 'first_name' ... end
class User attr_accessor :first_name ... end
-
Prefer
heredoc
to multiline strings.notice = "Subscription expiring soon!.\n Your free trial will expire in #{days_until_expiration} days.\n Please update your billing information."
notice = <<~HEREDOC Subscription expiring soon!. Your free trial will expire in #{days_until_expiration} days. Please update your billing information. HEREDOC
-
Use
format
when formatting string to a certain format.formatted_card_number = "#{card_brand} ******#{card_last_digits}"
CARD_NUMBER_FORMAT = '%{card_brand} ******%{card_last_digits}'.freeze formatted_card_number = format(CARD_NUMBER_FORMAT, card_brand: card_brand, card_last_digits: card_last_digits)
Methods
-
Use parentheses in method definitions only if it accepts any parameters.
def method_without_params # body omitted end def method_with_parameters(param1, param2) # body omitted end
-
Avoid usage of positional arguments. Use keyword arguments or an options hash instead.
def positional_method(a = 1, b = 2, c, d) # method body end
def method_with_keyword_arguments(a:, b:, c:, d:) end
def method_with_options(options = {}) end
-
Use endless method for writing single liner method (This convention only applicable for Ruby 3 or later).
def square(x) x * x end
def square(x) = x * x
Modules
-
Use modules to organize a chunk of common stateless operations.
-
Use
::
only to reference constants and constructors. Do not use :: for regular method invocation.Klass::some_method object::some_method
Klass.some_method object.some_method
-
Properly choose between
def self
,extend self
andmodule_function
.
Let’s consider a module Math
implementing a method pi
.
Use def self.pi
include Math
will not give any sort of accessor to .pi
, not YourClass.pi
, not instance.pi
, nothing.
Use Math.pi
instead.
Use extend self
with public methods
include Math
will give instances a public instance method .pi
.
Use extend self
with private methods
include Math
will give a private instance method .pi
but Math.pi
will say “private method pi called”
Use module_function :pi
include Math
will give instances a private .pi
method and Math.pi
still works just fine.
Classes
-
Code in Ruby classes must follow the following structure:
# requires require 'dependency_filename' class KlassName # extend and include extend SomeModule include AnotherModule # constants SOME_CONSTANT = '20'.freeze # attribute macros attr_reader :name # additional macros (if any) validates :name # public class methods class << self def some_method #... end end # initialization def initialize end # public instance methods def some_method end # Group alias methods at the end of public methods alias alias_name some_method alias alias_alternative_name some_method # protected methods protected def some_protected_method end # private methods private def some_private_method end end
-
Avoid nesting multiple classes within classes. Instead maintain each in their own file in a folder named like the containing class.
# parent.rb class Parent class Child end end
# parent.rb class Parent end # parent/child.rb class Parent class Child end end
-
Classes should be used only when it makes sense to create instances out of them. Prefer modules to classes with only class methods.
class SomeClass class << self def self.some_method #... end def self.some_other_method #... end end end
module SomeModule def some_method #... end def some_other_method #... end end
-
Use
class << self
to define class methods instead ofself.
.class SomeKlass def self.method_name #... end end
class SomeKlass class << self def method_name #... end end end
-
Try to make the classes as SOLID as possible.
-
Use the
attr
family of functions to define trivial accessors.class Klass def initialize(a, b) @a = a @b = b end def a @a end def b @b end end
class Klass attr_reader :a, :b def initialize(a, b) @a = a @b = b end end
Exceptions
-
Never rescue the
Exception
class. Rescue specific error classes.begin 1/0 rescue Exception # exception handling end
begin 1/0 rescue ZeroDivisionError # exception handling end
Monkey Patching/Refinements
-
Avoid monkey patching built-in classes, use refinements instead
class Hash def deep_transform_camelize_keys deep_transform_keys { |key| key.to_s.camelize(:lower) } end end
module HashExtensions refine Hash do def deep_transform_camelize_keys deep_transform_keys { |key| key.to_s.camelize(:lower) } end end end