Ruby on Rails Cheatsheet


Project setup / run


Setup a new project :

rails new <project_name>
bundle install

Start server :

rails server

Start IRB :

rails console


Scaffolding


Full scaffolding :

rails g scaffold <Entity>s <field_name>:<type>

Model scaffolding (includes migration) :

rails g model <Entity> <field_name>:<type>

Controller scaffolding :

rails g controller <Entity>s <list_of_actions> 
# generate all actions ( index show new create edit update delete )
rails g scaffold_controller <Entity>s

Show all routes :

rails routes


Migrations


Documentation : Active Record Migrations

Run migrations :

rails db:migrate # RAILS_ENV=dev VERSION=<timestamp>

Run rollback :

rails db:rollback # STEP=3

Reset DB / re-seed :

rails db:drop
rails db:reset
rails db:seed

Create a new migration :

# Example formats
CreateProducts                     name:string
AddDetailsToProducts               part_number:string price:decimal
AddPartNumberToProducts            part_number:string
AddUserRefToProducts               user:references
CreateJoinTableCustomerProduct     customer product
# Add a new Entity with fields
rails g migration Create<Entity>s <field_name>:<type>

# Add a new field to an Entity
rails g migration <Migration_Name> <field_name>:<type>
rails g migration <Migration_Name> <field_name>:<type>:index

# remove a field
rails g migration Remove<Field_name>From<Entity> <field_name>:<type>

# create a 'join' table for a 'many-to-many' relationship
rails g migration CreateJoinTable<EntityA><EntityB> <entity_a> <entity_b>


Migration file syntax


Field types :

add_column :products, :description, :text, :limit => 1000
add_column :products, :price, precision: 8, scale: 2

add_reference :users, :role, index: false
add_reference :users, :role, foreign_key: true
add_foreign_key :articles, :authors

Create table :

create_table :products do |t|
  t.string :name, index: true
  t.decimal :price, precision: 8, scale: 2
  t.references :color, null: false, foreign_key: true
  t.timestamps
end

Edit table :

add_column :products, :description, :text

change_table :products do |t|
  t.remove :description, :name
  t.string :part_number
  t.index :part_number
  t.rename :upccode, :upc_code
end

# Set special values
change_column_null :products, :name, false
change_column_default :products, :approved, from: true, to: false

Create ‘join’ table :

create_join_table :products, :categories, column_options: { null: true } do |t|
  t.index :product_id
  t.index :category_id
end

Add a reference with a custom name :

t.references :author, foreign_key: { to_table: :users }

Hand-made SQL :

Product.connection.execute("UPDATE products SET price = 'free' WHERE 1=1")


Routing


Documentation : Routing

Explicit route :

get 'profile', to: 'users#show'
get 'products/:id', to: 'products#show'
get 'exit', to: 'sessions#destroy', as: :logout
get '/stories', to: redirect('/articles')

Automatically generate routes for an Entity :

resources :products
resources :products, only: [:index, :show]
resources :products, except: [:delete]

# generate everything except 'index'
resource :geocoder

Nested entities :

resources :products do
  resources :reviews
end

Group routes into a namespace for clarity :

namespace :admin do
  resources :articles
end

Routes generated for resources :

index
show
new   ->  create
edit  ->  update
destroy


Models


Relationships :

Documentation : Association basics

has_many :books
has_one :author
belongs_to :author
has_and_belongs_to_many :parts

# Enfore constraint in DB
belongs_to :author, foreign_key: true

# Add a relationship with a custom name
belongs_to :author, class_name: "User", foreign_key: "author_id"

# ON DELETE CASCADE
has_many :books, dependent: :destroy
Many to Many - Specific table
class Physician < ApplicationRecord
  has_many :appointments
  has_many :patients, through: :appointments
end

class Appointment < ApplicationRecord
  belongs_to :physician
  belongs_to :patient
end

class Patient < ApplicationRecord
  has_many :appointments
  has_many :physicians, through: :appointments
end

Validations :

Documentation : Active Record Validations

validates :first_name, presence: true
validates :title, uniqueness: true
validates_uniqueness_of :title

validates :email, format: { with: /\A[^@\s]+@[^@\s]+\z/, message: 'Invalid email'}
validates :title, length: { minimum: 3, maximum: 100 }
validates :status, allow_blank: true
validates_numericality_of :rating, { only_integer: true, allow_nil: true }
validates_numericality_of :rating, { only_integer: true, greater_than_equal_to: 0, lesser_than_or_equal_to: 10 }
validates_inclusion_of :rating, :in => 0..10

# Also validate related entities when creating/updating both
validates_associated :books

Check validity in controller :

Person.create(name: "John Doe").valid?

# list of validation errors
p.errors
p.errors.objects.first.full_message

Hooks / Callbacks :

Documentation : Callbacks

before_destroy :ensure_not_reference_by_any_invoices 
before_save :downcase_email 

before_action :set_post, only: [ :show, :edit, :update, :destroy ]
private
  def set_post
  ...


Controllers


Documentation : Action Controller

Full controller example
def index
  if session[:q].present?
    params[:page] = 1
    @posts = Post.where "title like ?", "%" + session[:q] + "%"
  else
    @posts = Post.all
  end
  @posts = @posts.order("created_at DESC")
  session[:q] = nil
end

def show
end

def new
  @post = Post.new
end

def edit
end

def create
  @post = Post.new(post_params)
    if @post.save
      redirect_to @post, notice: 'Created successfully !'
    else
      render :new 
    end
end

def update
  if @post.update(post_params)
      redirect_to @post, notice: 'Updated successfully !'
    else
      render :edit 
    end
end

def destroy
  @post.destroy
  redirect_to posts_url, notice: 'Deleted successfully !' 
end
private
  # Util methods
  def set_post
    @post = Post.find(params[:id])
  end

  # Filter request params
  def post_params
    params.require(:post).permit(:title, :body, :image_url)
  end
end



Active Record


Documentation : Active Record

Article.find(params[:id])                # will error if not found
Article.find_by(product_id: product_id)  # will not error if no results
Article.find_by_name("Blaster 3000")
Article.find_by_name_and_parts_count("Blaster 3000", 3)
Article.where('quantity > 1')
Article.where(cat_id: cat_id)
Article.where(type: [2,6,11])
Article.where(model: model).or(Article.where(cat_id: cat_id))
Article.where("title LIKE ?", "%" + params[:q] + "%")
Article.join(:categories).where(categories: { id: 2 } )
Article.offset(5).limit(10).all
Article.select(:id).map(&:id)
Article.count
Article.all
Article.delete_all
Article.ids
Article.exists?(1)

Documentation : Calculations | Grouping

Article.group(:product_id).sum(:quantity)
Article.select("created_at, sum(total) as total_price")
  .group("created_at").having("sum(total) > ?", 200)

Documentation : Joining | n+1 Eager Loading

# Join
User.joins(:posts).select('distinct users.*')
User.joins(:posts).select(users.id, posts.published as published).where(id: 1)

# Preload (can't use WHERE here)
User.preload(:posts) # 

# Includes
User.includes(:posts).where(posts: { published: true })
Post.includes({ comments: [:author] }, :author).find(params[:id])

# Eager load
User.eager_load(:posts).where("posts.published = ?", true)

Documentation : Find or create

Customer.find_or_create_by(first_name: 'Andy')
nina = Customer.find_or_initialize_by(first_name: 'Nina')

Documentation : Scopes

class Book < ApplicationRecord
  scope :out_of_print, -> { where(out_of_print: true) }
  scope :out_of_print_and_expensive, -> { out_of_print.where("price > 500") }

  scope :costs_more_than, ->(amount) { where("price > ?", amount) }

  scope :created_before, ->(time) { where(created_at: ...time) if time.present? }

  # Apply to all queries in this model
  default_scope { where(out_of_print: false) }
end

class BookController < ApplicationController
  Book.out_of_print
  author.books.out_of_print

  Book.costs_more_than(100.10)
end

Documentation : Enums

create_table :orders do |t|
  t.integer :status, default: 0
end
class Order < ApplicationRecord
  enum :status, [:shipped, :being_packaged, :complete, :cancelled]
end

class OrderController < ApplicationController
  all_shipped_orders = Order.shipped
  order = Order.shipped.first
  order.shipped?
end


Views

Document : Layouts and Rendering

The usual ones :

<%= @user.name %>
<% if @user.name == 'Mike' %>

<%# link_to %>
<%= link_to 'Show', @product %>
<%= link_to 'About', about_path, class: 'nav-link' %>
<%= link_to 'Item record', item_path(@item) %>
<%= link_to category_path do %>
  <%= @product.category.name %>
<% end %>

<%# Delete link %>
<%= link_to 'Destroy', product, method: :delete, data: { confirm: 'Are you sure?' } %>
<%# Post link %>
<%= button_to 'Add to Cart', line_items_path(product_id: product) %>
<%# Button form %>
<%= button_to 'Logout', logout_path, method: :delete %>

<%# Image %>
<%= image_tag "rails.png" %>

Render a collection :

<% @users.each do |user| %>
  <%= user.name %>
<% end %>

<%# Will be rendered by _user.html.erb %>
<%= render @users %> 

Utils :

<%# Format currency %>
<%= number_to_currency(product.price) %>

<%# Safe Html through sanitization %>
<%= sanitize(product.description) %>

<%# Check current url %>
<%= request.path.include?('post') ? 'active' : '' %>">
<%= current_page?('about') ? 'active' : '' %>">

Partial views :

<%= render partial: 'shared/navbar' %>
<%# Pass variable @post as "post" to partial view %>
<%= render partial: "post_with_comments", object: @post, as: :post %>

<%= render 'form', product: @product %>

Error messages :

<% if @post.errors.any? %> 
<% if form.object.errors.any? %>

<% if @post.errors.empty? %>
  <% @post.errors.full_messages.each do |message| %>
    <p class='error'><%= message %></p>
  <%= user.errors.count %>
<%= post.errors[:description] %>

<%# Flash messages %>
<% flash.each do |msg_type, msg| %>
  <div class="alert alert-<%= msg_type %>">
    <%= msg %>
  </div>
<% end %>


Views - Forms

Documentation : Form helpers

Model form :

<%= form_with(model: product) do |form| %>
  <%= form.label :title %>
  <%= form.text_field :title, class: 'form-control' %>
  <%= form.submit %>
<% end %>

Other form :

<%# Add 'local: false' for external API calls %>
<%= form_with url: "/search", method: :get do |form| %>
  <%= form.label :query, "Search for:" %>
  <%= form.text_field :query, placeholder: 'search' %>
  <%= form.submit "Search", class: 'btn btn-primary' %>
<% end %>

Textarea :

<%# Textarea %>
<%= form.text_area :description, rows: 10, cols: 60 %>

Select / Options :

<%= form.select :type, Customer.types.keys, prompt: 'Select a type' %>
<%= form.select :rating, (1..5) %>

<%= form.select :city, [["Berlin", "BE"], ["Chicago", "CHI"]], selected: "CHI" %>
<%= form.select :city,
      {
        "Europe" => [ ["Berlin", "BE"], ["Madrid", "MD"] ],
        "North America" => [ ["Chicago", "CHI"] ],
      },
      selected: "CHI" %>

# Date select
<%= form_with model: @person do |form| %>
  <%= form.date_select :birth_date %>
<% end %>

<%# Select (field, collection, key, value, label) %>
<%= form.collection_select :category_id,
                           Category.all,
                           :id,
                           :name,
                           {include_blank: '- Select a Category -'}, {class: 'form-select'}

Checkbox & Radio :

<%= form.check_box :pet_dog %>
<%= form.label :pet_dog, "I own a dog" %>

<%= form.radio_button :age, "adult" %>
<%= form.label :age_adult, "I am over 21" %>

Documentation : Other helpers


Misc :

<%# if form in new or edit mode change submit text %> 
<%= form.submit @product.new_record? ? 'Create' : 'Update' %>


Flash :

# Create flash (reset every new request)
flash[:success] = 'User created with success!'

# Create flash.now (reset every new view render)
flash.now[:error] = 'Please select a user!'

Session :

# Create session (reset every browser close)
session[:user_id] = user.id

# Check if session exist
session[:user_id].nil?

# Remove / wipe
session.delete(:user_id)
reset_session      

Cookie :

# Create cookie
cookies.permanent[:remember_token] = remember_token

# Encrypted cookie
cookies.permanent.encrypted[:user_id] = user.id

# Delete cookie
cookies.delete(:user_id)



Some parts of this cheatsheet come from My beloved Ruby on Rails cheat sheet made by Eric The Coder.