README.md

Abstract

The goal of this guide is to present a set of best practices and style
prescriptions for Ruby on Rails 3 development. It's a complementary
guide to the already existing community-driven
Ruby coding style guide.

While in the guide the section Testing Rails applications is after
Developing Rails applications I truly believe that
Behaviour-Driven Development (BDD) is the best way to develop
software. Keep that in mind.

Rails is an opinionated framework and this is an opinionated guide. In
my mind I'm totally certain that
RSpec is superior to Test::Unit,
Sass is superior to CSS and
Haml (Slim) is
superior to Erb. So don't expect to find any Test::Unit, CSS or Erb
advice in here.

When a named scope, defined with a lambda and parameters, becomes too
complicated it is preferable to make a class method instead which serves
the same purpose of the named scope and returns and
ActiveRecord::Relation object.

Beware of the behavior of the update_attribute method. It doesn't
run the model validations (unlike update_attributes) and could easily corrupt the model state.

Migrations

Keep the schema.rb under version control.

Use rake db:schema:load instead of rake db:migrate to initialize
an empty database.

Avoid setting defaults in the tables themselves. Use the model layer
instead.

defamount
read_attributed(:amount) or0end

When writing constructive migrations (adding tables or columns), use
the new Rails 3.1 way of doing the migrations - use the change
method instead of up and down methods.

Bundler

Put gems used only for development or testing in the appropriate group in the Gemfile.

Use only established gems in your projects. If you're contemplating
on including some little-known gem you should do a careful review of
its source code first.

OS-specific gems will by default result in a constantly changing Gemfile.lock
for projects with multiple developers using different operating systems.
Add all OS X specific gems to a darwin group in the Gemfile, and all Linux
specific gems to a linux group:

# Gemfile
group :darwindogem'rb-fsevent'gem'growl'end
group :linuxdogem'rb-inotify'end

To require the appropriate gems in the right environment, add the
following to config/application.rb:

Managing processes

Testing Rails applications

The best approach to implementing new features is probably the BDD
approach. You start out by writing some high level feature tests
(generally written using Cucumber), then you use these tests to drive
out the implementation of the feature. First you write view specs for
the feature and use those specs to create the relevant
views. Afterwards you create the specs for the controller(s) that will
be feeding data to the views and use those specs to implement the
controller. Finally you implement the models specs and the models
themselves.

The steps for the scenarios are in .rb files under the
step_definitions directory. The naming convention for the steps file
is [description]_steps.rb. The steps can be separated into
different files based on different criterias. It is possible to have
one steps file for each feature (home_page_steps.rb). There also
can be one steps file for all features for a particular object
(articles_steps.rb).

Use multiline step arguments to avoid repetition

Scenario:User profile
GivenI am logged in as a user with e-mail "user@test.com"WhenI go to the profile edit page
ThenI should see the following information:|First name|John||Last name |Doe||E-mail |user@test.com|# the step:Then/^I should see the following information:$/do |table|
table.raw.each do |field, value|
field = find_field(field)
field.value.should =~/#{value}/endend

Use compound steps to keep the scenario DRY

# ...WhenI register as a user with email "user@test.com"and password "secret"# ...# the step:When/^I register as a user with email "([^"]*)" and password "([^"]*)"$/do |email, password|
steps %Q{ When I go to the user registration page And I fill in "E-mail" with #{email} And I fill in "Password" with #{password} And I fill in "Password Confirmation" with #{password} And I click "Register"}end

The helpers specs are separated from the view specs in the spec/helpers directory.

Controllers

Mock the models and stub their methods. Testing the controller should not depend on the model creation.

Test only the behaviour the controller should be responsible about:

Execution of particular methods

Data returned from the action - assigns, etc.

Result from the action - template render, redirect, etc.

# Example of a commonly used controller spec# spec/controllers/articles_controller_spec.rb# We are interested only in the actions the controller should perform# So we are mocking the model creation and stubbing its methods# And we concentrate only on the things the controller should do
describe ArticlesControllerdo# The model will be used in the specs for all methods of the controller
let(:article) { mock_model(Article) }
describe 'POST create'do
before { Article.stub(:new).and_return(article) }
it 'creates a new article with the given attributes'doArticle.should_receive(:new).with(title:'The New Article Title').and_return(article)
post :create, message: { title:'The New Article Title' }
end
it 'saves the article'do
article.should_receive(:save)
post :createend
it 'redirects to the Articles index'do
article.stub(:save)
post :create
response.should redirect_to(action:'index')
endendend

Use context when the controller action has different behaviour depending on the received params.

# A classic example for use of contexts in a controller spec is creation or update when the object saves successfully or not.
describe ArticlesControllerdo
let(:article) { mock_model(Article) }
describe 'POST create'do
before { Article.stub(:new).and_return(article) }
it 'creates a new article with the given attributes'doArticle.should_receive(:new).with(title:'The New Article Title').and_return(article)
post :create, article: { title:'The New Article Title' }
end
it 'saves the article'do
article.should_receive(:save)
post :createend
context 'when the article saves successfully'do
before { article.stub(:save).and_return(true) }
it 'sets a flash[:notice] message'do
post :create
flash[:notice].should eq('The article was saved successfully.')
end
it 'redirects to the Articles index'do
post :create
response.should redirect_to(action:'index')
endend
context 'when the article fails to save'do
before { article.stub(:save).and_return(false) }
it 'assigns @article'do
post :create
assigns[:article].should be_eql(article)
end
it 're-renders the "new" template'do
post :create
response.should render_template('new')
endendendend

Uploaders

# rspec/uploaders/person_avatar_uploader_spec.rbrequire'spec_helper'require'carrierwave/test/matchers'
describe PersonAvatarUploaderdoincludeCarrierWave::Test::Matchers# Enable images processing before executing the examples
before(:all) doUserAvatarUploader.enable_processing =trueend# Create a new uploader. The model is mocked as the uploading and resizing images does not depend on the model creation.
before(:each) do@uploader=PersonAvatarUploader.new(mock_model(Person).as_null_object)
@uploader.store!(File.open(path_to_file))
end# Disable images processing after executing the examples
after(:all) doUserAvatarUploader.enable_processing =falseend# Testing whether image is no larger than given dimensions
context 'the default version'do
it 'scales down an image to be no larger than 256 by 256 pixels'do@uploader.should be_no_larger_than(256, 256)
endend# Testing whether image has the exact dimensions
context 'the thumb version'do
it 'scales down an image to be exactly 64 by 64 pixels'do@uploader.thumb.should have_dimensions(64, 64)
endendend

Contributing

Nothing written in this guide is set in stone. It's my desire to work
together with everyone interested in Rails coding style, so that we could
ultimately create a resource that will be beneficial to the entire Ruby
community.

Feel free to open tickets or send pull requests with improvements. Thanks in
advance for your help!

Spread the Word

A community-driven style guide is of little use to a community that
doesn't know about its existence. Tweet about the guide, share it with
your friends and colleagues. Every comment, suggestion or opinion we
get makes the guide just a little bit better. And we want to have the
best possible guide, don't we?