Ruby đź’Ž
Linting
The team uses RuboCop for automated code checks, and their guidelines largely comply with the default guidelines set by the analyzer.
Formatting
- Use soft-tabs with a two space indent.
- Limit each line of code to fewer than 130 characters.
- Use a single empty line to break between statements to organize logical chunks of code.
- End each file with a new line.
Naming
-
Use
snake_casefor 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 endclass SomeClass # some code end -
Use
snake_casefor naming files.HelloWorld.rbhello_world.rb -
Use
snake_casefor naming directories.lib/HelloWorld/hello_world.rblib/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_CASEfor other constants.SomeConst = 5SOME_CONST = 5 -
The names of predicate methods (methods that return a boolean value) should end in a question mark
?.def is_true enddef true? end -
The names of method which could raise exception should end in bang
!.def authenticate raise UserNotAuthenticated unless authenticate_user(current_user) enddef authenticate! raise UserNotAuthenticated unless authenticate_user(current_user) end
Syntax
-
Use the
procinvocation shorthand when the invoked method is the only operation of a block.names.map { |name| name.upcase }names.map(&:upcase) -
Prefer
{...}overdo...endfor single-line blocks. Avoid using{...}for multi-line blocks.names = %w[Bozhidar Steve Sarah]names.each do |name| puts name endnames.each { |name| puts name } -
Use
.callexplicitly 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) enddef call(...) process(...) endRuby 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
freezeon 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, } endclass 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
ifblock. Useunlessinstead.if !false then #... endunless false #... end -
Never use the return value of
=in conditionals.if is_branded = array.include? 'brand' #... endis_branded = array.include? 'brand' if is_branded #... end -
Prefer using guard clauses over
if/elseand ternary operators.def message if valid? 'success' else 'error' end end def message valid? ? 'success' : 'error' enddef 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 enddef 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
%wto 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_namefull_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' ... endclass User attr_accessor :first_name ... end -
Prefer
heredocto 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
formatwhen 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 enddef method_with_keyword_arguments(a:, b:, c:, d:) enddef 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 enddef 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_methodKlass.some_method object.some_method -
Properly choose between
def self,extend selfandmodule_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 endmodule SomeModule def some_method #... end def some_other_method #... end end -
Use
class << selfto define class methods instead ofself..class SomeKlass def self.method_name #... end endclass SomeKlass class << self def method_name #... end end end -
Try to make the classes as SOLID as possible.
-
Use the
attrfamily 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 endclass Klass attr_reader :a, :b def initialize(a, b) @a = a @b = b end end
Exceptions
-
Never rescue the
Exceptionclass. Rescue specific error classes.begin 1/0 rescue Exception # exception handling endbegin 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 endmodule HashExtensions refine Hash do def deep_transform_camelize_keys deep_transform_keys { |key| key.to_s.camelize(:lower) } end end end