ソニックの部屋

主にプログラミングに関する記事を投稿します

Railsまとめ

気になった用語等をまとめる

第1, 2章_環境準備等

学んだこと

  • N/A

第3章_Rubyを学ぶ

学んだこと

  • Procオブジェクトはブロックをオブジェクト化したもの
proc = Proc.new {puts "hoge"}
proc.call
=> hoge
# Procオブジェクトで渡したブロックはcallメソッドで実行できる
  • yieldはメソッド呼び出しで渡されたブロックをメソッドの中で実行する
def yield_test
  yield(1, 2)
end
puts yield_test { |a, b| a + b}
=> 3
# ブロックをメソッド内の任意のタイミングで実行できる
  • メソッド内ではアクセサがなくてもインスタンス変数の読み書きが可能
  • クラスメソッドはメソッド名にselfを付ける
  • クラス変数は変数名に@@を付ける
  • メソッド内ではselfがなくてもインスタンスメソッドの呼び出しが可能
  • 同じクラスのメソッド内ではプライベートメソッドの呼び出しが可能(selfを付けることは不可)
  • モジュールは色々な呼び出し方がある
  • モジュールは複数のクラスから呼び出したい共通のメソッドをまとめたりする(コードを綺麗にしたい時に有効)
  • 例外で再度beginから始める場合はretryを使う
num=0
begin
  p 10 / num
resucue ZeroDivisionError => e
  p e
  num = 2
  retry
end
puts "終了"
# 無限ループ注意
  • 例外で明示的に例外を発生させる場合はraiseを使う
begin
  raise ZeroDivisionError
resucue => e
  p e
end

第4章_環境構築(Docker)

学んだこと

  • Dockerを使う場合は要参照

第5章_環境構築(Rails7)

学んだこと

  • コンテナ内でrailsコマンドを実行する点に注意
# ①Dockerfileを元にビルド
docker compose build
# ②コンテナ起動
docker compose up
# ③DB作成
# docker compose exec webでwebサービスのコンテナ上を指定
# execコマンドは対象のコンテナが実行中であることが前提
docker compose exec web (bundle exec) rails db:create

第6章_環境構築(Rails5)

学んだこと

  • エラーが発生したため以下の通り解消した
# Dockerfile上で
# ①「rails s時にuninitialized constant Nokogiri::HTML4 (NameError)」にはruby:2.4.5→2.5.0で解消
# ②「docker build時にapt-get updateでPackages Not Found」にはecho~を二つ記載することで解消
FROM ruby:2.5.0
RUN echo "deb http://archive.debian.org/debian/ stretch main" > /etc/apt/sources.list \
    && echo "deb http://archive.debian.org/debian-security stretch/updates main" >> /etc/apt/sources.list \
    && apt-get update -qq && apt-get install -y build-essential nodejs
...

第7章_Railsの基礎

学んだこと

  • 基本理念
    • 同じことは繰り返すな
    • 設定より規約が優先される
  • MVCについて
    • ①ルーティングによりURLとCが紐付けられる
    • ②CはデータをM(DB)から取得する
    • ③CはデータをVに渡してHTMLを生成する
    • M, Cはクラス
    • Vはerbファイル

第8章_初めてのRailsアプリ開発(Rails7)

学んだこと

  • BootstrapはWebのUIを作成するためのフレームワーク
    • ①GemfileでBootstrapを入れる(その後「docker compose build」でイメージの再作成)
    • cssをscssに変更する
    • ③scss内のrequire~は消し「@import "bootstrap";」を記載
    • javascriptのファイルを関連づける「docker compose run web rails importmap:install」を実行
    • ⑤config/importmap.rb内にjsファイルを参照する所定のコードを記載
    • ⑥config/assets.rb内にjsファイルをブラウザに配信するための所定のコードを記載
    • ⑦app/javascript/application.js内にjsファイルを記載する
  • app/views/layouts/application.html.erbで全体のレイアウトを設定
  • ※続きは9章のRails5をカスタマイズしながら進める

第9章_初めてのRailsアプリ開発(Rails5)

学んだこと

  • ※Rails7にカスタマイズしながら進める
  • docker-compose upでサーバ起動後にCtrl+cで停めるとログが残る時があり次回起動できなくなるため以下で削除
rm tmp/pids/server.pid
もしくは
docker-composeファイルに「rm -f tmp/pids/server.pid」を記載
  • gを使わずに一つ一つファイルを作成していくと理解が深まる
  • マイグレーションファイルでDBの設定
docker-compose run web (bundle exec) rails g model board name:string title:string body:text
# docker-compose run web→webサービスのコンテナ内でコマンド実行
# bundle exec rails g model→RailsPJにインストールされたrailsコマンド実行+マイグレーションファイルとモデルファイルの作成
# name:string title:string body:text →boardsテーブルのカラム作成
# モデルは単数、テーブルは複数

# その後テーブルの作成
docker-compose run web (bundle exec) rake db:migrate
  • O/Rマッパーとはテーブルのレコードをプログラムのオブジェクトとして扱う機能
  • Railsにおけるリソースは主にテーブルデータのこと
  • HTTPメソッドとパスの組み合わせでリクエス
  • ルーティングによってコントローラーのアクションに転送される
  • 掲示板新規作成ページの作成(ルーティングの書き方)
get "boards", to: "boards#index"
  • ヘルパーメソッドはビューの中で使用できるメソッド(簡潔に記述できる)
<%= form_for @board do |f| %>
  <div class="form-group">
    <%= f.label :name, "名前" %>
    <%= f.text_field :name, class: "form-control" %>
  </div>
  <div class="form-group">
    <%= f.label :title, "タイトル" %>
    <%= f.text_field :title, class: "form-control" %>
  </div>
  <div class="form-group">
    <%= f.label :body, "本文" %>
    <%= f.text_area :body, class: "form-control", rows: 10 %>
  </div>
  <%= f.submit "保存", class: "btn btn-primary" %>
<% end %>
  • コントローラー内で定義したインスタンス変数はビューで参照可能
def new
  @board = Board.new
end
  • デバッグツールpry-byebugを使用するにはgemでdevelopmentにインストール後attachする必要がある
docker ps # コンテナ名の確認
docker attach コンテナ名
# Ctrl + p + qで抜ける
  • postはコントローラー上paramsで受け取る
  • paramsの中で必要なデータのみ取れるようフィルタ(ストロングパラメーター)をかける
def board_params
    # 必要なデータのみparamsから取り出し
    params.require(:board).permit(:name, :title, :body)
  end
  • その後DBに保存
def create
    # DBへ保存
    Board.create(board_params)
  end
  • 保存したデータの一覧取得・表示
# C:取得
def index
    @boards = Board.all
  end

# V:表示
<tbody>
    <% @boards.each do |board| %>
      <tr>
        <th><%= board.id %></th>
        <td><%= board.title %></td>
        <td><%= board.name %></td>
        <td><%= board.created_at %></td>
        <td><%= board.updated_at %></td>
      </tr>
    <% end %>
  </tbody>
  • 時間表記の修正
# UTC表記を日本時間に直す
# config/application.rbに追記
config.time_zone = "Tokyo"

# 所定のフォーマットは使い回すためにまとめる
# config/initialize/time_formats.rbを作成
Time::DATE_FORMATS[:datetime_jp] = "%Y年 %m月 %d日 %H時 %M分"

# Vに追記
<td><%= board.created_at.to_s(:datetime_jp) %></td>
  • 改行するにはsimple_formatを使う
<p class="card-text"><%= simple_format(@board.body) %></p>
  • ルーティングの設定の確認方法
http://localhost:3000/rails/info/routes
# Helperはpath(URL)を返す
  • リソースベースルーティングによりルーティングの設定を一行で済ませることができる
# ルーティングに追記
resources :boards, only: [:index, :new, :create, :show]
  • 転送
# Vに追記(編集する際はpathだけでなく@でオブジェクトを渡すのがポイント)
<%= link_to "編集", edit_board_path(@board), class: "btn btn-outline-dark" %>

# Cに追記
redirect_to board(=path)
  • パーシャルによるビューの再利用
# Vに_form.html.erbを作成(必ず_から始める)
<%= render partial: "form", locals: { board: @board } %>
  • 削除
# Cに追記
def destroy
  board = Board.find(params[:id])
  board.delete
  
  redirect_to boards_path
end

# Vに追記
# ※Rails7からdeleteの仕様が変わったためjavascript等の設定変更が必要(参考文献②も参照)
<td><%= link_to "削除", board, class: "btn btn-outline-dark", data: { turbo_method: :delete } %></td>
  • コントローラーのフィルタ機能(アクションの実行前後で動作を付ける)
# Cに追記
before_action :set_target_board, only: %i[show edit update destroy]

def set_target_board
  @board = Board.find(params[:id])
end
  • ページネーションとは1ページに全て表示するのでなく複数のページに分割して表示する機能のこと
  • ①db/seeds.rbにサンプルデータを入れて確認する
    • デフォルトでRails.envにはdevelopmentが入っている
  • ②gem "kaminari"をインストールする
  • ③kaminariの設定ファイルを生成する
docker compose exec web bundle exec rails g kaminari:config
  • ④kaminariのビューファイルを生成する
docker compose exec web bundle exec rails g kaminari:views bootstrap4
  • ⑤コントローラーを修正する
  def index
    @boards = Board.page(params[:page])
  end
# デフォルトで1ページ辺り25件のデータを取得
  • ⑥ページリンクを付ける
# Vのindexに追記
<%= paginate @boards %>
  • ⑦ページリンクを日本語にする(任意:application.rb等を修正)
  • ⑧ページリンクを中央寄せにする(任意:Vの_pafinator.html.erbを修正)
  • ⑨ページ数を変更(任意:kaminari_config.rbを修正)
  • フラッシュを使用したメッセージの表示
# Cに追記
  def create
    # DBへ保存
    board = Board.create(board_params)
    flash[:notice] = "「#{board.title}」の掲示板を作成しました" # 一度だけのメッセージ
    redirect_to board
  end

# Vに追記(※createでなくshowとなるがflashは異なるVに値を渡せる)
<% if flash[:notice] %>
  <div class="alert alert-primary"><%= flash[:notice] %></div>
<% end %>

# ※redirectへの設定も可能
def destroy
    @board.delete

    redirect_to boards_path, flash: { notice: "「#{@board.title}」の掲示板が削除されました"}
  end
  • モデルのバリデーション設定
# Mに追記
class Board < ApplicationRecord
  validates :name, presence: true, length: {maximun:10}
  validates :title, presence: true, length: {maximun:30}
  validates :body, presence: true, length: {maximun:1000}
end

# Cに追記
  def create
    # DBへ保存
    board = Board.new(board_params)
    if board.save
      flash[:notice] = "「#{board.title}」の掲示板を作成しました"
      redirect_to board
    else
      redirect_to new_board_path, flash: {
        board: board,
        error_messages: board.errors.full_messages
      }
    end
  end

# Vに追記(new)
<% if flash[:error_messages] %>
  <div class="alert alert-danger">
    <ul>
      <% flash[:error_messages].each do |msg| %>
        <li><%= msg %></li>
      <% end %>
    </ul>
  </div>
<% end %>
  • モデルのアソシエーションとは複数のモデルを関連付ける機能のこと
# まずは便利なgemのインストール→モデルファイルでテーブル構成が分かるようになる(参考文献③も参照)
gem "annotate"
docker-compose exec web bundle exec rails g annotate:install
docker compose exec web bundle exec annotate
  • コメントモデルの実行
# board:reference はboardモデルへの外部キーの作成(borad_id)
docker compose run web bundle exec rails g model comment board:references name:string comment:text

# Mに追記(board)
# has_manyで1:多を設定
class Board < ApplicationRecord
  has_many :comments
  • コメント書き込み機能
# commentsコントローラーの作成
# --skip-template-engineでVの作成をスキップ
docker compose exec web bundle exec rails g controller comments create destroy --skip-template-engine

# routeの追加
resources :comments, only: %i[create destroy]

# Cに追記(boards)
# 掲示板に紐付けたコメントを作成
  def show
    @comment = @board.comments.new
  end
  • コメント書き込みフォームの作成
# Vに追記(show)
<%= render partial: "comment_form", locals: { commnet: @comment } %>

# _comment_form.html.erbの作成
所定の内容をコピー

# comments.scssの設定
所定の内容をコピー
  • コメントの保存
# gem "rails-flog", require: "flog"をインストール(binding_parameterを整形する)

# Cに追記
  def create
    comment = Comment.new(comment_params)
    if comment.save
      flash[:notice] = "コメントを投稿しました"
      redirect_to comment.board
    else
      flash[:comment] = comment
      flash[:error_messages] = comment.errors.full_messages
      redirect_back fallback_location: comment.board
    end
  end

  private

  def comment_params
    params.require(:comment).permit(:board_id, :name, :comment)
  end
  • コメント表示
# Cを修正(board)
  def show
    @comment = Comment.new(board_id: @board.id)
  end

# Vに追記(show)
<div class="p-comment-list">
  <div class="p-comment_listTitle">コメント</div>
  <%= render @board.comments %>
</div>
<%= render partial: "comments/form", locals: { comment: @comment } %>

# _comments.html.erb, comment.scss, _form.html.erbに所定の内容をコピー
  • コメントモデルへのバリデーションとエラーメッセージの設定
# commentモデルにバリデーションを書き、Vにsharedフォルを設け_error_messages.html.erbを作成
<% if flash[:error_messages] %>
  <div class="alert alert-danger">
    <ul>
      <% flash[:error_messages].each do |msg| %>
        <li><%= msg %></li>
      <% end %>
    </ul>
  </div>
<% end %>
  • コメントの削除
# Cに追記
  def destroy
    comment = Comment.find(params[:id])
    comment.delete
    redirect_to comment.board, flash: { notice: "コメントが削除されました"}
  end

# Vに追記(_comment.html.erb)
<span><%= link_to "削除", comment, data: { turbo_method: :delete, turbo_confirm: "削除しても宜しいですか?"} %></span>
  • 多対多の設定
# Mに追記(tag)
# tagとboardsをboard_tag_relationsを経由して関連付ける
class Tag < ApplicationRecord
  has_many :board_tag_relations
  has_many :boards, through: :board_tag_relations
end
  • 掲示板削除でコメントやタグの関連付けも一緒に削除するにはdependent: :delete_allを追記
# Mに追記(board, tag)
class Board < ApplicationRecord
  has_many :comments, dependent: :delete_all
  has_many :board_tag_relations, dependent: :delete_all
  has_many :tags, through: :board_tag_relations

# Cに追記(boards)
# deleteをdestroyメソッドにする必要がある
  def destroy
    @board.destroy
①db/seeds.rbに必要なタグを記載
②docker compose exec web rails db:seed(※rake db:seedでなくても可)
③_form.html.erb, _board.html.erbを修正
  • タグによる掲示板検索
# Vに追記(index)
<div class="ms-auto boards__linkBox">
    <%= form_tag boards_path, method: :get, class: 'boards__searchForm' do %>
      <%= select_tag :tag_id,
                     options_from_collection_for_select(Tag.all, :id, :name, params[:tag_id]),
                     {
                       prompt: 'タグで絞り込み',
                       class: 'form-control boards__select',
                       onchange: 'submit(this.form);'
                     }
      %>
    <% end %>

#Cに追記
  def index
    @boards = params[:tag_id].present? ? Tag.find(params[:tag_id]).boards : Board.all
    @boards = @boards.page(params[:page])
  end
  • ヘッダーメニューの追加とヘルパーメソッドの作成
# Vに作成(_header.html.erb)
# 全ページ共通のためapp/helpers/application_helper.rbにヘルパーメソッドを定義
module ApplicationHelper
  def header_link_item(name, path)
    class_name = 'nav-item'
    class_name << ' active' if current_page?(path)

    content_tag :li, class: class_name do
      link_to name, path, class: 'nav-link'
    end
  end
end

# rootに追記
root "home#index"

# Vに追記(_header.html.erb)
所定の内容をコピー

# Vに追記(全ページ共通application.html.erb)
  <body>
    <%= render "header" %>
    <div class="container">
      <%= yield %>
    </div>
  </body>

第10章_ユーザー認証の実装(Rails5)

学んだこと

  • ※Rails7にカスタマイズしながら進める
  • 1回目のアクセスでは認証情報からユーザーを識別しセッションIDをクッキーに乗せて発行
  • 2回目のアクセスではセッションIDからユーザーを識別する
  • M, V, Cの作成
    • ①gemでbcryptのインストール
    • rails gでユーザーモデルの作成
    • ③db:migrateでテーブルの作成
    • rails gでセッションコントローラーの作成
      • rails g controller sessions create destroy --skip-template-engine
      • Vは不要なため--skip-template-engineを付ける
    • rails gでホームコントローラーの作成
    • rails gでユーザーコントローラーの作成
  • ルーティングの設定
    • post "login", to: "sessions#create"
    • delete "logout", to: "sessions#destroy"
  • ユーザーモデルの認証機能設定
    • ①has_secure_passwordでパスワードの設定
    • ②validatesの設定
    • ③ja.ymlで日本語化
  • 登録フォームの作成
    • ①_header.html.erbでドロップリストの作成
      • ※機能しなかったためbootstrapのサイトの例示を参考に書き換えた(参考文献⑤参照)
    • ②ユーザーコントローラーで@userを新規作成
    • ③_form.html.erbで画面の作成
  • ユーザー登録
    • ①リクエストパラメーターの設定
    • ②createアクションでsession変数の設定
      • ※redirect_to :backはrails7では使用不可(参考文献④参照)
  • ログイン・ログアウト
    • ①home/index.html.erbにrender partialを記載し_login_form.html/erbを読み込む
    • ②sessionsコントローラーにログイン・ログアウト機能を記載
class SessionsController < ApplicationController
  def create
    user = User.find_by(name: params[:session][:name])
    if user && user.authenticate(params[:session][:password])
      session[:user_id] = user.id
      redirect_to mypage_path
    else
      render "home/index"
  end

  def destroy
    session.delete(:user_id)
    redirect_to root_path
  end
end
  • ログインユーザーの取得とマイページの作成
    • ①applicationコントローラーに現在のユーザーの取得
# before_actionにより全てのアクションより前に呼び出される
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  before_action :current_user

  private

  def current_user
    return unless session[:user_id]
    @current_user = User.find_by(id: session[:user_id])
  end
end
  • ②meビューでマイページの作成
<h1>マイページ</h1>
<%= @current_user.name %>

第11章_Railsのバージョンアップ

学んだこと

  • ①Gemfileのrailsのバージョンを変更
  • ②以下により関連するgemも含めてアップデートされる
docker-compose run web bundle update rails
  • ③イメージの再作成
  • ④エラー箇所は設定ファイルを更新
config/initializers/new_framework_defaults.rb
# ※機能の廃止などでエラーとなるためバージョンアップは慎重に行う

第12章_form_withヘルパーによるフォームの作成

学んだこと

  • form_withヘルパーとはform_for, form_tagヘルパーを置き換えるもの
# 修正前
<%= form_for user do |f| %>
# 修正後
<%= form_with model: user do |f| %>

# 修正前
<%= form_for(:session, method: :post, url: login_path) do |f| %>
# 修正後(デフォルトでpostのためpostは省略可)
<%= form_with scope: :session, url: login_path do |f| %>

# 修正前
<%= form_tag boards_path, method: :get, class: 'boards__searchForm' do %>
# 修正後
<%= form_wiht url: boards_path, method: :get, class: 'boards__searchForm' do %>

第13章_RSpecによるテスト

学んだこと

  • ①gem "rspec-rails", "~> 4.0.1" ※4.0.1以上を指定(参考文献⑥参照)
  • ②docker compose run web bundle
  • ③docker compose run web bundle exec rails g rspec:install(rspecの設定ファイルの作成)
  • ④docker compose exec web bundle exec rails g rspec:model User(Userモデルのテストファイル)
  • ⑤docker compose exec web bundle exec rails g rspec:controller Users
  • ユニットテストは特定のオブジェクトやメソッドのテスト
  • テストはテスト用のDBが自動で使われる
  • モデルのテスト
    • docker compose exec web bundle exec rspec ./spec/models/(テストコードの実行)
    • docker compose exec web bundle exec rails g migration AddbirthdayToUser birthday:date
    • docker compose exec web bundle exec rails db:migrate
    • user_spec.rbにテストコードを記載
RSpec.describe User, type: :model do
  describe "#age" do
    before "#age" do
      allow(Time.zone).to receive(:now).and_return(Time.zone.parse("2018/04/01"))
    end

    context "20年前の生年月日の場合" do
      let(:user) { User.new(birthday: Time.zone.now - 20.years) }

      it "年齢が20歳であること" do
        expect(user.age).to eq 20
      end
    end
  • 境界値のテストとモック
    • モックはテスト用にダミーオブジェクトやそれに対してメソッドを呼んだ場合に決まった値を返すもの
  • コントローラーのテスト
    • アクションによって、どういったレスポンスコードが返るのか、どういったテンプレート(V)をレンダリングするのか、どういった変数がテンプレート(V)に渡されるべきなのか、のテスト
    • user_controller_spec.rbにテストコードを記載
  describe "GET #new" do
    before {get :new}

    it "レスポンスコードが200であること" do
      expect(response).to have_http_status(:ok)
    end

    it "newテンプレートをレンダリングすること" do
      expect(response).to render_template :new
    end

    it "新しいuserオブジェクトがビューに渡されること" do
      expect(assigns(:user)).to be_a_new User
    end
  end
end
  • createアクションの正常系のテスト
  • createアクションの異常系のテスト
    • テストはリファラー(前のページの情報)がないので明示的に設定が必要
    • docker compose exec web bundle exec rspec -f d ./spec/controllers/users_controller_spec.rb :45 (45行目以降をテスト)

その他

学んだこと

  • コンテナとイメージを全て削除し始めからやり直す場合
  • ①Dockerfileを元にビルド
    • docker compose build
  • ②コンテナ起動
    • docker compose up
  • ③DB作成
    • docker compose exec webでwebサービスのコンテナ上を指定
    • execコマンドは対象のコンテナが実行中であることが前提
    • docker compose exec web (bundle exec) rails db:create
  • マイグレーションファイルでDBの設定(--skip-collision-checkでエラー回避)
    • docker-compose run web (bundle exec) rails g model board name:string title:string body:text --skip-collision-check
  • ⑤その後テーブルの作成

    • docker-compose run web (bundle exec) rake db:migrate
  • Gemfileにgemを追加した時の対応

  • ①インストール
  • ②ビルド
    • docker compose build

参考文献
Kazuya Kojima, 2023/10/10取得, フルスタックエンジニアが教える 即戦力Railsエンジニア養成講座

①@teriyakisan(Hiroki Tanaka), 2023/10/10取得, docker build時にapt-get updateでPackages Not Found
②アカリバ株式会社, 2023/10/10取得, rails7でlink_toのmethod: :deleteが動作しない場合の対処
③@gen_signup(げん), 2023/10/10取得, Dockerでannotateを起動させる手順
④ひつじもん (id:Hitsuji_mon), 2023/10/10取得, Rails 日記App制作 ~Rails5.2以上でのredirect_backの使い方 | Git~
⑤Bootstrap, 2023/10/10取得, ドロップダウン
⑥@dmrt, 2023/10/10取得, 【解決済】Ruby on Rails 6のrspec-rails実行で「raise WrongScopeError, name is not available from within an example (e.g. an it block) ...」