New Project setup files like database.yml , .gitgnore and etc files example just copy and paste while creating new rails 3 project.
A .gitignore file to accommodate Rails 3 projects.
Some files in a Rails project should not be checked into a Git version control repository. You can tell git to ignore certain files by creating a file called .gitignore in the top level of your working directory.
Rails 3 automatically creates a simple .gitignore file when a new application is generated. Here you'll find an example of a .gitignore file that designates additional files that may be part of a typical project.
Copy the example file gitignore.example to the top level of your working directory and rename it .gitignore.
Note that git will not ignore a file that was already tracked before a rule was added to this file to ignore it. In such a case the file must be un-tracked, usually with git rm --cached filename
.
The goal of this guide is to present a set of best practices and style prescriptions for Ruby on Rails 3 development. Rails is an opinionated framework and this is an opinionated guide. In my mind I'm totally certain that Sass is superior to CSS and Haml (Slim) is superior to Erb.
Some of the advice here is applicable only to Rails 3.1+.
You can generate a PDF or an HTML copy of this guide using Transmuter.
- Developing Rails applications
- Query Optimization
- Splitting Routes Into Smaller
- Directly Access Object
- Grep an object’s available methods
- Check Objects Relations Exist
- The Beauty of Collect
- Optimize your code comments
- NoMethodError
- Configuration
- Routing
- Controllers
- Models
- Migrations
- Views
- Assets
- Mailers
- Bundler
- Priceless Gems
- Flawed Gems
- Managing processes
- Active record query optimization in models
-
Use select with has_many and belongs_to on Active Record Associations
-
Using the select parameter in Active Record association, you can speed up your application about 50% and more. ```Ruby class Amenity < ActiveRecord::Base belongs_to :property, :select => 'id,name' end
class Property < ActiveRecord::Base has_many :amenities, :select => 'id,name' end ```
-
-
Split route namespaces into different files
ActionController::Routing::Routes.draw do |map| map.resources :posts map.resources :comments map.logout '/logout', :controller => 'sessions', :action => 'destroy' map.login '/login', :controller => 'sessions', :action => 'create' end
ActionController::Routing::Routes.draw do |map| map.namespace :developer do |dev| dev.resources :posts dev.resources :comments dev.logout '/logout', :controller => 'sessions', :action => 'destroy' dev.login '/login', :controller => 'sessions', :action => 'create' end end
ActionController::Routing::Routes.draw do |map| map.namespace :admin do |admin| admin.resources :posts admin.resources :comments admin.logout '/logout', :controller => 'sessions', :action => 'destroy' admin.login '/login', :controller => 'sessions', :action => 'create' end end
ActionController::Routing::Routes.draw do |map| map.namespace :api do |api| api.resources :posts api.resources :comments api.logout '/logout', :controller => 'sessions', :action => 'destroy' api.login '/login', :controller => 'sessions', :action => 'create' end end
-
Then you should tell rails there are 3 additional route files.
-
You can set the configs in
config/application.rb
config.paths.config.routes.concat Dir[Rails.root.join("config/routes/*.rb")]
-
Advantage of performing this-:
- It is easier to maintain
- I only change the developer related routes in
config/routes/developer.rb
, without any possible to change the routes in admin or api namespace - Rails will only reload the developer routes if you only change the
config/routes/developer.rb
, instead of reloading the whole routes, which may be expensive when your routes are complicated.
-
Directly access an object if it’s present *If you want to access an object only if it’s present, you can use
Rails’Object#presence/blank
. *This is handy for any representation of objects where blank is the same as not present at all. For example, this simplifies a common check for HTTP POST/query parameters:state = params[:state] if params[:state].present? country = params[:country] if params[:country].present? region = state || country || 'UK'
…becomes:
region = params[:state].presence || params[:country].presence || 'UK'
-
Put custom initialization code in
config/initializers
. The code in initializers executes on application startup. -
The initialization code for each gem should be in a separate file with the same name as the gem, for example
carrierwave.rb
,active_admin.rb
, etc. -
Adjust accordingly the settings for development, test and production environment (in the corresponding files under
config/environments/
)-
Mark additional assets for precompilation (if any):
```Ruby # config/environments/production.rb # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) config.assets.precompile += %w( rails_admin/rails_admin.css rails_admin/rails_admin.js ) ```
-
- Because Object.methods returns an array, you can grep that just like in this tip about grepping anything from your enumerables.
- For example, if you are looking for a particular method of an object, you can easily narrow down the results like this:
Object.methods.grep /inspect/ O/P => ["inspect", "pretty_inspect", "pretty_print_inspect"]
- How to check if objects or relations exist?
Here’s an interesting fact when checking if objects or relations exist in a collection.
-
To check if there were any items present in a collection you can do something like this:
Object.relation.present?
-
This, however, is better:
Object.relation.any?
-
Turns out that - when you request associated objects for the first time - the
any?
method will perform aCOUNT (*)
SQL query where as thepresent?
method will perform aSELECT (*)
which is infinitely slower than performing a count. ``` property = Property.first property.amenities.present? # SQL (284.1ms) SELECT * FROM "amenities" WHERE ("amenity".property_id = 1)property = Property.first property.amenities.any? # SQL (1.2ms) SELECT count(*) AS count_all FROM "amenities" WHERE ("amenities".property_id = 1) ```
-
- To get an array of some property from the objects
amount_array = account.orders.collect { |order| order.amount.some_operation }
- For live project example
property_list = Property.near(params[:place], 5) available_property_ids = property_list.collect(&:id)
- we can also use map
amount_array = account.orders.map { |order| order.amount.some_operation }
- FIXME, TODO and OPTIMIZE code comments
- You can add some special notes to your source code comments in Rails to remind you later of stuff you need to do:
class Property < ActiveRecord::Base # TODO add named_scopes # FIXME method A is broken # OPTIMIZE improve the code has_many :amenities .... end
- You can list these special notes with a rake task:
$ rake notes app/models/property.rb: * [2] [TODO] add named_scopes * [3] [FIXME] method A is broken * [4] [OPTIMIZE] improve the code
- You can add some special notes to your source code comments in Rails to remind you later of stuff you need to do:
- How to get rid of from
NoMethodError
-
First by using unless
article = Article.find_by_title("My Article") unless article.nil? article.body end
-
Second by using new and trendy
try
-
With
try
you can skip thenil?
check and do the following:
Article.find_by_title("My Article").try(:body) => #body or nil
-
- To see sql querry on console as active record log
Just put below lines into
development.rb
fileif "irb" == $0 ActiveRecord::Base.logger = Logger.new(STDOUT) end
- In Ruby, you check with nil? if an object is nil:
article = nil article.nil? # => true empty? checks if an element - like a string or an array f.e. - is empty: # Array [].empty? #=> true # String "".empty? #=> true
- Rails adds the method
blank?
to the Object class:- An object is blank if it‘s false, empty, or a whitespace string. For example, “”, ” “, nil, [], and {} are blank.
This simplifies
to
if !address.nil? && !address.empty?
if !address.blank?
- An object is blank if it‘s false, empty, or a whitespace string. For example, “”, ” “, nil, [], and {} are blank.
This simplifies
-
Put custom initialization code in
config/initializers
. The code in initializers executes on application startup. -
The initialization code for each gem should be in a separate file with the same name as the gem, for example
carrierwave.rb
,active_admin.rb
, etc. -
Adjust accordingly the settings for development, test and production environment (in the corresponding files under
config/environments/
)-
Mark additional assets for precompilation (if any):
```Ruby # config/environments/production.rb # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) config.assets.precompile += %w( rails_admin/rails_admin.css rails_admin/rails_admin.js ) ```
-
-
Create an additional
staging
environment that closely resembles theproduction
one.
-
When you need to add more actions to a RESTful resource (do you really need them at all?) use
member
andcollection
routes.# 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
-
If you need to define multiple
member/collection
routes use the alternative block syntax.resources :subscriptions do member do get 'unsubscribe' # more routes end end resources :photos do collection do get 'search' # more routes end end
-
Use nested routes to express better the relationship between ActiveRecord models.
class Post < ActiveRecord::Base has_many :comments end class Comments < ActiveRecord::Base belongs_to :post end # routes.rb resources :posts do resources :comments end
-
Use namespaced routes to group related actions.
namespace :admin do # Directs /admin/products/* to Admin::ProductsController # (app/controllers/admin/products_controller.rb) resources :products end
-
Never use the legacy wild controller route. This route will make all actions in every controller accessible via GET requests.
# very bad match ':controller(/:action(/:id(.:format)))'
- 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).
- Each controller action should (ideally) invoke only one method other than an initial find or new.
- Share no more than two instance variables between a controller and a view.
-
Introduce non-ActiveRecord model classes freely.
-
Name the models with meaningful (but short) names without abbreviations.
-
If you need model objects that support ActiveRecord behavior like validation use the ActiveAttr gem.
class Message include ActiveAttr::Model attribute :name attribute :email attribute :content attribute :priority attr_accessible :name, :email, :content validates_presence_of :name validates_format_of :email, :with => /^[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i validates_length_of :content, :maximum => 500 end
For a more complete example refer to the RailsCast on the subject.
-
Avoid altering ActiveRecord defaults (table names, primary key, etc) unless you have a very good reason (like a database that's not under your control).
-
Group macro-style methods (
has_many
,validates
, etc) in the beginning of the class definition. -
Prefer
has_many :through
tohas_and_belongs_to_many
. Usinghas_many :through
allows additional attributes and validations on the join model.# using has_and_belongs_to_many class User < ActiveRecord::Base has_and_belongs_to_many :groups end class Group < ActiveRecord::Base has_and_belongs_to_many :users end # prefered way - using has_many :through class User < ActiveRecord::Base has_many :memberships has_many :groups, through: :memberships end class Membership < ActiveRecord::Base belongs_to :user belongs_to :group end class Group < ActiveRecord::Base has_many :memberships has_many :users, through: :memberships end
-
Always use the new "sexy" validations.
-
When a custom validation is used more than once or the validation is some regular expression mapping, create a custom validator file.
# bad class Person validates :email, format: { with: /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/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 =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i end end class Person validates :email, email: true end
-
All custom validators should be moved to a shared gem.
-
Use named scopes freely.
class User < ActiveRecord::Base scope :active, -> { where(active: true) } scope :inactive, -> { where(active: false) } scope :with_orders, -> { joins(:orders).select('distinct(users.id)') } end
-
Wrap named scopes in
lambdas
to initialize them lazily.# bad class User < ActiveRecord::Base scope :active, where(active: true) scope :inactive, where(active: false) scope :with_orders, joins(:orders).select('distinct(users.id)') end # good class User < ActiveRecord::Base scope :active, -> { where(active: true) } scope :inactive, -> { where(active: false) } scope :with_orders, -> { joins(:orders).select('distinct(users.id)') } end
-
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.class User < ActiveRecord::Base def self.with_orders joins(:orders).select('distinct(users.id)') end end
-
Beware of the behavior of the
update_attribute
method. It doesn't run the model validations (unlikeupdate_attributes
) and could easily corrupt the model state. -
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 theid
of the record as a String. It could be overridden to include another human-readable attribute.```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. Theid
of the object needs to be at the beginning so that it can be found by thefind
method of ActiveRecord. -
Use the
friendly_id
gem. It allows creation of human-readable URLs by using some descriptive attribute of the model instead of itsid
.```Ruby class Person extend FriendlyId friendly_id :name, use: :slugged end ``` Check the [gem documentation](https://github.com/norman/friendly_id) for more information about its usage.
-
-
When the response is in a format different from the existing ones (XML and JSON) or some additional parsing of these formats is necessary, create your own custom format and use it in the class. The custom format should implement the following four methods:
extension
,mime_type
,encode
anddecode
.module ActiveResource module Formats module Extend module CSVFormat extend self def extension 'csv' end def mime_type 'text/csv' end def encode(hash, options = nil) # Encode the data in the new format and return it end def decode(csv) # Decode the data from the new format and return it end end end end end class User < ActiveResource::Base self.format = ActiveResource::Formats::Extend::CSVFormat ... end
-
If the request should be sent without extension, override the
element_path
andcollection_path
methods ofActiveResource::Base
and remove the extension part.class User < ActiveResource::Base ... def self.collection_path(prefix_options = {}, query_options = nil) prefix_options, query_options = split_options(prefix_options) if query_options.nil? "#{prefix(prefix_options)}#{collection_name}#{query_string(query_options)}" end def self.element_path(id, prefix_options = {}, query_options = nil) prefix_options, query_options = split_options(prefix_options) if query_options.nil? "#{prefix(prefix_options)}#{collection_name}/#{URI.parser.escape id.to_s}#{query_string(query_options)}" end end
These methods can be overridden also if any other modifications of the URL are needed.
-
Keep the
schema.rb
under version control. -
Use
rake db:schema:load
instead ofrake db:migrate
to initialize an empty database. -
Use
rake db:test:prepare
to update the schema of the test database. -
Avoid setting defaults in the tables themselves (unless the db is shared between several applications). Use the model layer instead.
def amount self[:amount] or 0 end
While the use of
self[:attr_name]
is considered fairly idiomatic, you might also consider using the slightly more verbose (and arguably more readable)read_attribute
instead:def amount read_attribute(:amount) or 0 end
-
When writing constructive migrations (adding tables or columns), use the new Rails 3.1 way of doing the migrations - use the
change
method instead ofup
anddown
methods.# the old way class AddNameToPerson < ActiveRecord::Migration def up add_column :persons, :name, :string end def down remove_column :person, :name end end # the new prefered way class AddNameToPerson < ActiveRecord::Migration def change add_column :persons, :name, :string end end
- Never call the model layer directly from a view.
- Never make complex formatting in the views, export the formatting to a method in the view helper or the model.
- Mitigate code duplication by using partial templates and layouts.
- Add
client side validation
for the custom validators. The steps to do this are:
-
Declare a custom validator which extends
ClientSideValidations::Middleware::Base
```Ruby module ClientSideValidations::Middleware class Email < Base def response if request.params[:email] =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i self.status = 200 else self.status = 404 end super end end end ```
-
Create a new file
public/javascripts/rails.validations.custom.js.coffee
and add a reference to it in yourapplication.js.coffee
file:``` # app/assets/javascripts/application.js.coffee #= require rails.validations.custom ```
-
Add your client-side validator:
```Ruby #public/javascripts/rails.validations.custom.js.coffee clientSideValidations.validators.remote['email'] = (element, options) -> if $.ajax({ url: '/validators/email.json', data: { email: element.val() }, async: false }).status == 404 return options.message || 'invalid e-mail format' ```
-
-
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. -
When the labels of an ActiveRecord 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" andUser.human_attribute_name("name")
will return "Full name". These translations of the attributes will be used as labels in the views. -
Separate the texts used in the views from translations of ActiveRecord attributes. Place the locale files for the models in a folder
models
and the texts used in the views in folderviews
.-
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.```Ruby # config/application.rb config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')] ```
-
-
Place the shared localization options, such as date or currency formats, in files under the root of the
locales
directory. -
Use the short form of the I18n methods:
I18n.t
instead ofI18n.translate
andI18n.l
instead ofI18n.localize
. -
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 templateapp/views/users/show.html.haml
like this:= t '.title'
-
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.# use this call I18n.t 'activerecord.errors.messages.record_invalid' # instead of this I18n.t :record_invalid, :scope => [:activerecord, :errors, :messages]
-
More detailed information about the Rails i18n can be found in the [Rails Guides] (http://guides.rubyonrails.org/i18n.html)
Use the assets pipeline to leverage organization within your application.
- Reserve
app/assets
for custom stylesheets, javascripts, or images. - Third party code such as jQuery or bootstrap
should be placed in
vendor/assets
. - When possible, use gemified versions of assets (e.g. jquery-rails).
-
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. -
Provide both HTML and plain-text view templates.
-
Enable errors raised on failed mail delivery in your development environment. The errors are disabled by default.
# config/environments/development.rb config.action_mailer.raise_delivery_errors = true
-
Use
smtp.gmail.com
for SMTP server in the development environment (unless you have local SMTP server, of course).# config/environments/development.rb config.action_mailer.smtp_settings = { address: 'smtp.gmail.com', # more settings }
-
Provide default settings for the host name.
# config/environments/development.rb config.action_mailer.default_url_options = {host: "#{local_ip}:3000"} # config/environments/production.rb config.action_mailer.default_url_options = {host: 'your_site.com'} # in your mailer class default_url_options[:host] = 'your_site.com'
-
If you need to use a link to your site in an email, always use the
_url
, not_path
methods. The_url
methods include the host name and the_path
methods don't.# wrong You can always find more info about this course = link_to 'here', url_for(course_path(@course)) # right You can always find more info about this course = link_to 'here', url_for(course_url(@course))
-
Format the from and to addresses properly. Use the following format:
# in your mailer class default from: 'Your Name <info@your_site.com>'
-
Make sure that the e-mail delivery method for your test environment is set to
test
:# config/environments/test.rb config.action_mailer.delivery_method = :test
-
The delivery method for development and production should be
smtp
:# config/environments/development.rb, config/environments/production.rb config.action_mailer.delivery_method = :smtp
-
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: premailer-rails3 and roadie.
-
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 send. To overcome this emails can be send in background process with the help of delayed_job gem.
-
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 adarwin
group in the Gemfile, and all Linux specific gems to alinux
group:# 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
:platform = RUBY_PLATFORM.match(/(linux|darwin)/)[0].to_sym Bundler.require(platform)
-
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 abundle install
.
One of the most important programming principles is "Don't reinvent the wheel!". If you're faced with a certain task you should always look around a bit for existing solutions, before unrolling your own. Here's a list of some "priceless" gems (all of them Rails 3.1 compliant) that are useful in many Rails projects:
- active_admin - With ActiveAdmin the creation of admin interface for your Rails app is child's play. You get a nice dashboard, CRUD UI and lots more. Very flexible and customizable.
- capybara - Capybara aims to simplify the process of integration testing Rack applications, such as Rails, Sinatra or Merb. Capybara simulates how a real user would interact with a web application. It is agnostic about the driver running your tests and currently comes with Rack::Test and Selenium support built in. HtmlUnit, WebKit and env.js are supported through external gems. Works great in combination with RSpec & Cucumber.
- carrierwave - the ultimate file upload solution for Rails. Support both local and cloud storage for the uploaded files (and many other cool things). Integrates great with ImageMagick for image post-processing.
- client_side_validations - Fantastic gem that automatically creates JavaScript client-side validations from your existing server-side model validations. Highly recommended!
- compass-rails - Great gem that adds support for some css frameworks. Includes collection of sass mixins that reduces code of css files and help fight with browser incompatibilities.
- cucumber-rails - Cucumber is the premium tool to develop feature tests in Ruby. cucumber-rails provides Rails integration for Cucumber.
- devise - Devise is full-featured authentication solution for Rails applications. In most cases it's preferable to use devise to unrolling your custom authentication solution.
- fabrication - a great fixture replacement (editor's choice).
- factory_girl - an alternative to fabrication. Nice and mature fixture replacement. Spiritual ancestor of fabrication.
- faker - handy gem to generate dummy data (names, addresses, etc).
- feedzirra - Very fast and flexible RSS/Atom feed parser.
- friendly_id - Allows creation of human-readable URLs by using some descriptive attribute of the model instead of its id.
- guard - fantastic gem that monitors file changes and invokes tasks based on them. Loaded with lots of useful extension. Far superior to autotest and watchr.
- haml-rails - haml-rails provides Rails integration for Haml.
- haml - HAML is a concise templating language, considered by many (including yours truly) to be far superior to Erb.
- kaminari - Great paginating solution.
- machinist - Fixtures aren't fun. Machinist is.
- rspec-rails - RSpec is a replacement for Test::MiniTest. I cannot recommend highly enough RSpec. rspec-rails provides Rails integration for RSpec.
- simple_form - once you've used simple_form (or formtastic) you'll never want to hear about Rails's default forms. It has a great DSL for building forms and no opinion on markup.
- simplecov-rcov - RCov formatter for SimpleCov. Useful if you're trying to use SimpleCov with the Hudson contininous integration server.
- simplecov - code coverage tool. Unlike RCov it's fully compatible with Ruby 1.9. Generates great reports. Must have!
- slim - Slim is a concise templating language, considered by many far superior to HAML (not to mention Erb). The only thing stopping me from using Slim massively is the lack of good support in major editors/IDEs. Its performance is phenomenal.
- spork - A DRb server for testing frameworks (RSpec / Cucumber currently) that forks before each run to ensure a clean testing state. Simply put it preloads a lot of test environment and as consequence the startup time of your tests in greatly decreased. Absolute must have!
- sunspot - SOLR powered full-text search engine.
This list is not exhaustive and other gems might be added to it along the road. All of the gems on the list are field tested, have active development and community and are known to be of good code quality.
This is a list of gems that are either problematic or superseded by other gems. You should avoid using them in your projects.
- rmagick - this gem is notorious for its memory consumption. Use minimagick instead.
- autotest - old solution for running tests automatically. Far inferior to guard and watchr.
- rcov - code coverage tool, not compatible with Ruby 1.9. Use SimpleCov instead.
- therubyracer - the use of this gem in production is strongly discouraged as it uses a very large amount of memory. I'd suggest using Mustang instead.
This list is also a work in progress. Please, let me know if you know other popular, but flawed gems.
- If your projects depends on various external processes use foreman to manage them.
There are a few excellent resources on Rails style, that you should consider if you have time to spare:
It's a complementary guide to the already existing community-driven Ruby coding style guide.