This Ruby style guide recommends standard practices so that in real-world we can write good code. The guide is separated into several sections.
WhitespaceIndentation:
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
# good
def self.create_translation(phrase_id,
phrase_key,
target_locale,
value,
user_id,
do_xss_check,
allow_verification)
...
end
# bad
def self.create_translation(phrase_id, phrase_key, target_locale,
value, user_id, do_xss_check, allow_verification)
...
end
# good
def is_eligible?(user)
Trebuchet.current.launch?(ProgramEligibilityHelper::PROGRAM_TREBUCHET_FLAG) &&
is_in_program?(user) &&
program_not_expired
end
# bad
def is_eligible?(user)
Trebuchet.current.launch?(ProgramEligibilityHelper::PROGRAM_TREBUCHET_FLAG) &&
is_in_program?(user) &&
program_not_expired
end
Inline:
sum = 1 + 2
a, b = 1, 2
1 > 2 ? true : false; puts 'Hi'
[1, 2, 3].each { |e| puts e }
some(arg).other
[1, 2, 3].length
Newlines:
if @reservation_alteration.checkin == @reservation.start_date &&
@reservation_alteration.checkout == (@reservation.start_date + @reservation.nights)
redirect_to_alteration @reservation_alteration
end
Line Length
Keep each line of code to a readable length. Unless you have a reason to, keep lines to fewer than 100 characters. Keeping code visually grouped together (as a 100-character line limit enforces) makes it easier to understand. For example, you don't have to scroll back and forth on one line to see what's going on -- you can view it all together.
Here are examples from our codebase showing several techniques for breaking complex statements into multiple lines that are all < 100 characters. Notice techniques like:
scope = Translation::Phrase.includes(:phrase_translations).
joins(:phrase_screenshots).
where(:phrase_screenshots => {
:controller => controller_name,
:action => JAROMIR_JAGR_SALUTE,
})</pre>
<pre name="code" class="ruby">
translation = FactoryGirl.create(
:phrase_translation,
:locale => :is,
:phrase => phrase,
:key => 'phone_number_not_revealed_time_zone',
:value => 'Símanúmerið þitt verður ekki birt. Það er aðeins hægt að hringja á '\
'milli 9:00 og 21:00 %{time_zone}.'
)
if @reservation_alteration.checkin == @reservation.start_date &&
@reservation_alteration.checkout == (@reservation.start_date + @reservation.nights)
redirect_to_alteration @reservation_alteration
end
<% if @presenter.guest_visa_russia? %>
<%= icon_tile_for(I18n.t("email.reservation_confirmed_guest.visa.details_header",
:default => "Visa for foreign Travelers"),
:beveled_big_icon => "stamp" do %>
<%= I18n.t("email.reservation_confirmed_guest.visa.russia.details_copy",
:default => "Foreign guests travelling to Russia may need to obtain a visa...") %>
<% end %>
<% end %>
These code snippets are very much more readable than the alternative:
scope = Translation::Phrase.includes(:phrase_translations).joins(:phrase_screenshots).where(:phrase_screenshots => { :controller => controller_name, :action => JAROMIR_JAGR_SALUTE })
translation = FactoryGirl.create(:phrase_translation, :locale => :is, :phrase => phrase, :key => 'phone_number_not_revealed_time_zone', :value => 'Símanúmerið þitt verður ekki birt. Það er aðeins hægt að hringja á milli 9:00 og 21:00 %{time_zone}.')
if @reservation_alteration.checkin == @reservation.start_date && @reservation_alteration.checkout == (@reservation.start_date + @reservation.nights)
redirect_to_alteration @reservation_alteration
end
<% if @presenter.guest_visa_russia? %>
<%= icon_tile_for(I18n.t("email.reservation_confirmed_guest.visa.details_header", :default => "Visa for foreign Travelers"), :beveled_big_icon => "stamp" do %>
<%= I18n.t("email.reservation_confirmed_guest.visa.russia.details_copy", :default => "Foreign guests travelling to Russia may need to obtain a visa prior to...") %>
<% end %>
<% end %>
Though a pain to w0rite, comments are absolutely vital to keeping our code readable. The following rules describe what you should comment and where. But remember: while comments are very important, the best code is self-documenting. Giving sensible names to types and variables is much better than using obscure names that you must then explain through comments.
When writing your comments, write for your audience: the next contributor who will need to understand your code. Be generous — the next one may be you!
Portions of this section borrow heavily from the Google C++ and Python style guides.
File/class-level comments:
Every class definition should have an accompanying comment that describes what it is for and how it should be used.
A file that contains zero classes or more than one class should have a comment at the top describing its contents.
# Automatic conversion of one locale to another where it is possible, like
# American to British English.
module Translation
# Class for converting between text between similar locales.
# Right now the only conversion between American English -> British, Canadian,
# Australian, New Zealand variations are provided.
class PrimAndProper
def initialize
@converters = { :en => { :"en-AU" => AmericanToAustralian.new,
:"en-CA" => AmericanToCanadian.new,
:"en-GB" => AmericanToBritish.new,
:"en-NZ" => AmericanToKiwi.new,
} }
end
...
# Applies transforms to American English that is common to
# variants of all other English colonies.
class AmericanToColonial
...
end
# Converts American to British English.
# In addition to general Colonial English variations, changes "apartment"
# to "flat".
class AmericanToBritish < AmericanToColonial
...
end
All files, including data and config files, should have file-level comments. From
translation/config/colonial_spelling_variants.yml:
List of American-to-British spelling variants.
#
# This list is made with
# lib/tasks/list_american_to_british_spelling_variants.rake.
#
# It contains words with general spelling variation patterns:
# [trave]led/lled, [real]ize/ise, [flav]or/our, [cent]er/re, plus
# and these extras:
# learned/learnt, practices/practises, airplane/aeroplane, ...
sectarianizes: sectarianises
neutralization: neutralisation
...
Function comments:
Every function declaration should have comments immediately preceding it that describe what the function does and how to use it. These comments should be descriptive ("Opens the file") rather than imperative ("Open the file"); the comment describes the function, it does not tell the function what to do. In general, these comments do not describe how the function performs its task. Instead, that should be left to comments interspersed in the function's code.
Every function should mention what the inputs and outputs are, unless it meets all of the following criteria:
You may use whatever format you wish. In Ruby, two popular function documentation schemes are TomDoc and YARD. You can also just write things out concisely:
# Returns the fallback locales for the_locale.
# If opts[:exclude_default] is set, the default locale, which is otherwise
# always the last one in the returned list, will be excluded.
#
# For example:
# fallbacks_for(:"pt-BR")
# => [:"pt-BR", :pt, :en]
# fallbacks_for(:"pt-BR", :exclude_default => true)
# => [:"pt-BR", :pt]
def fallbacks_for(the_locale, opts = {})
...
end
Block and inline comments:
The final place to have comments is in tricky parts of the code. If you're going to have to explain it at the next code review, you should comment it now. Complicated operations get a few lines of comments before the operations commence. Non-obvious ones get comments at the end of the line.
def fallbacks_for(the_locale, opts = {})
# dup() to produce an array that we can mutate.
ret = @fallbacks[the_locale].dup
# We make two assumptions here:
# 1) There is only one default locale (that is, it has no less-specific
# children).
# 1) The default locale is just a language. (Like :en, and not :"en-US".)
if opts[:exclude_default] &&
ret.last == self.default_locale &&
ret.last != language_from_locale(the_locale)
ret.pop
end
ret
end
On the other hand, never describe the code. Assume the person reading the code knows the language (though not what you're trying to do) better than you do.
Punctuation, spelling and grammar:
Pay attention to punctuation, spelling, and grammar; it is easier to read well-written comments than badly written ones.
Comments should be as readable as narrative text, with proper capitalization and punctuation. In many cases, complete sentences are more readable than sentence fragments. Shorter comments, such as comments at the end of a line of code, can sometimes be less formal, but you should be consistent with your style.
Although it can be frustrating to have a code reviewer point out that you are using a comma when you should be using a semicolon, it is very important that source code maintains a high level of clarity and readability. Proper punctuation, spelling, and grammar help with that goal.
TODO comments:
Use TODO comments for code that is temporary, a short-term solution, or good-enough but not perfect.
TODOs should include the string TODO in all caps, followed by the full name of the person who can best provide context about the problem referenced by the TODO, in parentheses. A colon is optional. A comment explaining what there is to do is required. The main purpose is to have a consistent TODO format that can be searched to find the person who can provide more details upon request. A TODO is not a commitment that the person referenced will fix the problem. Thus when you create a TODO, it is almost always your name that is given.
# 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.
Commented-out code:
Never leave commented-out code in our codebase.
MethodsMethod definitions:
def some_method
# body omitted
end
def some_method_with_parameters(arg1, arg2)
# body omitted
end
# 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
Method calls:
Use parentheses for a method call:
# bad
@current_user = User.find_by_id 1964192
# good
@current_user = User.find_by_id(1964192)
# bad
put! (x + y) % len, value
# good
put!((x + y) % len, value)
# bad
f (3 + 2) + 1
# good
f(3 + 2) + 1
# bad
nil?()
# good
nil?
# okay
render(:partial => 'foo')
# okay
render :partial => 'foo'
In either case:
# bad
get '/v1/reservations', { :id => 54875 }
# good
get '/v1/reservations', :id => 54875
Conditional Expressions
Conditional keywords:
# bad
if some_condition then
...
end
# good
if some_condition
...
end
# bad - this doesn't fit on one line
add_trebuchet_experiments_on_page(request_opts[:trebuchet_experiments_on_page]) if request_opts[:trebuchet_experiments_on_page] && !request_opts[:trebuchet_experiments_on_page].empty?
# okay
if request_opts[:trebuchet_experiments_on_page] &&
!request_opts[:trebuchet_experiments_on_page].empty?
add_trebuchet_experiments_on_page(request_opts[:trebuchet_experiments_on_page])
end
# bad - this is complex and deserves multiple lines and a comment
parts[i] = part.to_i(INTEGER_BASE) if !part.nil? && [0, 2, 3].include?(i)
# okay
return if self.reconciled?
# bad
unless success?
puts 'failure'
else
puts 'success'
end
# good
if success?
puts 'success'
else
puts 'failure'
end
# bad
unless foo? && bar?
...
end
# okay
if !(foo? && bar?)
...
end
# bad
if (x > 10)
...
end
# good
if x > 10
...
end
# ok
if (x = self.next_value)
...
end
Ternary operator:
# bad
result = if some_condition then something else something_else end
# good
result = some_condition ? something : something_else
# bad
some_condition ? (nested_condition ? nested_something : nested_something_else) : something_else
# good
if some_condition
nested_condition ? nested_something : nested_something_else
else
something_else
end
arr = [1, 2, 3]
# bad
for elem in arr do
puts elem
end
# good
arr.each { |elem| puts elem }
names = ["Bozhidar", "Steve", "Sarah"]
# good
names.each { |name| puts name }
# bad
names.each do |name| puts name end
# good
names.select { |name| name.start_with?("S") }.map { |name| name.upcase }
# bad
names.select do |name|
name.start_with?("S")
end.map { |name| name.upcase }
Some will argue that multiline chaining would look okay with the use of {...}, but they should ask themselves if this code is really readable and whether the block's content can be extracted into nifty methods.
# bad
def some_method(some_arr)
return some_arr.size
end
# good
def some_method(some_arr)
some_arr.size
end
# good - shows intended use of assignment
if (v = array.grep(/foo/))
...
end
# bad
if v = array.grep(/foo/)
...
end
# also good - shows intended use of assignment and has correct precedence
if (v = self.next_value) == "hello"
...
end
# set name to Bozhidar, only if it's nil or false
name ||= 'Bozhidar'
# bad - would set enabled to true even if it was false
enabled ||= true
# good
enabled = true if enabled.nil?
# bad
result = hash.map { |k, v| v + 1 }
# good
result = hash.map { |_, v| v + 1 }
# bad
bluths.map { |bluth| bluth.occupation }
bluths.select { |bluth| bluth.blue_self? }
# good
bluths.map(&:occupation)
bluths.select(&:blue_self?)
Naming
payment, _ = Payment.complete_paypal_payment!(params[:token],
native_currency,
created_at)
Classes
class Parent
@@class_var = 'parent'
def self.print_class_var
puts @@class_var
end
end
class Child < Parent
@@class_var = 'child'
end
Parent.print_class_var # => will print "child"
As you can see all the classes in a class hierarchy actually share one class variable. Class instance variables should usually be preferred over class variables.
class TestClass
# bad
def TestClass.some_method
...
end
# good
def self.some_other_method
...
end
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
class SomeClass
def public_method
# ...
end
private
def private_method
# ...
end
end
Exceptions
# 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
# bad
begin
# an exception occurs here
rescue Exception
# exception handling
end
# good
begin
# an exception occurs here
rescue StandardError
# exception handling
end
# acceptable
begin
# an exception occurs here
rescue
# exception handling
end
Collections
# bad
hash = { 'one' => 1, 'two' => 2, 'three' => 3 }
# good
hash = { :one => 1, :two => 2, :three => 3 }
hash = {
:protocol => 'https',
:only_path => false,
:controller => :users,
:action => :set_password,
:redirect => @redirect_url,
:secret => @secret,
}
Strings
# bad
email_with_name = user.name + ' <' + user.email + '<'
# good
email_with_name = "#{user.name} <#{user.email}>"
Furthermore, keep in mind Ruby 1.9-style interpolation. Let's say you are composing cache keys like this:
CACHE_KEY = '_store'
cache.write(@user.id + CACHE_KEY)
Prefer string interpolation instead of string concatentation:
CACHE_KEY = '%d_store'
cache.write(CACHE_KEY % @user.id)
# good and also fast
html = ''
html << '<h1>Page title</h1>'
paragraphs.each do |paragraph|
html << "<p>#{paragraph}</p>"
end
Regular Expressions
# bad
/(regexp)/ =~ string
...
process $1
# good
/(?<meaningful_var>regexp)/ =~ string
...
process meaningful_var
string = "some injection\nusername"
string[/^username$/] # matches
string[/\Ausername\z/] # don't match
regexp = %r{
start # some text
\s # white space char
(group) # first group
(?:alt1|alt2) # some alternation
end
}x
Percent Literals
STATES = %w(draft open closed)
# should be '<div class="text">Some text</div>'
# bad - no double-quotes
%(This is #{quality} style)
# should be "This is #{quality} style"
# bad - multiple lines
%(<div>\n<span class="big">#{exclamation}</span>\n</div>)
# should be a heredoc.
# good - requires interpolation, has quotes, single line
%(<tr><td class="name">#{name}</td>)
# bad
%r(\s+)
# still bad
%r(^/(.*)$)
# should be /^\/(.*)$/
# good
%r(^/blog/2011/(.*)$)
Percent Literals
STATES = %w(draft open closed)
# should be '<div class="text">Some text</div>'
# bad - no double-quotes
%(This is #{quality} style)
# should be "This is #{quality} style"
# bad - multiple lines
%(<div>\n<span class="big">#{exclamation}</span>\n</div>)
# should be a heredoc.
# good - requires interpolation, has quotes, single line
%(<tr><td class="name">#{name}</td>)
# bad
%r(\s+)
# still bad
%r(^/(.*)$)
# should be /^\/(.*)$/
# good
%r(^/blog/2011/(.*)$)
Previous: Ruby Literals
Next: Ruby Variables, Constants
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4