Rails

Ruby

Guides

http://guides.rubyonrails.org/

Primary

Secondary

New With a Specific Rails Version

$ rails _6.0.3.7_ new …

Generating Models

rails generate model Name field:type
rails generate scaffold Name field:type

Field types:

  • :primary_key - note that an id:integer primary key column is added by default if this isn’t specified
  • :string - i.e. of limited short length
  • :text - i.e. of indeterminate length
  • :integer
  • :bigint
  • :float
  • :decimal
  • :numeric
  • :datetime
  • :time
  • :date
  • :binary
  • :boolean
  • :references or :belongs_to - creates an ID column referencing another model table by name, along with a foreign key constraint

Suffixes

  • :uniq - unique index
  • :index - non-unique index

Create Table Migrations

Whether a create_table migration is generated or written by hand, column modifiers can be added to the t.column lines to further configure the column creation. The most common are:

  • default: the default value
  • index: whether to create an index for the column
  • limit: max characters
  • null: whether the column is nullable

Migrations

Writing

def change
  create_table :tablename do |t|
    t.string :field, null: false, default: ''
    t.references :user, null: false, foreign_key: true
    t.timestamps
  end
end

add_column :todos, :completed_at, :datetime, precision: 6
remove_column :ideas, :quote, :text
add_reference :todos, :category, type: :uuid, null: true, foreign_key: true
add_index :users, :email, unique: true

create_join_table :sources, :tags do |t|
  t.index :source_id
  t.index :tag_id
end

Running

rails db:setup
rails db:reset
rails db:migrate
rails db:rollback [STEP=N] [VERSION=0]

Modifying Columns

Outside of a create_table migration, columns on existing tables can be added, removed, or changed.

  • Change type: change_column
  • Change nullability: change_column_null
  • Change default value: change_column_default
  • add_foreign_key
  • remove_foreign_key
  • add_index
  • remove_index

Models

belongs_to :model
has_many :models
validates :field, presence: true

Model.create(attributes) # Boolean
Model.create!(attributes) # raises

Model.find(id)
Model.find_by(field: value)
Model.where(field: value)

model.update(attributes) # Boolean
model.update!(attributes) # raises

Routes

resources :model

Nesting

Path and module

namespace :admin do
  resources :articles # /admin/articles, Admin::ArticlesController
end

Path only

scope module: 'admin' do
  resources :articles # /articles, Admin::ArticlesController
end

Module only

scope '/admin/' do
  resources :articles # /admin/articles, ArticlesController
end

Controllers

Path Helper HTTP Method Controller Method
models models_path GET #index
    POST #create
models/new new_model_path GET #new
models/:id model_path(id) GET #show
    PUT/PATCH #update
    DELETE #destroy
models/:id/edit edit_model_path(id) GET #edit
before_action :populate_model

def populate_model
  @model = Model.find_by(slug: params[:model_slug])
end

params.require(:model).permit(:field, …)
redirect_to path, notice: string

Disabling CSRF

skip_forgery_protection

Rendering

render plain: "hello world"
render json: object.to_json, status: :something
head :no_content

Templates

<%= link_to label, path, method: :delete, data: { confirm: 'Are you sure?' } %>

<%= form_with model: @model, local: true do |f| %>
  <%= f.label :field %>
  <%= f.text_field :field %>
  <% @model.errors.full_messages_for(:field).each do |message| %>
    <p><%= message %>
  <% end %>
  <%= f.submit 'Create' %>
<% end %>

<%= yield :head %>

<% content_for :head do %>
  <title>A simple page</title>
<% end %>

Time Zones

https://thoughtbot.com/blog/its-about-time-zones

ActiveSupport::TimeWithZone supplements Time so it is fancy

Application time zone configured in config/application.rb

In a Rails app, we have three different time zones:

  1. system time
  2. application time
  3. database time
  • Time.now is system time
  • Time.parse gives system time

Doing Time.zone then calling methods on that gives application time instead:

Time.zone.now
Time.zone.parse

Time Helpers

https://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html

require 'active_support/testing/time_helpers'

RSpec.describe "whatev" do
  include ActiveSupport::Testing::TimeHelpers
  • #freeze_time (can take a block)
  • #unfreeze_time
  • #travel_to - stubs, so shouldn’t change as the test runs

UUIDs

class EnableUuids < ActiveRecord::Migration[6.0]
  def change
    enable_extension 'uuid-ossp'
    enable_extension 'pgcrypto'
  end
end
class CreateTodos < ActiveRecord::Migration[6.0]
  def change
    create_table :todos, id: :uuid do |t|
      # ...
    end
  end
end
class AddCategoryToTodos < ActiveRecord::Migration[6.0]
  def change
    add_reference :todos, :category, type: :uuid, foreign_key: true
  end
end

Cache

https://guides.rubyonrails.org/caching_with_rails.html

config.cache_store = :null_store
config.cache_store = :memory_store
config.cache_store = :file_store
config.cache_store = :redis_cache_store, { url: ENV['REDIS_URL'] } # not TLS

group :production do
  gem 'hiredis'
  gem 'redis'
end

Rails.cache.fetch('key', expires_in: 12.hours) do
  # calculate the value
end

Autoloading Lib

In config/application.rb:

config.autoload_paths << Rails.root.join('lib')

Rails Tests

Request (via RSpec-Rails)

get "/widgets/#{widget.id}", headers: headers
post '/categories', headers: {'Content-Type' => 'application/json'}, params: body_hash.to_json