My Personal Ruby Style Guide
Why? Makes it simpler to locate and organize code
lib
, stdlib extensions go in ext
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
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
Why? this is standard across almost all Ruby code
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
Why? Single-line methods are harder to change when the method needs an additional line
Why? Excessively long lines can be very difficult to read and understand; a low line length encourages keeping things simple
Why? This makes it easier to add elements and reorder elements without having to worry about missing commas
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 lineWhy? 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
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 newlinesWhy? Visually sets them off
class
or module
Why? Visual clearance
end
unless it's the string of end
s for the end of the class/moduleWhy? I don't get much value from visual clearance for the end blocks that 'close' a class or module
def self.class_name
and not inside a class << self
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
: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
Why? Abbreviations can clash, require explanation and generally disrupt the flow of things
Why? No reason not to be descriptive
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
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! }
foo
and foo=
for 'accessors'.Why? Since Ruby allows the '=' form, using 'get' or 'set' in the names just adds noise
Why? This allows methods that can be used in expressions to clearly stand out and makes code more fluent
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
Why? Prevents naming clashes when your code is used with other libraries
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
Why? Classes represent things, and things are nouns
Enumerable
Why? Modules used as mixins represent a trait or aspect that you want to use to enhance a class. These are naturally adjectives.
Why? Using modules just for namespacing is, again, specifying things, which are nouns
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
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
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
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
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
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
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
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.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
: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.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 testedWhy? 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
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
Why? A class with one method, especially Doer.do, is just a function, so make it a lambda
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
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
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 doWhy? 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
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
Why? Private methods that do not rely on instance variables can very easily be extracted to new classes when things get compledx
Why? RDoc.info does not support TomDoc, and YARD is way too heavyweight
Why? RDoc will link to methods or classes in your project
Why? This makes it clear that you mean a method name or class, because RDoc cannot link outside of your codebase
Why? Don't restate what the code does, but DO explain why it works the way it does, especially if it does something in a suprising or weird way, from a business logic perspective
## Wrong; don't explain what the code does, we can read it
def minor?
# Check if they are under 19
self.age < 19
end
## Right; explain the odd logic so others know it is intentional, with
## a ref for more info as to why
def minor?
# For our purposes, an 18-year-old is still a minor. See
# ticket XYZ for a more detailed discussion
self.age < 19
end
Why? Because a README is a nice way to explain what your code is/does
Why? Summarizing things in one line is helpful
Why? Because not everyone knows what needs to be done, even if it's just gem install
Why? This, along with the description allows someone to understand your library/app in under a minute
Why? When rendered as RDoc, these classes link to where the user should start reading to get a deeper perspective
Why? If you want contributions, developers need to know how to work with your code
Why? There's no other way to tell what the types are and it's just not nice to hide this info
Why? Because it's jerky not to; there's no other way to know what they are
# Makes a request
#
# url:: url to request
# options:: options to control request:
# +:method+:: HTTP method to use as a String (default "GET")
# +:content_type+:: Mime type to include as a header, as a String (default "text/plain")
# +:if_modified_since+:: Date for if-modified-since header, default nil
def request(url,options={})
end
Why? Good method names are preferred, but if it's somewhat complex, add a bit more about what the method does
Why? Again, the parameter names should be meaningful, but if they don't fully explain things, throws us a bone
Why? These values show up in rdoc, so restating them is just a maintenance issue
attr_
methodWhy? No other way to know what the types are
Why? Naming is hard; documentation helps explain what a class is for
Why? Because we don't have types, the user of your module needs to know what methods to implement to make the module work
Why? This lets someone see, at a glance, what the construct is for
Why? We know what kind of thing it is; just state what it does
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
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
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
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
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.
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
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.
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
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
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.
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.
if
statements; controllers should be as dumb as possibleWhy? 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.
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 truthWhy? 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]
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
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.
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
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
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.
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.