rails-style-guide

by rubocop-hq

rubocop-hq /rails-style-guide

A community-driven Ruby on Rails style guide

6.0K Stars 1.0K Forks Last release: Not found 397 Commits 0 Releases

Available items

No Items, yet!

The developer of this repository has not created any items for sale yet. Need a bug fixed? Help with integration? A different license? Create a request here:

= The Rails Style Guide :idprefix: :idseparator: - :sectanchors: :sectlinks: :toc: preamble :toclevels: 1 ifndef::backend-pdf[] :toc-title: pass:[

Table of Contents

] endif::[] :source-highlighter: rouge

== Introduction

[quote]


Role models are important.

-- Officer Alex J. Murphy / RoboCop


ifdef::env-github[] TIP: You can find a beautiful version of this guide with much improved navigation at https://rails.rubystyle.guide. endif::[]

The goal of this guide is to present a set of best practices and style prescriptions for Ruby on Rails 4 development. It's a complementary guide to the already existing community-driven https://github.com/rubocop-hq/ruby-style-guide[Ruby coding style guide].

This Rails style guide recommends best practices so that real-world Rails programmers can write code that can be maintained by other real-world Rails programmers. A style guide that reflects real-world usage gets used, and a style guide that holds to an ideal that has been rejected by the people it is supposed to help risks not getting used at all - no matter how good it is.

The guide is separated into several sections of related rules. I've tried to add the rationale behind the rules (if it's omitted I've assumed it's pretty obvious).

I didn't come up with all the rules out of nowhere - they are mostly based on my extensive career as a professional software engineer, feedback and suggestions from members of the Rails community and various highly regarded Rails programming resources.

NOTE: Some of the advice here is applicable only to Rails 4.0+.

You can generate a PDF copy of this guide using https://asciidoctor.org/docs/asciidoctor-pdf/[AsciiDoctor PDF], and an HTML copy https://asciidoctor.org/docs/convert-documents/#converting-a-document-to-html[with] https://asciidoctor.org/#installation[AsciiDoctor] using the following commands:

[source,shell]

Generates README.pdf

asciidoctor-pdf -a allow-uri-read README.adoc

Generates README.html

asciidoctor

[TIP]

Install the

rouge
gem to get nice syntax highlighting in the generated document.

[source,shell]

gem install rouge

====

Translations of the guide are available in the following languages:

  • https://github.com/JuanitoFatas/rails-style-guide/blob/master/README-zhCN.md[Chinese Simplified]
  • https://github.com/JuanitoFatas/rails-style-guide/blob/master/README-zhTW.md[Chinese Traditional]
  • https://github.com/satour/rails-style-guide/blob/master/README-jaJA.md[Japanese]
  • https://github.com/arbox/rails-style-guide/blob/master/README-ruRU.md[Russian]
  • https://github.com/tolgaavci/rails-style-guide/blob/master/README-trTR.md[Turkish]
  • https://github.com/pureugong/rails-style-guide/blob/master/README-koKR.md[Korean]
  • https://github.com/CQBinh/rails-style-guide/blob/master/README-viVN.md[Vietnamese]
  • https://github.com/abraaomiranda/rails-style-guide/blob/master/README-ptBR.md[Portuguese (pt-BR)]

TIP: https://github.com/rubocop-hq/rubocop[RuboCop], a static code analyzer (linter) and formatter, has a https://github.com/rubocop-hq/rubocop-rails[

rubocop-rails
] extension, based on this style guide.

== Configuration

=== Config Initializers [[config-initializers]]

Put custom initialization code in

config/initializers
. The code in initializers executes on application startup.

=== Gem Initializers [[gem-initializers]]

Keep initialization code for each gem in a separate file with the same name as the gem, for example

carrierwave.rb
,
active_admin.rb
, etc.

=== Dev/Test/Prod Configs [[dev-test-prod-configs]]

Adjust accordingly the settings for development, test and production environment (in the corresponding files under

config/environments/
)

Mark additional assets for precompilation (if any):

[source,ruby]

config/environments/production.rb

Precompile additional assets (application.js, application.css,

and all non-JS/CSS are already added)

config.assets.precompile += %w( railsadmin/railsadmin.css railsadmin/railsadmin.js )

=== App Config [[app-config]]

Keep configuration that's applicable to all environments in the

config/application.rb
file.

=== Staging Like Prod [[staging-like-prod]]

Create an additional

staging
environment that closely resembles the
production
one.

=== YAML Config [[yaml-config]]

Keep any additional configuration in YAML files under the

config/
directory.

Since Rails 4.2 YAML configuration files can be easily loaded with the new

config_for
method:

[source,ruby]

Rails::Application.configfor(:yamlfile)

== Routing

=== Member Collection Routes [[member-collection-routes]]

When you need to add more actions to a RESTful resource (do you really need them at all?) use

member
and
collection
routes.

[source,ruby]

bad

get 'subscriptions/:id/unsubscribe' resources :subscriptions

good

resources :subscriptions do get 'unsubscribe', on: :member end

bad

get 'photos/search' resources :photos

good

resources :photos do get 'search', on: :collection

end

=== Many Member Collection Routes [[many-member-collection-routes]]

If you need to define multiple

member/collection
routes use the alternative block syntax.

[source,ruby]

resources :subscriptions do member do get 'unsubscribe' # more routes end end

resources :photos do collection do get 'search' # more routes end

end

=== Nested Routes [[nested-routes]]

Use nested routes to express better the relationship between Active Record models.

[source,ruby]

class Post < ActiveRecord::Base has_many :comments end

class Comment < ActiveRecord::Base belongs_to :post end

routes.rb

resources :posts do resources :comments

end

=== Shallow Routes [[shallow-routes]]

If you need to nest routes more than 1 level deep then use the

shallow: true
option. This will save user from long URLs
posts/1/comments/5/versions/7/edit
and you from long URL helpers
edit_post_comment_version
.

[source,ruby]

resources :posts, shallow: true do resources :comments do resources :versions end

end

=== Namespaced Routes [[namespaced-routes]]

Use namespaced routes to group related actions.

[source,ruby]

namespace :admin do # Directs /admin/products/* to Admin::ProductsController # (app/controllers/admin/products_controller.rb) resources :products

end

=== No Wild Routes [[no-wild-routes]]

Never use the legacy wild controller route. This route will make all actions in every controller accessible via GET requests.

[source,ruby]

very bad

match ':controller(/:action(/:id(.:format)))'

=== No Match Routes [[no-match-routes]]

Don't use

match
to define any routes unless there is need to map multiple request types among
[:get, :post, :patch, :put, :delete]
to a single action using
:via
option.

== Controllers

=== Skinny Controllers [[skinny-controllers]]

Keep the controllers skinny - they should only retrieve data for the view layer and shouldn't contain any business logic (all the business logic should naturally reside in the model).

=== One Method [[one-method]]

Each controller action should (ideally) invoke only one method other than an initial find or new.

=== Shared Instance Variables [[shared-instance-variables]]

Minimize the number of instance variables passed between a controller and a view.

=== Lexically Scoped Action Filter [[lexically-scoped-action-filter]]

Controller actions specified in the option of Action Filter should be in lexical scope. The ActionFilter specified for an inherited action makes it difficult to understand the scope of its impact on that action.

[source,ruby]

bad

class UsersController < ApplicationController beforeaction :requirelogin, only: :export end

good

class UsersController < ApplicationController beforeaction :requirelogin, only: :export

def export end

end

== Controllers: Rendering [[rendering]]

=== Inline Rendering [[inline-rendering]]

Prefer using a template over inline rendering.

[source,ruby]

very bad

class ProductsController < ApplicationController def index render inline: "<% products.each do |p| %>

<%= p.name %>

<% end %>", type: :erb end end

good

app/views/products/index.html.erb

<%= render partial: 'product', collection: products %>

app/views/products/_product.html.erb

app/controllers/products_controller.rb

class ProductsController < ApplicationController def index render :index end

end

=== Plain Text Rendering [[plain-text-rendering]]

Prefer

render plain:
over
render text:
.

[source,ruby]

bad - sets MIME type to
text/html

... render text: 'Ruby!' ...

bad - requires explicit MIME type declaration

... render text: 'Ruby!', content_type: 'text/plain' ...

good - short and precise

... render plain: 'Ruby!'

...

=== HTTP Status Code Symbols [[http-status-code-symbols]]

Prefer https://gist.github.com/mlanett/a31c340b132ddefa9cca[corresponding symbols] to numeric HTTP status codes. They are meaningful and do not look like "magic" numbers for less known HTTP status codes.

[source,ruby]

bad

... render status: 403 ...

good

... render status: :forbidden

...

== Models

=== Model Classes [[model-classes]]

Introduce non-Active Record model classes freely.

=== Meaningful Model Names [[meaningful-model-names]]

Name the models with meaningful (but short) names without abbreviations.

=== ActiveAttr Gem [[activeattr-gem]]

If you need model objects that support Active Record behavior (like validation) without the Active Record database functionality use the https://github.com/cgriego/active_attr[ActiveAttr] gem.

[source,ruby]

class Message include ActiveAttr::Model

attribute :name attribute :email attribute :content attribute :priority

attr_accessible :name, :email, :content

validates :name, presence: true validates :email, format: { with: /\A[-a-z0-9_+.]+\@([-a-z0-9]+.)+[a-z0-9]{2,4}\z/i } validates :content, length: { maximum: 500 }

end

For a more complete example refer to the http://railscasts.com/episodes/326-activeattr[RailsCast on the subject].

=== Model Business Logic [[model-business-logic]]

Unless they have some meaning in the business domain, don't put methods in your model that just format your data (like code generating HTML). These methods are most likely going to be called from the view layer only, so their place is in helpers. Keep your models for business logic and data-persistence only.

== Models: Active Record [[activerecord]]

=== Keep Active Record Defaults [[keep-ar-defaults]]

Avoid altering Active Record defaults (table names, primary key, etc) unless you have a very good reason (like a database that's not under your control).

[source,ruby]

bad - don't do this if you can modify the schema

class Transaction < ActiveRecord::Base self.table_name = 'order' ...

end

=== Enums [[enums]]

Prefer using the hash syntax for

enum
. Array makes the database values implicit & any insertion/removal/rearrangement of values in the middle will most probably lead to broken code.

[source,ruby]

class Transaction < ActiveRecord::Base # bad - implicit values - ordering matters enum type: %i[credit debit]

# good - explicit values - ordering does not matter enum type: { credit: 0, debit: 1 }

end

=== Macro Style Methods [[macro-style-methods]]

Group macro-style methods (

has_many
,
validates
, etc) in the beginning of the class definition.

[source,ruby]

class User < ActiveRecord::Base # keep the default scope first (if any) default_scope { where(active: true) }

# constants come up next COLORS = %w(red green blue)

# afterwards we put attr related macros attraccessor :formatteddateofbirth

attraccessible :login, :firstname, :last_name, :email, :password

# Rails 4+ enums after attr macros enum role: { user: 0, moderator: 1, admin: 2 }

# followed by association macros belongs_to :country

has_many :authentications, dependent: :destroy

# and validation macros validates :email, presence: true validates :username, presence: true validates :username, uniqueness: { casesensitive: false } validates :username, format: { with: /\A[A-Za-z][A-Za-z0-9._-]{2,19}\z/ } validates :password, format: { with: /\A\S{8,128}\z/, allownil: true }

# next we have callbacks beforesave :cook beforesave :updateusernamelower

# other macros (like devise's) should be placed after the callbacks

...

end

===

has_many :through
[[has-many-through]]

Prefer

has_many :through
to
has_and_belongs_to_many
. Using
has_many :through
allows additional attributes and validations on the join model.

[source,ruby]

not so good - using hasandbelongstomany

class User < ActiveRecord::Base hasandbelongstomany :groups end

class Group < ActiveRecord::Base hasandbelongstomany :users end

preferred way - using has_many :through

class User < ActiveRecord::Base hasmany :memberships hasmany :groups, through: :memberships end

class Membership < ActiveRecord::Base belongsto :user belongsto :group end

class Group < ActiveRecord::Base hasmany :memberships hasmany :users, through: :memberships

end

=== Read Attribute [[read-attribute]]

Prefer

self[:attribute]
over
read_attribute(:attribute)
.

[source,ruby]

bad

def amount read_attribute(:amount) * 100 end

good

def amount self[:amount] * 100

end

=== Write Attribute [[write-attribute]]

Prefer

self[:attribute] = value
over
write_attribute(:attribute, value)
.

[source,ruby]

bad

def amount write_attribute(:amount, 100) end

good

def amount self[:amount] = 100

end

=== New-style Validations [[new-style-validations]]

Always use the http://thelucid.com/2010/01/08/sexy-validation-in-edge-rails-rails-3/["new-style" validations].

[source,ruby]

bad

validatespresenceof :email validateslengthof :email, maximum: 100

good

validates :email, presence: true, length: { maximum: 100 }

=== Single-attribute Validations [[single-attribute-validations]]

To make validations easy to read, don't list multiple attributes per validation.

[source,ruby]

bad

validates :email, :password, presence: true validates :email, length: { maximum: 100 }

good

validates :email, presence: true, length: { maximum: 100 }

validates :password, presence: true

=== Custom Validator File [[custom-validator-file]]

When a custom validation is used more than once or the validation is some regular expression mapping, create a custom validator file.

[source,ruby]

bad

class Person validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+.)+[a-z]{2,})\z/i } end

good

class EmailValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) record.errors[attribute] << (options[:message] || 'is not a valid email') unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+.)+[a-z]{2,})\z/i end end

class Person validates :email, email: true

end

=== App Validators [[app-validators]]

Keep custom validators under

app/validators
.

=== Custom Validators Gem [[custom-validators-gem]]

Consider extracting custom validators to a shared gem if you're maintaining several related apps or the validators are generic enough.

=== Named Scopes [[named-scopes]]

Use named scopes freely.

[source,ruby]

class User < ActiveRecord::Base scope :active, -> { where(active: true) } scope :inactive, -> { where(active: false) }

scope :with_orders, -> { joins(:orders).select('distinct(users.id)') }

end

=== Named Scope Class [[named-scope-class]]

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 an

ActiveRecord::Relation
object. Arguably you can define even simpler scopes like this.

[source,ruby]

class User < ActiveRecord::Base def self.with_orders joins(:orders).select('distinct(users.id)') end

end

=== Callbacks Order [[callbacks-order]]

Order callback declarations in the order in which they will be executed. For reference, see https://guides.rubyonrails.org/activerecordcallbacks.html#available-callbacks[Available Callbacks].

[source,Ruby]

bad

class Person aftercommit :aftercommitcallback beforevalidation :beforevalidationcallback end

good

class Person beforevalidation :beforevalidationcallback aftercommit :aftercommitcallback

end

=== Beware Skip Model Validations [[beware-skip-model-validations]]

Beware of the behavior of the https://guides.rubyonrails.org/activerecordvalidations.html#skipping-validations[following] methods. They do not run the model validations and could easily corrupt the model state.

[source,ruby]

bad

Article.first.decrement!(:viewcount) DiscussionBoard.decrementcounter(:postcount, 5) Article.first.increment!(:viewcount) DiscussionBoard.incrementcounter(:postcount, 5) person.toggle :active product.touch Billing.updateall("category = 'authorized', author = 'David'") user.updateattribute(:website, 'example.com') user.updatecolumns(lastrequestat: Time.current) Post.updatecounters 5, commentcount: -1, actioncount: 1

good

user.update_attributes(website: 'example.com')

=== User-friendly URLs [[user-friendly-urls]]

Use user-friendly URLs. Show some descriptive attribute of the model in the URL rather than its

id
. There is more than one way to achieve this.

==== Override the

to_param
Method of the Model

This method is used by Rails for constructing a URL to the object. The default implementation returns the

id
of the record as a String. It could be overridden to include another human-readable attribute.

[source,ruby]

class Person def to_param "#{id} #{name}".parameterize end

end

In order to convert this to a URL-friendly value,

parameterize
should be called on the string. The
id
of the object needs to be at the beginning so that it can be found by the
find
method of Active Record.

====

friendly_id
Gem

It allows creation of human-readable URLs by using some descriptive attribute of the model instead of its

id
.

[source,ruby]

class Person extend FriendlyId friendly_id :name, use: :slugged

end

Check the https://github.com/norman/friendly_id[gem documentation] for more information about its usage.

===

find_each
[[find-each]]

Use

find_each
to iterate over a collection of AR objects. Looping through a collection of records from the database (using the
all
method, for example) is very inefficient since it will try to instantiate all the objects at once. In that case, batch processing methods allow you to work with the records in batches, thereby greatly reducing memory consumption.

[source,ruby]

bad

Person.all.each do |person| person.doawesomestuff end

Person.where('age > 21').each do |person| person.partyallnight! end

good

Person.findeach do |person| person.doawesome_stuff end

Person.where('age > 21').findeach do |person| person.partyall_night!

end

===

before_destroy
[[before_destroy]]

Since https://github.com/rails/rails/issues/3458[Rails creates callbacks for dependent associations], always call

before_destroy
callbacks that perform validation with
prepend: true
.

[source,ruby]

bad (roles will be deleted automatically even if super_admin? is true)

has_many :roles, dependent: :destroy

beforedestroy :ensuredeletable

def ensuredeletable raise "Cannot delete super admin." if superadmin? end

good

has_many :roles, dependent: :destroy

beforedestroy :ensuredeletable, prepend: true

def ensuredeletable raise "Cannot delete super admin." if superadmin?

end

===

has_many
/
has_one
Dependent Option [[hasmany-hasone-dependent-option]]

Define the

dependent
option to the
has_many
and
has_one
associations.

[source,ruby]

bad

class Post < ActiveRecord::Base has_many :comments end

good

class Post < ActiveRecord::Base has_many :comments, dependent: :destroy

end

===

save!
[[save-bang]]

When persisting AR objects always use the exception raising bang! method or handle the method return value. This applies to

create
,
save
,
update
,
destroy
,
first_or_create
and
find_or_create_by
.

[source,ruby]

bad

user.create(name: 'Bruce')

bad

user.save

good

user.create!(name: 'Bruce')

or

bruce = user.create(name: 'Bruce') if bruce.persisted? ... else ... end

good

user.save!

or

if user.save ... else ...

end

== Models: Active Record Queries [[activerecord-queries]]

=== Avoid Interpolation [[avoid-interpolation]]

Avoid string interpolation in queries, as it will make your code susceptible to SQL injection attacks.

[source,ruby]

bad - param will be interpolated unescaped

Client.where("orders_count = #{params[:orders]}")

good - param will be properly escaped

Client.where('orders_count = ?', params[:orders])

=== Named Placeholder [[named-placeholder]]

Consider using named placeholders instead of positional placeholders when you have more than 1 placeholder in your query.

[source,ruby]

okish

Client.where( 'createdat >= ? AND createdat <= ?', params[:startdate], params[:enddate] )

good

Client.where( 'createdat >= :startdate AND createdat <= :enddate', startdate: params[:startdate], enddate: params[:enddate]

)

===

find
[[find]]

Favor the use of

find
over
where.take!
,
find_by!
, and
find_by_id!
when you need to retrieve a single record by primary key id and raise
ActiveRecord::RecordNotFound
when the record is not found.

[source,ruby]

bad

User.where(id: id).take!

bad

User.findbyid!(id)

bad

User.find_by!(id: id)

good

User.find(id)

===

find_by
[[find_by]]

Favor the use of

find_by
over
where.take
and
find_by_attribute
when you need to retrieve a single record by one or more attributes and return
nil
when the record is not found.

[source,ruby]

bad

User.where(email: email).take User.where(firstname: 'Bruce', lastname: 'Wayne').take

bad

User.findbyemail(email) User.findbyfirstnameandlastname('Bruce', 'Wayne')

good

User.find_by(email: email)

User.findby(firstname: 'Bruce', last_name: 'Wayne')

=== Hash conditions [[where-not]] [[hash-conditions]]

Favor passing conditions to

where
and
where.not
as a hash over using fragments of SQL.

[source,ruby]

bad

User.where("name = ?", name)

good

User.where(name: name)

bad

User.where("id != ?", id)

good

User.where.not(id: id)

=== Order by

id
[[order-by-id]]

Don't use the

id
column for ordering. The sequence of ids is not guaranteed to be in any particular order, despite often (incidentally) being chronological. Use a timestamp column to order chronologically. As a bonus the intent is clearer.

[source,ruby]

bad

scope :chronological, -> { order(id: :asc) }

good

scope :chronological, -> { order(created_at: :asc) }

===

pluck

Use https://api.rubyonrails.org/classes/ActiveRecord/Calculations.html#method-i-pluck[pluck] to select a single value from multiple records.

[source,ruby]

bad

User.all.map(&:name)

bad

User.all.map { |user| user[:name] }

good

User.pluck(:name)

===

pick

Use https://api.rubyonrails.org/classes/ActiveRecord/Calculations.html#method-i-pick[pick] to select a single value from a single record.

[source,ruby]

bad

User.pluck(:name).first

bad

User.first.name

good

User.pick(:name)

===

ids
[[ids]]

Favor the use of

ids
over
pluck(:id)
.

[source,Ruby]

bad

User.pluck(:id)

good

User.ids

=== Squished Heredocs [[squished-heredocs]]

When specifying an explicit query in a method such as

find_by_sql
, use heredocs with
squish
. This allows you to legibly format the SQL with line breaks and indentations, while supporting syntax highlighting in many tools (including GitHub, Atom, and RubyMine).

[source,ruby]

User.findbysql(<<-SQL.squish) SELECT users.id, accounts.plan FROM users INNER JOIN accounts ON accounts.user_id = users.id # further complexities...

SQL

https://api.rubyonrails.org/classes/String.html#method-i-squish[

String#squish
] removes the indentation and newline characters so that your server log shows a fluid string of SQL rather than something like this:

SELECT\n users.id, accounts.plan\n FROM\n users\n INNER JOIN\n acounts\n ON\n accounts.user_id = users.id

===

size
over
count
or
length
[[size-over-count-or-length]]

When querying Active Record collections, prefer

size
(selects between count/length behavior based on whether collection is already loaded) or
length
(always loads the whole collection and counts the array elements) over
count
(always does a database query for the count).

[source,ruby]

bad

User.count

good

User.all.size

good - if you really need to load all users into memory

User.all.length

== Migrations

=== Schema Version [[schema-version]]

Keep the

schema.rb
(or
structure.sql
) under version control.

=== DB Schema Load [[db-schema-load]]

Use

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

=== Default Migration Values [[default-migration-values]]

Enforce default values in the migrations themselves instead of in the application layer.

[source,ruby]

bad - application enforced default value

class Product < ActiveRecord::Base def amount self[:amount] || 0 end end

good - database enforced

class AddDefaultAmountToProducts < ActiveRecord::Migration def change changecolumndefault :products, :amount, 0 end

end

While enforcing table defaults only in Rails is suggested by many Rails developers, it's an extremely brittle approach that leaves your data vulnerable to many application bugs. And you'll have to consider the fact that most non-trivial apps share a database with other applications, so imposing data integrity from the Rails app is impossible.

=== Foreign Key Constraints [[foreign-key-constraints]]

Enforce foreign-key constraints. As of Rails 4.2, Active Record supports foreign key constraints natively.

=== Change vs Up/Down [[change-vs-up-down]]

When writing constructive migrations (adding tables or columns), use the

change
method instead of
up
and
down
methods.

[source,ruby]

the old way

class AddNameToPeople < ActiveRecord::Migration def up add_column :people, :name, :string end

def down remove_column :people, :name end end

the new preferred way

class AddNameToPeople < ActiveRecord::Migration def change add_column :people, :name, :string end

end

=== Define Model Class Migrations [[define-model-class-migrations]]

If you have to use models in migrations, make sure you define them so that you don't end up with broken migrations in the future.

[source,ruby]

db/migrate/.rb

frozenstringliteral: true

bad

class ModifyDefaultStatusForProducts < ActiveRecord::Migration def change oldstatus = 'pendingmanualapproval' newstatus = 'pending_approval'

reversible do |dir|
  dir.up do
    Product.where(status: old_status).update_all(status: new_status)
    change_column :products, :status, :string, default: new_status
  end

dir.down do Product.where(status: new_status).update_all(status: old_status) change_column :products, :status, :string, default: old_status end end

end end

good

Define
table_name
in a custom named class to make sure that you run on the

same table you had during the creation of the migration.

In future if you override the
Product
class and change the
table_name
,

it won't break the migration or cause serious data corruption.

class MigrationProduct < ActiveRecord::Base self.table_name = :products end

class ModifyDefaultStatusForProducts < ActiveRecord::Migration def change oldstatus = 'pendingmanualapproval' newstatus = 'pending_approval'

reversible do |dir|
  dir.up do
    MigrationProduct.where(status: old_status).update_all(status: new_status)
    change_column :products, :status, :string, default: new_status
  end

dir.down do MigrationProduct.where(status: new_status).update_all(status: old_status) change_column :products, :status, :string, default: old_status end end

end

end

=== Meaningful Foreign Key Naming [[meaningful-foreign-key-naming]]

Name your foreign keys explicitly instead of relying on Rails auto-generated FK names. (https://guides.rubyonrails.org/activerecordmigrations.html#foreign-keys)

[source,ruby]

bad

class AddFkArticlesToAuthors < ActiveRecord::Migration def change addforeignkey :articles, :authors end end

good

class AddFkArticlesToAuthors < ActiveRecord::Migration def change addforeignkey :articles, :authors, name: :articlesauthorid_fk end

end

=== Reversible Migration [[reversible-migration]]

Don't use non-reversible migration commands in the

change
method. Reversible migration commands are listed below. https://api.rubyonrails.org/classes/ActiveRecord/Migration/CommandRecorder.html[ActiveRecord::Migration::CommandRecorder]

[source,ruby]

bad

class DropUsers < ActiveRecord::Migration def change drop_table :users end end

good

class DropUsers < ActiveRecord::Migration def up drop_table :users end

def down create_table :users do |t| t.string :name end end end

good

In this case, block will be used by create_table in rollback

https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters.html#method-i-drop_table

class DropUsers < ActiveRecord::Migration def change drop_table :users do |t| t.string :name end end

end

== Views

=== No Direct Model View [[no-direct-model-view]]

Never call the model layer directly from a view.

=== No Complex View Formatting [[no-complex-view-formatting]]

Avoid complex formatting in the views. A view helper is useful for simple cases, but if it's more complex then consider using a decorator or presenter.

=== Partials [[partials]]

Mitigate code duplication by using partial templates and layouts.

=== No Instance Variables in Partials [[no-instance-variables-in-partials]]

Avoid using instance variables in partials, pass a local variable to

render
instead. The partial may be used in a different controller or action, where the variable can have a different name or even be absent. In these cases, an undefined instance variable will not raise an exception whereas a local variable will.

[source,erb]

<%= render 'coursedescription' %> <!-- app/views/courses/course_description.html.erb --> <%= @course.description %>

<%= render 'coursedescription', course: @course %> <!-- app/views/courses/course_description.html.erb -->

<%= course.description %>

== Internationalization

=== Locale Texts [[locale-texts]]

No strings or other locale specific settings should be used in the views, models and controllers. These texts should be moved to the locale files in the

config/locales
directory.

=== Translated Labels [[translated-labels]]

When the labels of an Active Record model need to be translated, use the

activerecord
scope:

en: activerecord: models: user: Member attributes: user:

name: 'Full name'

Then

User.model_name.human
will return "Member" and
User.human_attribute_name("name")
will return "Full name". These translations of the attributes will be used as labels in the views.

=== Organize Locale Files [[organize-locale-files]]

Separate the texts used in the views from translations of Active Record attributes. Place the locale files for the models in a folder

locales/models
and the texts used in the views in folder
locales/views
.

When organization of the locale files is done with additional directories, these directories must be described in the

application.rb
file in order to be loaded.

[source,ruby]

config/application.rb

config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '*', '.{rb,yml}')]

=== Shared Localization [[shared-localization]]

Place the shared localization options, such as date or currency formats, in files under the root of the

locales
directory.

=== Short I18n [[short-i18n]]

Use the short form of the I18n methods:

I18n.t
instead of
I18n.translate
and
I18n.l
instead of
I18n.localize
.

=== Lazy Lookup [[lazy-lookup]]

Use "lazy" lookup for the texts used in views. Let's say we have the following structure:


en: users: show:

title: 'User details page'

The value for

users.show.title
can be looked up in the template
app/views/users/show.html.haml
like this:

[source,ruby]

= t '.title'

=== Dot-separated Keys [[dot-separated-keys]]

Use the dot-separated keys in the controllers and models instead of specifying the

:scope
option. The dot-separated call is easier to read and trace the hierarchy.

[source,ruby]

bad

I18n.t :record_invalid, scope: [:activerecord, :errors, :messages]

good

I18n.t 'activerecord.errors.messages.record_invalid'

=== I18n Guides [[i18n-guides]]

More detailed information about the Rails I18n can be found in the https://guides.rubyonrails.org/i18n.html[Rails Guides]

== Assets

Use the https://guides.rubyonrails.org/asset_pipeline.html[asset pipeline] to leverage organization within your application.

=== Reserve

app/assets
[[reserve-app-assets]]

Reserve

app/assets
for custom stylesheets, javascripts, or images.

===

lib/assets
[[lib-assets]]

Use

lib/assets
for your own libraries that don't really fit into the scope of the application.

===

vendor/assets
[[vendor-assets]]

Third party code such as https://jquery.com/[jQuery] or https://twitter.github.com/bootstrap/[bootstrap] should be placed in

vendor/assets
.

===

gem/assets
[[gem-assets]]

When possible, use gemified versions of assets (e.g. https://github.com/rails/jquery-rails[jquery-rails], https://github.com/joliss/jquery-ui-rails[jquery-ui-rails], https://github.com/thomas-mcdonald/bootstrap-sass[bootstrap-sass], https://github.com/zurb/foundation[zurb-foundation]).

== Mailers

=== Mailer Name [[mailer-name]]

Name the mailers

SomethingMailer
. Without the Mailer suffix it isn't immediately apparent what's a mailer and which views are related to the mailer.

=== HTML Plain Email [[html-plain-email]]

Provide both HTML and plain-text view templates.

=== Enable Delivery Errors [[enable-delivery-errors]]

Enable errors raised on failed mail delivery in your development environment. The errors are disabled by default.

[source,ruby]

config/environments/development.rb

config.actionmailer.raisedelivery_errors = true

=== Local SMTP [[local-smtp]]

Use a local SMTP server like https://github.com/sj26/mailcatcher[Mailcatcher] in development environment.

[source,ruby]

config/environments/development.rb

config.actionmailer.smtpsettings = { address: 'localhost', port: 1025, # more settings

}

=== Default Hostname [[default-hostname]]

Provide default settings for the host name.

[source,ruby]

config/environments/development.rb

config.actionmailer.defaulturloptions = { host: "#{localip}:3000" }

config/environments/production.rb

config.actionmailer.defaulturloptions = { host: 'yoursite.com' }

in your mailer class

defaulturloptions[:host] = 'your_site.com'

=== Email Addresses [[email-addresses]]

Format the from and to addresses properly. Use the following format:

[source,ruby]

in your mailer class

default from: 'Your Name [email protected]_site.com'

=== Delivery Method Test [[delivery-method-test]]

Make sure that the e-mail delivery method for your test environment is set to

test
:

[source,ruby]

config/environments/test.rb

config.actionmailer.deliverymethod = :test

=== Delivery Method SMTP [[delivery-method-smtp]]

The delivery method for development and production should be

smtp
:

[source,ruby]

config/environments/development.rb, config/environments/production.rb

config.actionmailer.deliverymethod = :smtp

=== Inline Email Styles [[inline-email-styles]]

When sending html emails all styles should be inline, as some mail clients have problems with external styles. This however makes them harder to maintain and leads to code duplication. There are two similar gems that transform the styles and put them in the corresponding html tags: https://github.com/fphilipe/premailer-rails[premailer-rails] and https://github.com/Mange/roadie[roadie].

=== Background Email [[background-email]]

Sending emails while generating page response should be avoided. It causes delays in loading of the page and request can timeout if multiple email are sent. To overcome this emails can be sent in background process with the help of https://github.com/mperham/sidekiq[sidekiq] gem.

== Active Support Core Extensions

===

try!
[[try-bang]]

Prefer Ruby 2.3's safe navigation operator

&.
over
ActiveSupport#try!
.

[source,ruby]

bad

obj.try! :fly

good

obj&.fly

=== Active Support Aliases [[activesupportaliases]]

Prefer Ruby's Standard Library methods over

ActiveSupport
aliases.

[source,ruby]

bad

'the day'.startswith? 'th' 'the day'.endswith? 'ay'

good

'the day'.start_with? 'th'

'the day'.end_with? 'ay'

=== Active Support Extensions [[activesupportextensions]]

Prefer Ruby's Standard Library over uncommon Active Support extensions.

[source,ruby]

bad

(1..50).toa.fortytwo 1.in? [1, 2] 'day'.in? 'the day'

good

(1..50).to_a[41] [1, 2].include? 1

'the day'.include? 'day'

===

inquiry
[[inquiry]]

Prefer Ruby's comparison operators over Active Support's

Array#inquiry
, and
String#inquiry
.

[source,ruby]

bad - String#inquiry

ruby = 'two'.inquiry ruby.two?

good

ruby = 'two' ruby == 'two'

bad - Array#inquiry

pets = %w(cat dog).inquiry pets.gopher?

good

pets = %w(cat dog)

pets.include? 'cat'

===

exclude?
[[exclude]]

Prefer Active Support's

exclude?
over Ruby's negated
include?
.

[source,ruby]

bad

!array.include?(2) !hash.include?(:key) !string.include?('substring')

good

array.exclude?(2) hash.exclude?(:key)

string.exclude?('substring')

== Time

=== Time Zone Config [[tz-config]]

Configure your timezone accordingly in

application.rb
.

[source,ruby]

config.time_zone = 'Eastern European Time'

optional - note it can be only :utc or :local (default is :utc)

config.activerecord.defaulttimezone = :local

===

Time.parse
[[time-parse]]

Don't use

Time.parse
.

[source,ruby]

bad

Time.parse('2015-03-02 19:05:37') # => Will assume time string given is in the system's time zone.

good

Time.zone.parse('2015-03-02 19:05:37') # => Mon, 02 Mar 2015 19:05:37 EET +02:00

===

to_time
[[to-time]]

Don't use https://api.rubyonrails.org/classes/String.html#method-i-totime[`String#totime`]

[source,ruby]

bad - assumes time string given is in the system's time zone.

'2015-03-02 19:05:37'.to_time

good

Time.zone.parse('2015-03-02 19:05:37') # => Mon, 02 Mar 2015 19:05:37 EET +02:00

===

Time.now
[[time-now]]

Don't use

Time.now
.

[source,ruby]

bad

Time.now # => Returns system time and ignores your configured time zone.

good

Time.zone.now # => Fri, 12 Mar 2014 22:04:47 EET +02:00

Time.current # Same thing but shorter.

== Duration

If used without a parameter, prefer

from_now
and
ago
instead of
since
,
after
,
until
or
before
.

[source,ruby]

bad - It's not clear that the qualifier refers to the current time (which is the default parameter)

5.hours.since 5.hours.after 5.hours.before 5.hours.until

good

5.hours.from_now

5.hours.ago

If used with a parameter, prefer

since
,
after
,
until
or
before
instead of
from_now
and
ago
.

[source,ruby]

bad - It's confusing and misleading to read

2.days.from_now(yesterday) 2.days.ago(yesterday)

good

2.days.since(yesterday) 2.days.after(yesterday) 2.days.before(yesterday)

2.days.until(yesterday)

Avoid using negative numbers for the duration subject. Always prefer using a qualifier that allows using positive literal numbers.

[source,ruby]

bad - It's confusing and misleading to read

-5.hours.from_now -5.hours.ago

good

5.hours.ago

5.hours.from_now

== Bundler

=== Dev/Test Gems [[dev-test-gems]]

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

=== Only Good Gems [[only-good-gems]]

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

Gemfile.lock
[[os-specific-gemfile-locks]]

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:

[source,ruby]

Gemfile

group :darwin do gem 'rb-fsevent' gem 'growl' end

group :linux do gem 'rb-inotify'

end

To require the appropriate gems in the right environment, add the following to

config/application.rb
:

[source,ruby]

platform = RUBYPLATFORM.match(/(linux|darwin)/)[0].tosym

Bundler.require(platform)

===

Gemfile.lock
[[gemfile-lock]]

Do not remove the

Gemfile.lock
from version control. This is not some randomly generated file - it makes sure that all of your team members get the same gem versions when they do a
bundle install
.

== Managing Processes

=== Foreman [[foreman]]

If your projects depends on various external processes use https://github.com/ddollar/foreman[foreman] to manage them.

== Further Reading

There are a few excellent resources on Rails style, that you should consider if you have time to spare:

  • https://www.informit.com/store/rails-5-way-9780134657677[The Rails 5 Way]
  • https://guides.rubyonrails.org/[Ruby on Rails Guides]
  • https://pragprog.com/book/rspec3/effective-testing-with-rspec-3[Effective Testing with RSpec 3]
  • https://pragprog.com/book/hwcuc/the-cucumber-book[The Cucumber Book]
  • https://leanpub.com/everydayrailsrspec[Everyday Rails Testing with RSpec]
  • https://pragprog.com/book/nrtest3/rails-5-test-prescriptions[Rails 5 Test Prescriptions]
  • https://rspec.rubystyle.guide[RSpec Style Guide]

== 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!

You can also support the project (and RuboCop) with financial contributions via https://www.patreon.com/bbatsov[Patreon].

=== How to Contribute?

It's easy, just follow the contribution guidelines below:

  • https://help.github.com/articles/fork-a-repo[Fork] the project on GitHub
  • Make your feature addition or bug fix in a feature branch.
  • Include a http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html[good description] of your changes
  • Push your feature branch to GitHub
  • Send a https://help.github.com/articles/using-pull-requests[Pull Request]

== License

image:https://i.creativecommons.org/l/by/3.0/88x31.png[Creative Commons License] This work is licensed under a https://creativecommons.org/licenses/by/3.0/deed.en_US[Creative Commons Attribution 3.0 Unported License]

== 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?

Cheers, + https://twitter.com/bbatsov[Bozhidar]

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.