This is the coding style guide we use at FreeAgent for our Ruby apps. We encourage you to set up one that works for your own team.
Much of this was based on the GitHub Ruby Style Guide. Feel free to fork the guide but we won't accept pull requests from non-FreeAgent staff, unless they're for typos etc.
- Use two spaces per indentation level (aka soft tabs). No hard tabs.
# bad - four spacesdefsome_methoddo_somethingend# gooddefsome_methoddo_somethingendKeep lines equal to or fewer than 115 characters. (Width of github's diff view without wrapping.)
Never leave trailing whitespace.
End each file with a blank newline.
Use spaces around operators, after commas, colons and semicolons. Use spaces around
{and before}in blocks.
sum=1 + 2a,b=1,21 > 2 ? true : false;puts"Hi"[1,2,3].each{ |e| putse}- No spaces after
(,[or before],). No spaces after{and before}in hash declarations.
some(arg).other[1,2,3].lengthsome_hash={one: 1,two: 2,three: 3}- No spaces after
!.
!array.include?(element)- Use spaces inside
<%...%>.
<% if condition %><% else %><% end %>- Indent
whenas deep as the correspondingend.
casewhensong.name == "Misty"puts"Not again!"whensong.duration > 120puts"Too long!"whenTime.now.hour > 21puts"It's too late"elsesong.playendkind=caseyearwhen1850..1889then"Blues"when1890..1909then"Ragtime"when1910..1929then"New Orleans Jazz"when1930..1939then"Swing"when1940..1950then"Bebop"else"Jazz"end- Use empty lines between method (
def) blocks, and within methods to break up method code into logical paragraphs.
defsome_methoddata=initialize(options)data.manipulate!data.resultenddefsome_methodresultend- Last line of a multiline array or hash should end with a trailing comma. It keeps diffs much smaller when adding or deleting lines in future.
["one","two",]- Last element of an array or hash on a single line should omit the trailing comma however.
# Bad["one",2,]# Good["one",2]Use TomDoc to the best of your ability. It's pretty sweet:
# 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.defmultiplex(text,count)text * countendTo check and generate documentation install Yard with TomDoc Plugin
gem install yard yard-tomdocRun your isolated file through the documentation parser
yard doc --plugin tomdoc $FILENAME
open doc/index.htmlYou do not need to commit the generated ./doc or .yardoc files.
- Use
defwith parentheses when there are arguments. Omit the parentheses when the method doesn't accept any arguments.
defsome_method# body omittedenddefsome_method_with_arguments(arg1,arg2)# body omittedend- Never use
for, unless you know exactly why. Most of the time iterators should be used instead.foris implemented in terms ofeach(so you're adding a level of indirection), but with a twist -fordoesn't introduce a new scope (unlikeeach) and variables defined in its block will be visible outside it.
arr=[1,2,3]# badforeleminarrdoputselemend# goodarr.each{ |elem| putselem}- Never use
thenfor multi-lineif/unless.
# badifsome_conditionthen# body omittedend# goodifsome_condition# body omittedend- Avoid the ternary operator (
?:) except in cases where all expressions are extremely trivial. However, do use the ternary operator(?:) overif/then/else/endconstructs for single line conditionals.
# badresult=ifsome_conditionthensomethingelsesomething_elseend# goodresult=some_condition ? something : something_else- Use one expression per branch in a ternary operator. This also means that ternary operators must not be nested. Prefer
if/elseconstructs in these cases.
# badsome_condition ? (nested_condition ? nested_something : nested_something_else) : something_else# goodifsome_conditionnested_condition ? nested_something : nested_something_elseelsesomething_elseendThe
andandorkeywords are banned. It's just not worth it. Always use&&and||instead.Avoid multi-line
?:(the ternary operator), useif/unlessinstead.Favor modifier
if/unlessusage when you have a single-linebody.
# badifsome_conditiondo_somethingend# gooddo_somethingifsome_condition- Never use
unlesswithelse. Rewrite these with the positive case first.
# badunlesssuccess?puts"failure"elseputs"success"end# goodifsuccess?puts"success"elseputs"failure"end- Don't use parentheses around the condition of an
if/unless/while.
# badif(x > 10)# body omittedend# goodifx > 10# body omittedend- Prefer
{...}overdo...endfor single-line blocks. Avoid using{...}for multi-line blocks (multiline chaining is always ugly). Always usedo...endfor "control flow" and "method definitions" (e.g. in Rakefiles and certain DSLs). Avoiddo...endwhen chaining.
names=["Bozhidar","Steve","Sarah"]# goodnames.each{ |name| putsname}# badnames.eachdo |name|
putsnameend# goodnames.select{ |name| name.start_with?("S")}.map{ |name| name.upcase}# badnames.selectdo |name|
name.start_with?("S")end.map{ |name| name.upcase}Some will argue that multiline chaining would look OK with the use of {...}, but they should ask themselves - is this code really readable and can't the block's contents be extracted into nifty methods?
- Avoid
returnwhere not required.
# baddefsome_method(some_arr)returnsome_arr.sizeend# gooddefsome_method(some_arr)some_arr.sizeend- Use spaces around the
=operator when assigning default values to method parameters:
# baddefsome_method(arg1=:default,arg2=nil,arg3=[])# do something...end# gooddefsome_method(arg1=:default,arg2=nil,arg3=[])# do something...endWhile several Ruby books suggest the first style, the second is much more prominent in practice (and arguably a bit more readable).
- Using the return value of
=(an assignment) is ok.
# badif(v=array.grep(/foo/)) ...
# goodifv=array.grep(/foo/) ...
# also good - has correct precedence.if(v=next_value) == "hello" ...- Use
||=freely to initialize variables.
# set name to Bozhidar, only if it's nil or falsename ||= "Bozhidar"- Don't use
||=to initialize boolean variables. (Consider what would happen if the current value happened to befalse.)
# bad - would set enabled to true even if it was falseenabled ||= true# goodenabled=trueifenabled.nil?Avoid using Perl-style special variables (like
$0-9,$,etc. ). They are quite cryptic and their use in anything but one-liner scripts is discouraged. Prefer long form versions such as$PROGRAM_NAME.Never put a space between a method name and the opening parenthesis.
# badf(3 + 2) + 1# goodf(3 + 2) + 1If the first argument to a method begins with an open parenthesis, always use parentheses in the method invocation. For example, write
f((3 + 2) + 1).Prefix with
_unused block parameters and local variables. It's also acceptable to use just_(although it's a bit less descriptive). This convention is recognized by the Ruby interpreter and tools like RuboCop and will suppress their unused variable warnings.# badresult=hash.map{ |k,v| v + 1}defsomething(x)unused_var,used_var=something_else(x)# ...end# goodresult=hash.map{ |_k,v| v + 1}defsomething(x)_unused_var,used_var=something_else(x)# ...end# goodresult=hash.map{ |_,v| v + 1}defsomething(x)_,used_var=something_else(x)# ...end
Don't use the
===(threequals) operator to check types.===is mostly an implementation detail to support Ruby features likecase, and it's not commutative. For example,String === "hi"is true and"hi" === Stringis false. Instead, useis_a?orkind_of?if you must.
Refactoring is even better. It's worth looking hard at any code that explicitly checks types.
- Avoid
::when nesting modules (at least in the application)
# not goodmoduleFoo;endmoduleFoo::Bar;endmoduleFoo::Bar::Bazdefself.nputsModule.nesting.inspect# => [Foo::Bar::Baz]endend# goodmoduleFoo;endmoduleFoomoduleBar;endendmoduleFoomoduleBarmoduleBazdefself.nputsModule.nesting.inspect# => [Foo::Bar::Baz, Foo::Bar, Foo]endendendendThis can prevent complications when it comes to constant lookup.
Use
snake_casefor methods and variables.Use
CamelCasefor classes and modules. (Keep acronyms like HTTP, RFC, XML uppercase.)Use
SCREAMING_SNAKE_CASEfor other constants.The names of predicate methods (methods that return a boolean value) should end in a question mark. (i.e.
Array#empty?).The names of potentially "dangerous" methods (i.e. methods that modify
selfor the arguments,exit!, etc.) should end with an exclamation mark. Bang methods should only exist if a non-bang method exists. (More on this).
- Avoid the usage of class (
@@) variables due to their unusual behavior in inheritance.
classParent@@class_var="parent"defself.print_class_varputs@@class_varendendclassChild < Parent@@class_var="child"endParent.print_class_var# => will print "child"As you can see all the classes in a class hierarchy actually share oneclass variable. Class instance variables should usually be preferred over class variables.
- Use
def self.methodto define singleton methods. This makes the methods more resistant to refactoring changes.
classTestClass# baddefTestClass.some_method# body omittedend# gooddefself.some_other_method# body omittedendend- Avoid
class << selfexcept when necessary, e.g. single accessors and aliased attributes.
classTestClass# badclass << selfdeffirst_method# body omittedenddefsecond_method_etc# body omittedendend# goodclass << selfattr_accessor:per_pagealias_method:nwo,:find_by_name_with_ownerenddefself.first_method# body omittedenddefself.second_method_etc# body omittedendend- Indent the
public,protected, andprivatemethods as much the method definitions they apply to. Leave one blank line above and below them.
classSomeClassdefpublic_method# ...endprivatedefprivate_method# ...endend- Avoid explicit use of
selfas the recipient of internal class or instance messages unless to specify a method shadowed by a variable.
classSomeClassattr_accessor:messagedefgreeting(name)message="Hi #{name}"# local variable in Ruby, not attribute writerself.message=messageendend- Avoid explicit use of instance variables
# badclassSomeClassdefinitialize(foo)@foo=fooendenddeffoo?@foo == "Foo"end# goodclassSomeClassattr_reader:foodefinitialize(foo)@foo=fooenddeffoo?foo == "Foo"endend# better (if `foo` doesn't need to be public)classSomeClassdefinitialize(foo)@foo=fooenddeffoo?foo == "Foo"endprivateattr_reader:fooend- Avoid complex logic in the initialiser
# badclassSomeClassdefinitialize(foo)@foo=foo@bar=some_method(foo)endend# goodclassSomeClassdefinitialize(foo)@foo=fooenddefbar@bar ||= some_method(foo)endend# good (using a class method)classSomeClassattr_reader:foodefself.from_id(id)new(Foo.find(id))enddefinitialize(foo)@foo=fooendend# good (using an instance method)classSomeClassdefinitialize(foo_id)@foo_id=foo_idenddeffoo@foo ||= Foo.find(foo_id)endprivateattr_reader:foo_idend- Don't use exceptions for flow of control.
# badbeginn / drescueZeroDivisionErrorputs"Cannot divide by 0!"end# goodifd.zero?puts"Cannot divide by 0!"elsen / dend- Don't use bare rescues or rescue the
Exceptionclass.
# badbegin# an exception occurs hererescueException=>e# exception handlingend# badbegin# an exception occurs hererescue=>e# exception handlingend# goodbegin# an exception occurs hererescueStandardError=>e# error handling hereend- Use the letter
efor your short rescue variable.
# badbegin# an exception occurs hererescueStandardError=>ex# exception handlingend# goodbegin# an exception occurs hererescueStandardError=>e# error handling hereend- Skip the rescue variable if you aren't going to use it.
# badbegin# an exception occurs hererescueStandardError=>eRails.logger.error("A problem happened!")end# goodbegin# an exception occurs hererescueStandardErrorRails.logger.error("A problem happened!")end- Prefer
%wto the literal array syntax when you need an array of strings.
# badSTATES=["draft","open","closed"]# goodSTATES=%w(draftopenclosed)Use
Setinstead ofArraywhen dealing with unique elements.Setimplements a collection of unordered values with no duplicates. This is a hybrid ofArray's intuitive inter-operation facilities andHash's fast lookup.Use symbols instead of strings as hash keys, and use the Ruby 1.9 hash syntax rather than hash rockets where possible.
# badhash={"one"=>1,"two"=>2,"three"=>3}# goodhash={one: 1,two: 2,three: 3}When splitting a hash over multiple lines, place one key/value pair per line with the closing brace on the line after the last key/value pair.
Indent the contents of multiline hashes one level deeper than the preceeding code, don't line the hash up with the braces.
# badhash=Contact.create(first_name: "Robert",last_name: "Burns",email: "haggis@burns.net")# goodhash=Contact.create(first_name: "Robert",last_name: "Burns",email: "haggis@burns.net",)- Drop
{}around arguments when the there is only one hash as the argument, whether parens are included or not
# badhash=Contact.create({first_name: "Robert",last_name: "Burns"})# goodhash=Contact.create(first_name: "Robert",last_name: "Burns")# also goodhash=Contact.createfirst_name: "Robert",last_name: "Burns"Add spacing to line up the hash rockets and/or values in columns if it helps readability.
Don't use symbols where you have dynamic key names.
# badhash={:"user_#{id}"=>"fred"}- Prefer string interpolation instead of string concatenation:
# bademail_with_name=user.name + " <" + user.email + ">"# goodemail_with_name="#{user.name} <#{user.email}>"- Prefer double-quoted strings. Interpolation and escaped characters will always work without a delimiter change, and
'is a lot more common than"in string literals.
# badname='Bozhidar'# goodname="Bozhidar"- Avoid using
String#+when you need to construct large data chunks. Instead, useString#<<. Concatenation mutates the string instance in-place and is always faster thanString#+, which creates a bunch of new string objects.
# good and also fasthtml=""html << "<h1>Page title</h1>"paragraphs.eachdo |paragraph|
html << "<p>#{paragraph}</p>"end- Add the
# frozen_string_literal: trueto the top of all files. This implicitly freezes all the string literals created in that file, which puts less pressure on garbage collection.
# frozen_string_literal: trueclassFoodefinitializestring="I'm frozen!"endend- Avoid using
$1-9as it can be hard to track what they contain. Named groups can be used instead.
# bad/(regexp)/ =~ string
...
process $1
# good/(?<meaningful_var>regexp)/ =~ string
...
processmeaningful_var- Be careful with
^and$as they match start/end of line, not string endings. If you want to match the whole string use:\Aand\z.
string="some injection\nusername"string[/^username$/]# matchesstring[/\Ausername\z/]# don't match- Use
xmodifier for complex regexps. This makes them more readable and you can add some useful comments. Just be careful as spaces are ignored.
regexp=%r{ start # some text\s # white space char (group) # first group (?:alt1|alt2) # some alternation end }x- Use
%wfreely.
STATES=%w(draftopenclosed)- Use
%()for single-line strings which require both interpolation and embedded double-quotes. For multi-line strings, prefer heredocs.
# bad (no interpolation needed)%(<div class="text">Some text</div>)# 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>)- Use
%ronly for regular expressions matching more than one '/' character.
# bad%r(\s+)# still bad%r(^/(.*)$)# should be /^\/(.*)$/# good%r(^/blog/2011/(.*)$)Follow your ❤️