🚧

Rails 入門まとめ

2025/02/04に公開

Rails 入門まとめ

基本コマンド

プロジェクト作成

rails new store

Rails サーバー起動

bin/rails s

モデル・レコード

モデル作成

bin/rails generate model Product name:string
  1. db/migrateにマイグレーションファイル作成
  2. app/models/product.rbというActive Recordモデル作成
  3. このモデルで使うテストファイルとフィクスチャを作成

マイグレーション実行

bin/rails db:migrate

直前のマイグレーションに戻す

bin/rails db:rollback

Railsコンソール実行(Railsと対話)

bin/rails console

レコード作成・保存

product = Product.new(name: "T-Shirt")
product.save
Product.create(name: "Cap") # 作成・保存

レコード検索

Product.all # 全件
Product.where(name: "Pants") # where
Product.order(name: :asc) # orderBy
Product.find(1) # id

レコード更新

product = Product.find(1)
product.update(name: "Shoes")
or
product = Product.find(1)
product.name = "Shoes"
product.save

レコード削除

product.destroy

バリデーション

# app/models/product.rb
class Product < ApplicationRecord
  validates :name, presence: true # nameカラムの存在確認
end

バリデーションすることでレコード登録するときに値の検証が自動でできる
登録に失敗した時の確認は以下

product.errors
product.errors.full_messages # わかりやすいメッセージ

ルーティング

ルーティング定義
config/routes.rb

Rails.application.routes.draw do
  # (省略)
  # /products への GETメソッド は ProductController の indexメソッドへ
  # ルーティング という意味
  get "/products", to: "products#index"  # この行を追加
end

主なコントローラアクション

  • index: すべてのレコードを表示
  • new: 新しいレコード1件を作成するためのフォームをレンダリング
  • create: newのフォーム送信を処理し、エラーを処理してレコードを1件作成
  • show: 指定のレコード1件をレンダリングして表示
  • edit: 指定のレコード1件を更新するためのフォームをレンダリング
  • update: editのフォーム送信を処理し、エラーを処理してレコードを1件更新
  • destroy: 指定のレコード1件を削除
get "/products", to: "products#index"
get "/products/new", to: "products#new"
post "/products", to: "products#create"
get "/products/:id", to: "products#show"
get "/products/:id/edit", to: "products#edit"
patch "/products/:id", to: "products#update"
put "/products/:id", to: "products#update"
delete "/products/:id", to: "products#destroy"

resources :products # これだけあれば上記と同等

ルーティング確認コマンド

bin/rails routes

コントローラー作成

# ルーティングは作成したので --skip-routes
bin/rails generate controller Products index --skip-routes

作成されるコントローラーファイル

# app/controllers/products_controller.rb
class ProductsController < ApplicationController
  # これがアクションになる
  # 中身が空だが、その場合はデフォルトで同名のテンプレートをレンダリング
  def index
  end
end

indexを実行すると以下をレンダリング

# app/views/products/index.html.erb
<h1>Products#index</h1>
<p>Find me in app/views/products/index.html.erb</p>

ルートパスにルーティングさせる

root "products#index"

ビューにデータを渡す

class ProductsController < ApplicationController
  def index
    # ビューにデータを渡す時はインスタンス変数を使用
    @products = Product.all
  end
end

表示するビューを変更

<h1>Products</h1>

<div id="products">
  <% @products.each do |product| %>
    <div>
      <%= product.name %>
    </div>
  <% end %>
</div>

showアクションの追加

class ProductsController < ApplicationController
  def index
    @products = Product.all
  end

  def show
    # paramsハッシュから値を取得
    @product = Product.find(params[:id])
  end
end

showのビューを作成

# app/views/products/show.html.erb
<h1><%= @product.name %></h1>

<%= link_to "Back", products_path %>

routesが以下だった場合のヘルパーメソッド

Prefix Verb URI Pattern Controller#Action
products GET /products(.:format) products#index
product GET /products/:id(.:format) products#show

link_toを使用したindex.html.erb

<h1>Products</h1>

<div id="products">
  <% @products.each do |product| %>
    <div>
      <%= link_to product.name, product %>
    </div>
  <% end %>
</div>

newアクションの追加

# Productコントローラー
  def new
    @product = Product.new
  end

indexのビュー更新

<h1>Products</h1>

<%= link_to "New product", new_product_path %>

<div id="products">
  <% @products.each do |product| %>
    <div>
      <%= link_to product.name, product %>
    </div>
  <% end %>
</div>

newのビュー追加

<h1>New product</h1>

<%= form_with model: @product do |form| %>
  <div>
    <%= form.label :name %>
    <%= form.text_field :name %>
  </div>

  <div>
    <%= form.submit %>
  </div>
<% end %>

createアクションの追加

  def create
    @product = Product.new(product_params)
    if @product.save
      redirect_to @product
    else
      render :new, status: :unprocessable_entity
    end
  end

  private
    def product_params
      params.expect(product: [ :name ])
    end

product_paramsは渡されたパラメータを検査する
productに許可しているのは:nameだけなので、その他の値は無視される
詳しくはStrongParameter参照
redirect_toに Active Recordオブジェクトを渡すと、そのレコードのshowアクションへのパスを生成する
saveが失敗して、レコードが有効にならなかった場合、同じフォームを再レンダリングして、ユーザーが無効なデータを修正できるようにする
createアクションのelseではrender :newをレンダリングするように指示している

編集用にedit, updateを追加

  def edit
    @product = Product.find(params[:id])
  end

  def update
    @product = Product.find(params[:id])
    if @product.update(product_params)
      redirect_to @product
    else
      render :edit, status: :unprocessable_entity
    end
  end

showのビューにeditを追加

# app/views/products/show.html.erb
<h1><%= @product.name %></h1>

<%= link_to "Back", products_path %>
<%= link_to "Edit", edit_product_path(@product) %>

before_actionでcontrollerをDRY

class ProductsController < ApplicationController
  before_action :set_product, only: %i[ show edit update ]

  def index
    @products = Product.all
  end

  def show
  end

  def new
    @product = Product.new
  end

  def create
    @product = Product.new(product_params)
    if @product.save
      redirect_to @product
    else
      render :new, status: :unprocessable_entity
    end
  end

  def edit
  end

  def update
    if @product.update(product_params)
      redirect_to @product
    else
      render :edit, status: :unprocessable_entity
    end
  end

  private
    def set_product
      @product = Product.find(params[:id])
    end

    def product_params
      params.expect(product: [ :name ])
    end
end

before_actionを使うと、アクション間で共有されているコードを抽出して、アクションの直前に実行できる

ビューを切り出す(パーシャル)
newとeditのビューが似ているので、共通部分として切り出す
パーシャルはファイル名の頭にアンダースコアをつける

# app/views/products/_form.html.erb
<%= form_with model: product do |form| %>
  <div>
    <%= form.label :name %>
    <%= form.text_field :name %>
  </div>

  <div>
    <%= form.submit %>
  </div>
<% end %>

new, editのビューはパーシャルを活用し、以下の様になる

<h1>New product</h1>

<%= render "form", product: @product %>
<%= link_to "Cancel", products_path %>
<h1>Edit product</h1>

<%= render "form", product: @product %>
<%= link_to "Cancel", @product %>

destroyアクションを追加

 before_action :set_product, only: %i[ show edit update destroy ]
  ...
  def destroy
    @product.destroy
    redirect_to products_path
  end

destroyビュー

<h1><%= @product.name %></h1>

<%= link_to "Back", products_path %>
<%= link_to "Edit", edit_product_path(@product) %>
<%= button_to "Delete", @product, method: :delete, data: { turbo_confirm: "Are you sure?" } %>

button_toは適切にアクションをトリガーしてくれる
turbo_confirmデータ属性は、フォームを送信する前にユーザーに確認ダイアログを表示するようにTurboというJavaScriptライブラリに指示する

認証機能を追加する

bin/rails generate authentication

続けてマイグレーションを行うとUserモデル用のusersテーブルと、Sessionモデル用のsessionsテーブルをデータベースに追加する

bin/rails db:migrate

コンソールでユーザー作成

store(dev)> User.create! email_address: "you@example.org", password: "s3cr3t", password_confirmation: "s3cr3t"

ログアウト機能追加

<!DOCTYPE html>
<html>
  <!-- (省略) -->
  <body>
    <nav>
      <%= link_to "Home", root_path %>
      <%= button_to "Log out", session_path, method: :delete if authenticated? %>
    </nav>

    <main>
      <%= yield %>
    </main>
  </body>
</html>

ユーザー認証されているときのみログアウトボタン表示

認証なしの時のアクセスを許可する

class ProductsController < ApplicationController
  allow_unauthenticated_access only: %i[ index show ]
  # (省略)
end

ユーザー認証されているときだけリンクを表示

# app/views/products/index.html.erb
<%= link_to "New product", new_product_path if authenticated? %>

ユーザー認証されていないときはログインリンク表示

<%= link_to "Login", new_session_path unless authenticated? %>

ユーザー認証されているときだけedit, destroyリンク

# app/views/products/show.html.erb
<h1><%= @product.name %></h1>

<%= link_to "Back", products_path %>
<% if authenticated? %>
  <%= link_to "Edit", edit_product_path(@product) %>
  <%= button_to "Destroy", @product, method: :delete, data: { turbo_confirm: "Are you sure?" } %>
<% end %>

キャッシュ機能を追加する
工事中

Discussion