Ruby Style Guide
These rules may be broken if it increases code clarity or maintainability
Always default to using the style of the code you are modifying
Design
Classes should do one thing and one thing only
If a class has only one method, consider using a lambda or proc instead
Why? A class with one method, especially Doer.do, is just a function, so make it a lambda
For a base class with abstract methods, include the methods in the base class and have them raise.
Why? This gives you a single place to document which methods subclasses are expected to implement, and ensures that, at least at runtime, they are implemented
Avoid instantiating classes inside other classes. Prefer dependency injection and default parameter values.
Why? This makes it easier to test the classes, but doesn't require Herculean efforts to instantiate classes at runtime:
class PersonFormatter
def format(person)
# whatever
end
end
# Wrong; we are tightly coupled to an implementation and have to use
# crazy mocks to fake this out
class View
def render
# code
puts PersonFormatter.new.format(person)
# more code
end
# Correct; Option 1 - constructor injection
class View
def initialize(person_formatter=PersonFormatter.new)
@person_formatter = person_formatter
end
def render
# code
puts @person_formatter.format(person)
# more code
end
end
# Correct; Option 2 - setter injection with a sensible default
class View
attr_writer :person_formatter
def initialize
@person_formatter = PersonFormatter.new
end
def render
# code
puts @person_formatter.format(person)
# more code
end
end
Don't make methods public unless they are part of the public interface
Why? Public is the way to formally document the contract of your class. Putting methods that shouldn't be called by others in the public interface is just lazy, and increases maintenance down the road.
protected
is likely not correct; only use it for the template method pattern and even then, would a lambda or proc work better?
Why? Protected is used to allow subclasses to call non-public methods. This implies a potentially complex type hierarchy, which is likely not correct for what you are doing.
ClassMethods
pattern for sharing class methods via a module, since simple use of extend
will do
Avoid the Why? Although it was idiomatic in Rails, the whole idea of overriding included
to call extend
is a bit overkill. The client code can simply use extend
. You could use the InstanceMethods
concept to add instance level methods if you like. See this article from Yehuda Katz for more info.
module Helper
def has_strategy(strat)
cattr_accessor :strategy
# or singleton_class.send(:attr_accessor,:strategy) if not in Rails land
self.strategy = strat
include InstanceMethods
end
module InstanceMethods
def operate
case self.class.strategy
when :foo then puts "FOO!"
when :bar then puts "BAR!"
else
puts "Doing #{self.class.strategy}"
end
end
end
end
class UsesHelper
extend Helper
has_strategy :foo
end
UsesHelper.new.operate
Do not use ivars as a way of avoiding method parameters.
Why? Instance variables are a form of global data, and your routines' complexity increases when their input comes from multiple sources. If the instance variables control flow or logic, pass them in as parameters
Private methods should be used to make public methods clear; they should avoid reliance on ivars if at all possible
Why? Private methods that do not rely on instance variables can very easily be extracted to new classes when things get compledx
Private methods calling more private methods might indicate a second class is hiding inside this class
Files
One class/module per file
Why? Makes it simpler to locate and organize code
Follow Rails conventions in Rails apps
lib
, stdlib extensions go in ext
For non-Rails apps, code goes in Why? This is where most Rubyists will expect it, and clearly delineates your app from lib extensions
Foo::Bar::Baz
should be in foo/bar/baz.rb
, relative to lib
or ext
Code for a class/module goes in the lower-cased, snake-cased named file, in a directory structure that matches the module namespaces. For example, the class Why? Makes it simpler to locate the code for a given class
Test::Unit
files should go in test
and be named class_test.rb
, e.g. to test the class Foo
, create test/foo_test.rb
.
Why? Test dir is then sorted by classname, making it easy to visually scan or auto-complete test names
Nest test classes to match modules if you have a large library with a lot of different namespaces
Follow RSpec and Cucumber file/dir naming conventions
Formatting
2-space indent
Why? this is standard across almost all Ruby code
For more than 2 groups of arrowed, equaled, or coloned lines (e.g. a hash or set of local variables), align the operators so that the code forms a 'table'
Why? Although it's extra work to maintain and can break diffs, I feel that the clarity and readability of this structure is worth it - the code is read many more times than changed
Avoid single-line methods
Why? Single-line methods are harder to change when the method needs an additional line
Keep line lengths 80 characters or less unless doing so negatively impacts readability (e.g. for large string literals)
Why? Excessively long lines can be very difficult to read and understand; a low line length encourages keeping things simple
Literal arrays or hashes should have a comma after every element, including the last one
Why? This makes it easier to add elements and reorder elements without having to worry about missing commas
For 'options-hash' calls, align keys across multiple lines, one per line, e.g.
Why? This makes it easier to read and modify the options sent to the method
some_call(non_option1,non_option2,:foo => "bar",
:blah => "quux",
:bar => 42)
attr_reader
or attr_accessor
, put one per line
When declaring attributes with Why? This makes it easier to add new attributes without creating complex diffs. It also documents what the attributes represent
# Wrong; hard to modify
attr_accessor :first_name, :last_name, :gender
# Right; we can easily modify and document
attr_accessor :first_name
attr_accessor :last_name
attr_accessor :gender
Literal lists should be on one line or one element per line, depending on length
Why? Two-dimensional data is hard to read and modify.
protected
and private
should be aligned with class
Why? No reason, just my personal preference
class SomeClass
def public_method
end
protected
def protected_method
end
private
def private_method
end
end
protected
and private
are surrounded by newlines
Why? Visually sets them off
class
or module
A newline after Why? Visual clearance
end
unless it's the string of end
s for the end of the class/module
A newline after Why? I don't get much value from visual clearance for the end blocks that 'close' a class or module
def self.method_name
and not inside a class << self
Class methods should be defined via Why? This reduces errors caused by moving methods around, and also doesn't require you to scroll up in the class to determine what type of method it is; you can see it instantly
if
expression, indent the else
and end
with the if
:
When using the return value of an Why? This makes it very clear that you are using if..else..end
as a complex expression and not as a control structure
# Wrong
result = if something?
:foo
else
:bar
end
# Right
result = if something?
:foo
else
:bar
end
Naming
Variables
Avoid abbreviations
Why? Abbreviations can clash, require explanation and generally disrupt the flow of things
Avoid one-character names
Why? No reason not to be descriptive
For non 'primitive' types, use the name of the class, or the name of the class plural if a collection, unless there will be multiple instances in scope, then do not follow this convention
Why? When there's no particular specific name for something, using its type makes it easy to remember what the object is
# Just use the classname
def routine
customer = Customer.find(124)
customers = Customer.find_all_by_gender("M")
end
# Here we have two lists, so neither should just be "customers"
def other_routine
males = Customer.find_all_by_gender("M")
minors = Customer.where('age < ?',18)
end
# Here we have a very long method that, even though it should be refactored,
# isn't, so we use a more verbose variable name to keep things clear 50 lines in.
def other_routine
# tons of code
male_customers = Customer.find_all_by_gender("M")
minor_customers = Customer.where('age < ?',18)
# tons more code
end
foo_proc
or foo_block
For procs and lambdas, use a verb as opposed to Why? Procs and lambdas are more like methods and thus should be verbs, since they do something
# Wrong; the variable has been needlessly "nounified" for no real benefit
saver = lambda { |x| x.save! }
# Correct; the variable, being a verb, is instantly recognizable as an action
save = lambda { |x| x.save! }
Methods
foo
and foo=
for 'accessors'.
Follow the 'referentially transparent' naming scheme of using Why? Since Ruby allows the '=' form, using 'get' or 'set' in the names just adds noise
Boolean methods end in a question mark
Why? This allows methods that can be used in expressions to clearly stand out and makes code more fluent
Dangerous methods end in a bang.
Why? This calls out methods whose use should be carefully understood
# Mutates the object; dangerous on an otherwise immutable object
def process!
@processed = true
end
# Has a side-effect that is not obvious and might not be idempotent
def render!(output)
queue_event(:some_event)
output.puts @content
end
# Raises an exception on failure, unlike its analog, save, which does not
def save!
raise ValidationException unless self.save
end
Classes And Modules
For non-Rails apps, namespace all classes in a top-level module named for your app or library
Why? Prevents naming clashes when your code is used with other libraries
Class names should be comprehensible without their module namespace.
Why? Ensures that classnames are understood everywhere used.
# Wrong; 'Base' is not an accurate classname
class ActiveRecord::Base
end
# Good; using the class without its namespaced module doesn't remove any clarity
class Gateway::BraintreeGateway
end
Class names should be nouns
Why? Classes represent things, and things are nouns
Enumerable
Non-namespace module names should tend toward adjectives, e.g. Why? Modules used as mixins represent a trait or aspect that you want to use to enhance a class. These are naturally adjectives.
Namespace module names should tend toward nouns
Why? Using modules just for namespacing is, again, specifying things, which are nouns
General Style
Avoid 1.9-style hash syntax
Why? This syntax only works if your hash keys are symbols; otherwise you have to use the old syntax. There's a very limited benefit to the new syntax, and the cost is two ways of doing things, increasing mental overhead when reading/writing code. Further, for libraries, 1.8 compatibility is nice to achieve
For libraries or CLI apps, stick to 1.8-compatible features where possible
Why? Many shops are still on 1.8, and many systems have 1.8 as the default, so there's no reason not to keep 1.8 compatible for code that will be shared
Blocks that return a value you intend to use should use curly braces
Why? This visually sets them off, and makes for a cleaner chaning syntax
# Wrong; calling a method on 'end' is awkward looking
male_teens = Customers.all.select do |customer|
customer.gender == :male
end.reject do |man|
man.age > 19 || man.age < 13
end
# Right; the chaining and use of the comprehensions is clear
male_teens = Customers.all.select { |customer|
customer.gender == :male
}.reject { |man|
man.age > 19 || man.age < 13
}
do..end
Blocks that do not return a value, return a value you will ignore, or represent a method definition use Why? These blocks are more like control structures or methods, so do..end
is more natural. It sets them off from blocks that produce a useful value
Always use parens when calling methods with arguments unless you are coding in a heavy 'DSL Style' bit of code
Why? Parens visually set off the parameters, and reduce confusion about how Ruby will parse the line, making the code easier to maintain
# Given this code
class Person
def self.create(name,status)
# ..
end
end
Person.create 'Dave', :single
# and we change it so that :status is based on a condition
# Wrong; what does this line do?
Person.create 'Dave', wife || :single
# If we used parens from the get go, it's a no brainer
Person.create('Dave',:single)
# Right; code is clear
Person.create('Dave',wife || :single)
else
with an unless
Do not use Why? the expression becomes too difficult to unwind, just use an if
unless
for any expression that requires a ||
, &&
, or !
. Either extract to a method or use if
Do not use an Why? unless
is like putting a giant !()
around your expression, so it becomes harder and harder to understand what is being tested by adding this. It's not worth it
# Wrong; too hard to figure out
unless person.valid? && !person.from('US')
# doit
end
# Right; DeMorgan's law simplified this
if !person.valid? || person.from('US')
# doit
end
# Better; use a method
unless valid_and_foreign?(person)
# doit
end
Use inline if/unless for 'early-exit' features only
Why? Code can become hard to read when conditions are trailing and complex. Early-exit conditions are generally simple and can benefit from this form
# Wrong; too complex
person.save unless person.from('US') || person.age > 18
# OK; an early exit
raise "person may not be nil" if person.nil?
Exception
unless you really want to catch 'em all.
Do not catch Why? Exception
is the base of all exceptions, and you typically don't want to catch memory exceptions and the like. Catch StandardError
instead
tap
:
When you must mutate an object before returning it, avoid creating intermediate objects and use Why? Intermediate objects increase the mental requirements for understanding a routine. tap
also creates a nice scope in which the object is being mutated; you will not forget to return the object when you change the code later
# Wrong
def eligible_person(name)
person = Person.create(:name => name)
person.update_eligibility(true)
person.save!
person
end
# Right
def eligible_person(name)
Person.create(:name => name).tap { |person|
person.update_eligibility(true)
person.save!
}
end
Enumerable
calls using simple blocks instead of one complex block that does everything.
Feel free to chain Why? The braces syntax encourages clean chaning, and with simple blocks, you can easily follow step-by-step what is being done without having to have a bunch of private methods explaining things.
.present?
or .nil?
to be clear as to what's being tested
Avoid conditionals based on the truthiness or falsiness of a variable. Use Why? It's a lot clearer when you state exactly what you are testing; this makes it easier to change the code later, and avoids sticky issues like 0 and the empty string being truthy
# Wrong; intent is masked
if person.name
# do it
end
# Right; we can see just what we're testing
if person.name.present?
# do it
end
If your method returns true or false, be sure those are the only values returned.
Why? Returning a 'truthy' value will lead to unintended consequences, and could lead to complex dependencies in your code. You don't need the hassle
Testing
Unit test methods should have three parts: Given, When, Then
Why? This makes it clear what each section of the test is doing, which is crucial when tests get complex
def test_something
# Given
person = Person.new("Dave",38)
# When
minor = person.minor?
# Then
assert minor,"Expected #{person.inspect} to be a minor"
end
For mocking, include a fourth part before 'When' so that the flow of the test is maintained
Why? It's important to establish what the mock expectations are, and thinking of them as separate from the given helps the test read better.
def test_something
# Given
person = Person.new("Dave",38)
# When the test runs, Then
AuditStats.expects(:query).returns(true)
# When
minor = person.minor?
# Then
assert minor,"Expected #{person.inspect} to be a minor"
end
If your only assertion is that mock expectations are met, include a comment indicating this
Why? It's important to let others know your intent that the mock expectations ARE the test and that you just didn't forget an assert
def test_something
# Given
person = Person.new("Dave",38)
# When the test runs, Then
PersonDAO.expects(:save).with(person)
# When
person.save!
# Then mock expectations should've been met
end
For block-based assertions, place the code inside a lambda to preserve the structure
Why? Again, this preserves the flow of the test
def test_something
# Given
name = 'Dave'
# When
code = lambda { Person.create(:name => name) }
# Then
assert_difference('Person.count',&code)
assert_equal name,Person.last.name
end
any
method or some
method, e.g. some_int
Avoid literals that aren't relevant to the test; abstract into a Why? Tests littered with literals can be very hard to follow; if the only literals in the test are those specific to this test, it becomes very clear what's being tested.
For literals that are relevant to the test, do not repeat them in a test
Why? Just like magic strings are bad in real code, they are in test code. Plus it codifies that the values are the same by design and not just by happenstance
Extract setup and assertions that are the same by design into helper methods
Why? If you are running a different test under the exact same conditions as another test, extract that duplicated code into a helper. Similarly, if you are conducting a different test that should have the same outcome, extract those assertions to a helper.
Leave repeated code that is the same by happenstance
Why? This sort of duplication is OK because the code is only the same by happenstance, and may diverge as the code matures. By leaving it seperate, it's easier to change
The 'When' part of your tests should ideally use the public API of the class under test.
Why? Some RSpec constructs assert things about the class under test without calling its public API, e.g. person.should be_valid
. This goes against the spirit of TDD, and requires the reader to make a mental transaction between the testing DSL and the class' method, with no discernable benefit.
raise
instead of assert
If you need to sanity-check your setup in the 'Given', use Why? raise
will mark your test as erroneous, not failed, as that is the case when the setup conditions for the test aren't correct. Better yet, don't use brittle globally-shared test fixtures or factories.
Avoid fixtures, factories, or other globally-shared setup data.
Why? As an app matures, the fixtures or factories become incredibly brittle and hard to modify or understand. It also places key elements of your test setup far away from the test itself, making it hard to understand any given test.
Rails
Controllers
if
statements; controllers should be as dumb as possible
There should be very few Why? if
statements usually imply business logic, which does not belong in controllers. The logic in the controller should be mainly concerned with sending the correct response to the user.
Avoid excessive filters, or filters that are highly conditional
Why? When the number of filters increases, it becomes harder and harder to know what code is executing and in what order. Further, when filters set instance variables, it becomes increasingly difficult to understand where those variables are being set, and the filters become very order-specific. Finally, conditional filters, or filters used on only one controller method increase complexity beyond the point of utility
rake routes
should be the truth, the whole truth, and nothing but the truth
Why? By lazily creating all routes for a resource, when you only need a few to be valid, you create misleading output for newcomers, and allow valid named route methods to be created that can only fail at runtime or in production.
# Wrong; our app only supports create and show
resources :transactions
# Right; rake routes reflects the reality of our app now
resources :transactions, :only => [:create, :show]
Prefer exposing the exact objects views require rather than 'root' objects requiring deep traveral
Why? When views navigate deep into object hierarchies, it becomes very difficult to understand what data the views really do require, and it makes refactoring anything in those object hierarchies incredibly difficult
# A view for a person's credit cards requires the person's name, and a list of last-4, type, and expiration date of cards
# Wrong; the view must navigate through the person to get his credit cards and has
# access to the entire person object, which is not needed
def show
@person = Person.find(params[:person_id])
end
# Wrong; although the view can now access credit cards directly, it's still not clear what data
# is really needed by the view
def show
@person = Person.find(params[:person_id])
@credit_cards = @person.credit_cards
end
# Right; the ivars represent what the view needs AND contain only what the view needs.
# You may wish to use a more sophisticated "presenter" pattern instead of OpenStruct
def show
@person_name = Person.find(params[:person_id]).full_name
@credit_cards = @person.credit_cards.map { |card|
OpenStruct.new(:last_four => card.last_four,
:card_type => card.card_type,
:expiration_date => [card.expiration_month,card.expiration_year].join('/'))
}
end
Do not create ivars unless they are to be shared with the views.
Why? Controllers are special types of classes. You don't instantiate them, and the don't hold state. ivars in a controller are only for passing data to a view. If you need to pass data to prive methods, use method arguments.
Active Record
Avoid hooks
Why? Hooks make your models very hard to use in different ways, and lock them to business rules that are likely not all that hard and fast. They also make testing very difficult, as it becomes harder and harder to set up the correct state using objects that have excessive hooks on them.
# Wrong; we've hidden business logic behind a simple CRUD operation
class Person < ActiveRecord::Base
after_save :update_facebook
private
def update_facebook
# send Facebook some status update
end
end
# Better; we have a method that says what it does
class Person < ActiveRecord::Base
def save_and_update_facebook
if save
# Send Facebook some status update
end
end
end
Conditional validations should be used with care
Why? Validations that are not always applicable make it very hard to modify objects and enhance them, because it becomes increasingly difficult to understand what a valid object really is. Further, it becomes very difficult to set up objects in a particular state for a given test if there are a lot of conditonal validations
Use database constraints to enforce valid data in the database
Why? The database is the only place that can truly ensure various constraints, such as uniqueness. Constraints are incredibly useful for making sure that, regardless of bugs in your code, your data will be clean.
AR objects should be as dumb as possible; only derived values should be new methods
Why? It may be tempting to add business logic to your models, but this violates the single responsibility principle, and makes the classes harder and harder to understand, test, and modify. Treat your models as dumb structs with persistence, and put all other concerns on other classes. Do not just mix in a bunch of modules.