The Ultimate Ruby on Rails Cheatsheet

Digestible Ruby on Rails information to help you with development.

The Ultimate Ruby on Rails

Cheat Sheet & Code Snippets

General

Create a Rails Application

rails new my-app

See the Options Available for Creating a Rails Application

rails new --help

Start Rails Server

rails s

Start Rails Console

rails c

Start Rails Console without Database Saving

This stops changes from saving while in the rails console

rails c --sandbox

View and Search Rails Routes in the Browser

localhost:3000/rails/info/routes

View Application Information in the Browser

http://localhost:3000/rails/info/properties

Bundle

Install Ruby Gems in Gemfile

bundle install

Install Ruby Gem

bundle add devise

Update ALL Ruby Gems

bundle update

Update Ruby Gem

bundle update devise

Remove Ruby Gem

bundle remove devise

Execute Bundle Command in Context of Application Gemfile

bundle exec ...

Generators

Create Controller

Creates a controllers/pages_controller.rb with the home and about action with view files

rails g controller pages home about

Create Migration

Creates the migration to add the column publish_date (date) to the projects table

rails g migration add_publish_date_to_projects publish_date:date

Create Model

Creates a models/project.rb file and a migration to create the projects table with the columns title (string) and body (text)

rails g model project title body:text

Create Scaffold

Does everything that rails g controller and rails g model would do

rails g scaffold projects title body:text

Create Rake Task

Creates a lib/rake/projects.rake file that includes the trim_title method, you can call this method with rake projects:trim_title

rails g task projects trim_title

Create Mailer

Creates a mailers/user_mailer.rb file with a thanks_for_joining action with corresponding view files; views/user_mailer/thanks_for_joining.txt.erb and views/user_mailer/thanks_for_joining.html.erb

rails g mailer user thanks_for_joining

Destroy

Think of destroy as the opposite of generate. It'll figure out what generate did, and undo it

Destroy Controller

rails d controller Pages

Delete Model

rails d model Article

Delete Scaffold

rails d scaffold Projects

Delete Mailer

rails d mailer User

Database

Create the Database

rails db:create

Drop the Database

rails db:drop

Migrate the Database

rails db:migrate

Get Statuses of All Database Migrations

rails db:migrate:status

Rollback Last Database Migration

rails db:rollback

Rollback Multiple Database Migrations

rails db:rollback STEP=5

Redo Database Migration

This does the following; rollback and migration the database

rails db:migrate:redo

Runs db:rollback and db:migrate

Seed the Database

Runs the db/seeds.rb file

rails db:seed

Reset the Database

This does the following; drops, create, migrates and seeds the database

rails db:reset

Change Database to PostgresQL

rails db:system:change --to=postgresql

Rest Database Table ID Count

ActiveRecord::Base.connection.reset_pk_sequence!('table_name')

Reset ALL Database Tables ID Count

ActiveRecord::Base.connection.tables.each do |t|
  ActiveRecord::Base.connection.reset_pk_sequence!(t)
end

Populate a New Table in a Database Migration

class CreatePostCategories < ActiveRecord::Migration[5.0]
	def up
		t.integer :id
        t.string :name

        t.timestamps
	end

	PostCategory.reset_column_information

	%w[news blog insight guide].each do |category|
        PostCategory.create(name: category)
    end

	def down
        drop_table :post_categories
    end
end

Reset Cached Information in Table Columns

PostCategory.reset_column_information

Routes

Set Root

root to: 'pages#home'

Create a Route

Creates an /about path that maps to the controller/pages_controller.rb about action

get 'about', to: 'pages#about'

Map the /about route to the PagesController about action (creates a about_path helper)

Create a Route with a Named Route Helper

Reference the /about path with about_us_path

get 'about', to: 'pages#about', as: 'about_us'

Same as the above but this time the helper is about_us_path

CRUD Routes

This creates the full CRUD routes; index, show, new, create, edit, update and destroy

resources :projects

CRUD Routes (Only)

This only creates the index and show action routes

resources :projects, only: %i[index show]

CRUD Routes (Except)

This creates all except the index and show action routes

resources :projects, except: %i[index show]

Non ID Resource

Use this when the resource doesn't need an ID lookup, in this example the route /profile would be the show action route

This creates all the CRUD routes, minus the index action route

resource :profile

Prefix URL and Controller

This will create routes like admin/projects which map to the controller controller/admin/projects_controller.rb and the views would be located at views/admin/projects

namespace :admin do
	resources :projects
end

Prefix URL without Changing Controller

This will create routes like admin/projects but the controller would be still be controller/projects_controller.rb and the views will be located at views/projects

scope :admin do
	resources :projects
end

Create Route for Object

This will create the route projects/search

resources :projects do
	collection do
		get "search"
	end
end

Create Route for Individual Records

This will create the route projects/:id/complete

resources :projects do
	member do
		put "complete"
	end
end

Add 301 Redirect

get 'about', to: redirect('about-us')

301 Moved Permanently - HTTP | MDN

Add 308 Redirect

get 'about', to: redirect('about-us')

308 Permanent Redirect - HTTP | MDN

Controllers

Run Code Before Action

before_action :authenticate_user

Run Code Before Action on Specific Actions

before_action :authenticate_user, only: %i[create update destroy]

Redirect Back with Fallback Location

redirect_back(fallback_location: root_path)

Models

Validate Presence of Attribute

validates :title, **presence**: true

Validate Formatting of Email

validates :email, **format**: URI::MailTo::EMAIL_REGEXP

Validate Attribute Value is Unique

validates :title, **uniqueness**: true

Validate Attribute Length is Within a Range

validates :title, length: { minimum: 60, maximum: 120 }

Set a CONST Variable

This can then be referenced as Model::COUNTRIES where Model is the class name

COUNTRIES = %w[England France Germany

Create a Scope

This allows you to call Project.live

You can also use methods, but scopes are recommend when they can be used

scope :live, -> { where(draft: false) }

Set Default Scope (NOT Recommended)

default_scope { order(created_at :desc) }

Callback Method on Action

This runs the set_publish_date method before the record is created

before_create :set_publish_date

private

def set_publish_date
	self.publish_date = Date.today
end

There are a lot of different call back methods, you can see the full list here:

Active Record Callbacks - Ruby on Rails Guides

Be careful when when using update in a callback as it can cause an infinite loop, to avoid this use update_columns

Query TRUE/FALSE on Boolean Column

Attributes that are stored as a boolean can be queried if TRUE or FALSE by appending "?"

@project.live?

@user.admin?

@product.published?

Setup Nested Attributes

has_many :tags

accepts_nested_attributes_for :tags

Prevent Record Creation if ALL Nested Attributes are Blank

has_many :tags

accepts_nested_attributes_for :tags, reject_if: :all_blank

Allow Records from Nested Attributes to be Destroy

has_many :tags

accepts_nested_attributes_for :tags, allow_destroy: true

Delegate Attributes to Model in a Relationship (Law of Demeter)

The "Law of Demeter" is the process of chaining method calls across objects

The Problem

post.user.name
post.user.email

Fix #1 - Model Methods

This works, but you could cause the model from growing out of control

# models/post.rb

def user_name
	user.username
end

def user_email
	user.email
end

Fix #2 - Delegate

It's a nice idea to pass allow_nil: true so nil is returned if the data is not available

# models/user.rb

delegate :name, :email, to: :post, allow_nil: true

You can then do:

post.user_name
post.user_email

Deleting Records without Callbacks (Fast but Risky)

This is the fastest way to delete records, but it skips callbacks

Post.first.delete
Post.delete_all

Deleting Records with Callbacks (Slow but Safe)

This is the slower way to delete records, but it doesn't skip callbacks and isn't that slow

Post.first.destroy
Post.destroy_all

ActiveRecord

Return All Records of Object

Project.all

Find Record by ID

Project.find(10)

Find Record by ID (Won't Crash if NIL)

Project.find_by(id: 10)

Find Record by ID (ID from Params)

Project.find(params[:id])

Find Records by Attribute Value

Project.find_by(title: 'Hello')

Return Records Matching the Query

This accepts multiple column/value combinations

Project.where(draft: false)

Return Records NOT Matching the Query

This accepts multiple column/value combinations

Project.where.not(draft: false)

Return Number of Records in Database Table

Project.count

Find the First, Second, Third, Fourth, Fifth, Forty Second and Last Record

Project.first
Project.second
Project.third
Project.fourth
Project.fifth
Project.forty_two
...
Project.last

"Accessing the Reddit" with Ruby on Rails

Exclude Current Record from Query

Project.where(live: true).without(self)

ActiveStorage

Add File Attachment to Model

has_one_attached :image

Check if File Attachment is Present

@post.image.attached?

Delete a File Attachment from Active Storage

@post.image.purge

ActionText

Add ActionText to Model

has_rich_text :body

Convert ActionText Content to Plain Text

This is useful for truncating to create an excerpt

body.to_plain_text

Expose ActionText Content on Model

This will allow you to query the content in SQL, useful for searching

has_one :action_text_rich_text, class_name: 'ActionText::RichText', as: :record

def self.search(query)
	joins(:action_text_rich_text)
		.where('action_text_rich_texts.body LIKE :query', query: "%#{query}%")
end

This will search the database for records where body is similar to the value of query

Views

Render Data

<%= @project.title %>

Render HTML Data

<%== @project.title %>

Render Data

<%= @project.title %>

Set Variable/Prop

<% background_color = "#000" %>

Assign Variable/Prop

<% bg_color = local_assigns.fetch(:background_color)

Assign Variable/Prop with Default Value

<% bg_color = local_assigns.fetch(:background_color, "#000")

Render Page Content

<%= yield %>

Render Content from Views into Layout

Add a <%= yield(...) %> helper layout file: views/layouts/application.html.erb

<%= yield(:head) %>

Pass content into a <%= content_for(...) %> with matching name to the helper yield

<% content_for(:head) do %>
    <meta name="turbolinks-visit-control" content="reload" />
<% end %>

Link to Page

<a href="/about">About</a>

<%= link_to("About", about_path) %>

Link to Page with HTML Attributes

<a href="/about" class="button">About</a>

<%= link_to("About", about_path, class: "button") %>

Same as the above but with HTML attributes included

Link to Record

<a href="/projects/10">Project title</a>

<%= link_to(@project.title, project_path(@project)) %>

Link to Record (Short)

<a href="/projects/10">Project title</a>

<%= link_to(@project.title, @project) %>

Delete Record (Link)

<%= link_to("Delete", @project, method: :delete) %>

Delete Record (Form - Recommended)

<%= button_to("Delete", @project, method: :delete) %>

Back Link

<%= link_to("Back", :back) %>

Create Object Link with Array (Admin)

<a href="admin/projects/">Projects</a>

<%= link_to("Projects", [:admin, :projects]) %>

This is the equivalent of:

<%= link_to("Project", admin_projects_path) %>

Create Record Show Link with Array (Admin)

<a href="admin/projects/10">Project</a>

<%= link_to("Project", [:admin, @project]) %>

This is the equivalent of:

<%= link_to("Project", admin_project_path(project)) %>

Create Record Edit Link with Array (Edit/Admin)

<a href="admin/projects/10/edit">Edit</a>

<%= link_to("Edit", [:edit, :admin, @project]) %>

This is the equivalent of:

<%= link_to("Edit", edit_admin_project_path(project)) %>

Create Email Link

<a href="mailto:john@doe.com">john@doe.com</a>

<%= mail_to("john@doe.com") %>

Create Email Link with Link Text

<a href="mailto:john@doe.com">Email me</a>

<%= mail_to("john@doe.com", "Email me") %>

Render Partial

<%= render "shared/header" %>

Render Partial with Variables/Props

<%= render("shared/header, title: "Hello World!") %>

Loop Through Object

<% @projects.each do |project| %>
    <%= project.title %>
<% end %>

Render Collection

<%= render(@projects) %>

This is the equivalent of:

<% @projects.each do |project| %>
    <%= render("project", project: project)
<% end %>

Conditional Render (IF)

<% if @project.draft? %>
    <span>Draft</span>
<% end %>

Conditional Render (Inline IF)

<%= render("download") if @project.download? %>

Conditional Render (IF/ELSE)

<% if @project.draft? %>
    <span>Draft</span>
<% else %>
    <span>Live</span>
<% end %>

Conditional Render (IF/ELSIF/ELSE)

<% if @project.draft? %>
    <span>Draft</span>
<% elsif @project.scheduled? %>
    <span>Scheduled</span>
<% else %>
    <span>Live</span>
<% end %>

Conditional rendering based on the value of @project.draft? and @project.scheduled?

Alternate CSS Classes in Loop

<% @projects.each do |project| %>
	<div class="<%= cycle("odd-class", "even-class") -%>">
		<%= project.title %>
	</div>
<% end %>

Pluralize Word

This will render "1 project" or "2 projects"

<%= pluralize(@projects.count, 'project') %>

Change "project" to "projects" if @projects.count > 1

Pluralize Word with Specific Plural Word

This will render "1 person" or "2 users"

<%= pluralize(@users.count, 'person', plural: 'users') %>

Truncate Text

<%= truncate("...")

Truncate Text at Specific Length

<%= truncate("...", length: 50)

Truncate HTML

This will render the HTML tags as a string

<%= truncate("<div>...</div>", escape: false)

Created HTML ID from Record

This will create project_10

dom_id(@project)

Created HTML ID from Record with Prefix

This will create edit_project_10

dom_id(@project, :edit)

Flash Messages

Add More Flash Types

controllers/application_controller.rb

add_flash_types :error, :success

Render Flash Message

<div class="alert alert-notice" role="alert">
    <%= flash[:notice] %>
</div>

<div class="alert alert-alert" role="alert">
    <%= flash[:alert] %>
</div>

Render Flash Messages with Dynamic Class

<% flash.each do |type, message| %>
    <div class="alert alert-<%= type %>" role="alert"> <%= message %> </div>
<% end %>

Caching

Fragment Caching

<%= @projects.each do |project| %>
    <% cache(project) %>
        ...
    <% end %>
<% end %>

Collection Caching

<%= render(partial: "project", collection: @projects, cached: true) %>

Fragment and Collection Caching in Ruby on Rails

Forms

Render Form Partial

views/projects/_form.html.erb

You should use this on the new and edit views to keep them DRY

<%= render("form", project: @project) %>

Form with Model

<%= form_with(model: project) do |form| %>
    <%= form.label(:title) %>
    <%= form.text_field(:title) %>
    <%= form.submit %>
<% end %>

Non-Remote Form

This will stop the form submitting with JavaScript

<%= form_with(..., local: true) do |form| %>
    ...
<% end %>

Working with JavaScript in Rails - Ruby on Rails Guides

Search Form

<%= form_with(url: search_path, method: :get, local: true) do |form| %>
    <%= form.label(:q, "Search") %>
    <%= form.search_field(:q, placeholder: "Search...") %>
    <%= form.submit("Search") %>
<% end %>

Namespace Form URL

This will create the URL admin/projects

<%= form_with(model: [:admin, project]) do |form| %>
    ...
<% end %>

Form Select

Data supplied from a CONST variable on the User model

<%= form.select(:country, User::COUNTRIES) %>

Form Select with Prompt Option

<%= form.select(:country, User::COUNTRIES, include_blank: "Select country") %>

Form Select with HTML Attributes

<%= form.select(:country, User::COUNTRIES, {}, { class: "form-control" }) %>

Created a Nested Form

<%= form.fields_for(:projects) do |project_fields| %>
    <%= render "project/form", form: project_fields %>
<% end %>

Remove Generated ID from Nested Form

<%= form.fields_for(:projects, include_id: false) do |project_fields| %>
    <%= render "project/form", form: project_fields %>
<% end %>

Showing Form Errors in the View

Base HTML to use for form errors:

<div role="alert">
  <h2> There's <%= pluralize(errors.count, 'error') %> to fix </h2>

  <ul>
    <% errors.full_messages.each do |error| %>
        <li><%= error %></li>
    <% end %>
  </ul>
</div>

The error message here will render like:

Name can't be blank
Email is invalid

Render this with:

<%= render("components/form/errors", errors: MODEL.errors) %>

Mailer

Email Address with Name

This will create the following string John Doe <johndoe@email.com>

If now name is passed it will return the email address only

ActionMailer::Base.email_address_with_name(@user.email, @user.name)

Cookies & Sessions

Create Session Cookie

Session cookies will be removed once the session is over (page closes)

session[:user_id] = current_user.id

Delete Session Cookie

session.delete(:user_id)

Create Cookie

cookies[:user_id] = current_user.id

Delete Cookie

cookies.delete(:user_id)

Signed Cookie

cookies.signed[:user_id] = current_user.id

Encrypted Cookie

cookies.encrypted[:user_id] = current_user.id

Set Cookie Expiration Date

cookies[:seen_newsletter_popup] = {
    value: "true",
    expires: 10.days
}

Permanent Cookie

This cookie will last 20 years

cookies.permanent[:seen_newsletter_popup] = "true"

Demystifying Cookie Security in Ruby on Rails 6

Strings

Return Length of String

"Hello World".size # 11

"Hello World".length # 11

Check if String Includes Text

"Hello World".includes?("Hello") # true

"Hello World".includes?("Goodbye") # true

Replace Part of a String

"Hello World".gsub("Hello", "Goodbye") # Goodbye World

Split String into Array

"Hello World".split # ["Hello", "World"]

"Simon, Jay, William".split(",") # ["Simon", " Jay", " William"]

String Interpolation

name = "Simon"

"Hello #{name}"

Arrays

Return Length of Array

["Hello", "World"].size # 2

["Hello", "World"].length # 2

["Hello", "World"].count # 2

Push to Array

["Hello", "World"] << "Goodbye" # ["Hello", "World", "Goodbye"]

["Hello", "World"].push("Goodbye") # ["Hello", "World", "Goodbye"]

Combine Arrays

["Hi", "Hello"].concat(["Bye", "See Ya"]) # ["Hi", "Hello", "Bye", "See Ya"]

["Hi", "Hello"] + ["Bye", "See Ya"] # ["Hi", "Hello", "Bye", "See Ya"]

Remove from Array

["Hello", "World"].delete("Hello") # World

["Hello", "World"].delete_at(1) # World

Remove All from Array

["Hello", "World"].clear # []

Check if Array Includes Text

["Hello", "World"].includes?("Hello") # true

["Hello", "World"].includes?("Goodbye") # true

Reverse Array

["Hello", "World"].reverse # ["World", "Hello"]

Shuffle the Array

Return a random order of the array

["Simon", "Jay", "William"].shuffle

Sample the Array

Return a random item from the array

["Simon", "Jay", "William"].sample

Remove Duplicate Items from Array

["A", "A", "B", "C", "C"].uniq # ["A", "B", "C"]

Flatten Array

[["Hi", "Hello"], ["Bye", "See Ya"]].flatten # ["Hi", "Hello", "Bye", "See Ya"]

Join Array Items (String)

["Simon", "Jay", "William"].join(", ") # Simon, Jay, William

Array Items to Sentence (String)

["Simon", "Jay", "William"].to_sentence # Simon, Jay, and William

Other

Symbol to Proc

Capitalize Each Item in Array

["hello", "world"].map(&:capitalize) # ["Hello", "World"]

Find Current Controller Name

controller.controller_name

Find Current Controller Action Name

controller.action_name

Remove Empty Values from Array

Returns a new array with empty values removed

["Hello", "", "World", nil].reject(&:blank?)

# ["Hello", "World"]

Create Array of Strings

You can use %W if you need interpolation

# Old
["Rails", "Tailwind", "HTML", "Stimulus"]

# New
%w[Rails Tailwind HTML Stimulus]

You can only use %w and %W on single words as the array splits on whitespace

Create Array of Symbols

You can use %I if you need interpolation

# Old
[:new, :edit, :create, :update, :destroy]

# New
%i[new edit create update destroy]

You can only use %i and %I on single words as the array splits on whitespace

Calculate Sum of Values

[10, 10, 20].sum

Do Block

Post.all.each do |post|
  post.title
end

Single Line Do Blocks

Post.all.each{ |post| post.save }

Create a Method

def do_something
end

Create Method with Arguments

def do_something(name)
end

Create Method with Default Argument

def do_something(name = "User")
end

Create a Class

class Person
end

Create a Class that Accepts Arguments

class Person
	attr_reader :name

	def initialize(name:)
		@name = name
	end
end

person = Person.new(name: "Simon")

If/Else Statement

number = 20

if number > 10
	"Greater than 10"
else
	"Less than 10"
end

If/Elsif/Else Statement

number = 20

if number > 15
	"Greater than 15"
elsif number > 10
	"Greater than 10"
else
	"Less than 10"
end

Case/Switch Statement

number = 20

case number
when > 15
	"Greater than 15"
when > 10
	"Greater than 10"
else
	"Less than 10"
end

PG Search (Gem)

bundle add pg_search

Search Against Single Attribute on Model

include PgSearch::Model

pg_search_scope :search, against: :title

Search Against Multiple Attributes on Model

include PgSearch::Model

pg_search_scope :search, against: %i[title subtitle body]

Search Against Action Text Data

Replace body with the name of your column using Action Text

include PgSearch::Model

pg_search_scope :search, associated_against: { rich_text_body: :body }

Web Console (Gem)

Installed by default

View Interactive Rails Console in the Browser

class ProjectsController < ApplicationController
	def index
		@projects = Project.all

		console
	end
end

Or you can use it in ERB like so.

<h1>All Projects</h1>

<%= console %>

Friendly ID (Gem)

bundle add friendly_id

Use Single Attribute

class Project < ApplicationRecord
	extend FriendlyId
	friendly_id :title, use: [:slugged, :finders]
end

Fallback to Multiple Attributes

If title is not unique fallback to title-company

class Project < ApplicationRecord
	extend FriendlyId
	friendly_id :slug_candidates, use: [:slugged, :finders]

	def slug_candidates
		[:title, [:title, :company]]
	end
end

Better Find

This removes the need to use friendly.find

use: [:slugged, :finders]

Stripe (Gem)

bundle add stripe

Setup Stripe

config/initializers/stripe.rb

Rails.configuration.stripe = {
  publishable_key: ENV["STRIPE_PUBLISHABLE_KEY"],
  secret_key: ENV["STRIPE_SECRET_KEY"],
}

Stripe.api_key = Rails.configuration.stripe[:secret_key]

views/layouts/application.html.erb

<%= javascript_include_tag "https://js.stripe.com/v3/" %>

Stripe Checkout

config/routes.rb

scope "checkout" do
	post "create", to: "checkout#create", as: :checkout_create
	get "cancel", to: "checkout#cancel", as: :checkout_cancel
	get "success", to: "checkout#success", as: :checkout_success
end

controllers/checkouts_controller.rb

class CheckoutController < ApplicationController
  def create
    @session = Stripe::Checkout::Session.create(
      payment_method_types: %w[card],
      line_items: [
        {
          name: params[:name],
          description: params[:description],
          amount: params[:amount],
          currency: "gbp",
          quantity: params[:quantity],
        },
      ],
      success_url: checkout_success_url,
      cancel_url: checkout_cancel_url,
    )

    respond_to do |format|
      format.js
    end
  end

  def success; end

  def cancel; end
end

views/checkout/create.js.erb

const stripe = Stripe("<%= ENV['STRIPE_PUBLISHABLE_KEY'] %>")

stripe.redirectToCheckout({
  sessionId: "<%= @session.id %>"
})

views/products/product.html.erb

<%= form_with(url: checkout_create_path, local: false) do |form| %>
    <%= form.hidden_field :name, value: @product.title %>
    <%= form.hidden_field :description, value: @product.description %>
    <%= form.hidden_field :amount, value: @product.price * 100 %> <%= form.number_field :quantity, value: 1 %>
    <%= form.submit %>
<% end %>

Code Snippets

Rails Based Body Class

Set a unique class to the <body> tag, created from the current controller and action names

def page_class
  "#{controller.controller_name}-#{controller.action_name}"
end

And then in your layout file you can do this.

<body class="<%= page_class %>">

Better Email Link

Removes the need to pass the email twice when using HTML attributes

def email_to(email, **object)
  link_to(email, "mailto:#{email}", **object)
end

Better Phone Link

Removes the need to pass the email twice when using HTML attributes

def tel_to(number, **object)
  link_to(number, "tel:#{number}", **object)
end

As of Rails v6.1.3.1 there is a phone_to helper