ソニックの部屋

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

RailsでECサイトを作る

コード整形

docker compose run --rm web bundle exec rubocop -A
docker compose run --rm web bin/htmlbeautifier

binding.pry

docker attach rails_ec-web-1

DBリセット&seed追加

docker compose run --rm web bundle exec rails db:migrate:reset    
docker compose run --rm web bundle exec rails db:seed

# rake taskの実行
docker compose run --rm web bundle exec rake promotion_code:generate

Herokuへデプロイ

git push heroku dev_checkout:main
heroku pg:reset -a shrouded-dusk-65815
heroku run rails db:migrate
heroku run rails db:seed

# rake taskの実行
heroku rake promotion_code:generate

Herokuで管理者設定

heroku config:set BASIC_AUTH_USER="admin"
heroku config:set BASIC_AUTH_PASSWORD="pw"

Herokuエラー確認方法

heroku logs --tail
heroku run rails c

Herokuアプリ削除※S3等の設定が再度必要になるため注意

1、setteingからapp削除
2、git remote rm heroku

Dockerで管理者設定

# .envファイルに以下追記
BASIC_AUTH_USER=user
BASIC_AUTH_PASSWORD=password
# docker composeファイルに以下追加
version: '3'
services:
  app:
    container_name: "app"
    build:
      context: .
      dockerfile: Dockerfile
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    ports:
      - 3000:3000
    external_links:
      - db
    volumes:
      - .:/app

    # 追記
    env_file:
      - .env

pathの確認

http://localhost:3000/rails/info/routes

delete出来ない時(GETメソッドになる時)

docker compose run --rm web bundle exec rake assets:precompile

参考文献

商品一覧機能実装

docker compose exec web bundle update --bundler 

商品詳細機能実装

商品管理機能実装

http://admin@localhost:3000/login
<%= form_with model: item, url: admin_items_path do |f| %>
# .rubocop.ymlを編集
# Rails/ReversibleMigrationの警告を無効化
Rails/ReversibleMigration:
  Enabled: false

カート機能実装

  • railsでの画像の表示方法
こうではなく
<img class="d-block mx-auto mb-4" src="../assets/images/bootstrap-logo.svg" alt="" width="72" height="57">
こう(アセットヘルパーの使用)
<%= image_tag("bootstrap-logo.svg", alt: "Bootstrap Logo", width: 72, height: 57, class: "d-block mx-auto mb-4") %>
  • カート情報の永続化(オプション): セッションは通常、ブラウザを閉じたり、タブを閉じたりすると破棄されるため、カート情報を永続化するためのデータベーステーブルを設計し、カート内のアイテムを永続的に保存する方法を実装することも考えられます。GPT3.5
  • importmapをインストールするとコンフリクトになる⚫︎
Add Importmap include tags in application layout
File unchanged! Either the supplied flag value not found or the content has already been inserted!  app/views/layouts/application.html.erb
Create application.js module as entrypoint
    conflict  app/javascript/application.js
Overwrite /myapp/app/javascript/application.js? (enter "h" for help) [Ynaqdhm] n
web:
    tty: true
    stdin_open: true
  • Railsのfind_or_create_byメソッドは、Active Recordを使用してデータベースからレコードを検索し、存在しない場合に新しいレコードを作成します。このメソッドは、特定の条件に一致するレコードが既に存在するかをチェックし、存在する場合はそのレコードを返し、存在しない場合は新しいレコードを作成して返します。
  • Fat Controllerを避ける→コントローラーからモデルにロジックを集約する

チェックアウト機能実装

入明細実装

プロモーションコード機能実装

  • Rakeとは、Rubyで書かれたコードをタスクとして作成しておき必要に応じて呼び出し実行する事が出来る機能
  • ロジックはビューではなくなるべくコントローラーに書く

「LangChain完全入門 生成AIアプリケーション開発がはかどる大規模言語モデルの操り方」を読む

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

第1章_ChatGPTとLangChain

学んだこと

  • Chat(対話形式の生成)とComplete(テキストの続きを生成)2つのモデルがある
  • よってプロンプトでChatは対話形式で指示、Completeは文章の途中まで設定することが一般的
  • マルチモーダルとは異なる種類のデータを扱うこと
  • OpenAIのモデルはカスタマイズ不可、Meta等のモデルはカスタマイズ可
  • LangChainは言語モデルを使ったアプリ開発をする際のライブラリ
  • LangChainとChatGPTを組み合わせることで単純なAPIによるアプリ開発よりもより複雑なアプリ開発が可能

第2章_Modle I/O-言語モデルを扱いやすくする

学んだこと

  • 言語モデルを呼び出すプロンプトの書き換えやモデルの差し替えを容易にするためにModle I/Oを使う
  • Modle I/Oは3つのサブモジュールから構成される
    • Language models:モデルの差し替えを容易にしキャッシュや逐次出力ができる
    • Prompts:テンプレートで例えを示し出力を最適化できる
    • Output parsers:出力をカスタマイズしたり、修正したりできる

第3章_Retrieval-未知のデータを扱えるようにする

学んだこと

  • 回答に必要な文章を探す方法が重要
  • テキストのベクトル化により数値で表現し類似度を計算する
  • 類似度の高いものを回答とする
  • RAGによるアプリの作り方
    • 事前準備:DBの作成(テキストのベクトルと文章のペアを保存)
    • 検索とプロンプト構築:「DBの検索」と「取得した文章と質問を組み合わせてプロンプト構築」
  • 以下ライブラリ間でバージョン指定が必要
pip install auth0-python urllib3 kubernetes==28.1.0
  • RAGとはDBから関連する情報を取得し回答を生成する手法
  • ファインチューニングは既存モデルを再学習により専門的なモデルにすることができる
  • RetrievalQAモジュールによりRAGによるQAシステムの開発を簡単かつ多機能にする
    • QAシステムで同じ実装になる処理を省略できる
    • Retrieversを使うことで実装を楽にする
    • 情報源となる文章の組み合わせ方法の選択が可能

第4章_Memory-過去の対話を記憶する

学んだこと

  • 過去の対話はMemoryモジュールに保存する
  • ConversationChainを使ってコードをシンプルにする
  • ※chainlitから回答が返ってこない
  • (参考)同期処理はコードを上から下まで順番に処理すること。非同期処理はある処理が終わるのを待たずに別の処理を実行すること
  • DB(Redis等)に会話履歴を保存し永続化
  • 過去の会話を消去又は要約することでモデル呼び出しの制限に対応する

第5章_Chains-複数の処理をまとめる

学んだこと

  • Chainsモジュールを使うことで複数の処理を簡単にまとめられる(Chains自体もまとめられる)
  • 特定の用途に特化できる(例えば特定のサイトの情報を元に回答を生成する)

第6章_Agents-自律的に外部と干渉する

学んだこと

  • AgentがToolを選択して回答を得る。Agentが回答が適切かチェックをする。
  • Toolを追加してAgentができることを増やす
  • Agentで特定のURLから情報を取得すると文字数制限でエラーとなった →文字数制限の対応が必要
    • 「指示文の文字列」が長文の時→LangChain Chainsにより指示を分割する
    • 「使用データの文字列」が長文の時→4種類の方法あり
名称 使い分け モジュール
RAG コストを抑えたい LangChain Retrieval
Map Reduce 精度を追求したいが、処理速度も優先したい、欲しい情報が使用データの中で散らばっている LangChain Chains
Map Rerank 精度を追求したいが、処理速度も優先したい、欲しい情報が使用データの中で一箇所にある LangChain Chains
Refine 精度を最優先したい LangChain Chains

第7章_Callbacks-様々なイベント発生時に処理をする

学んだこと

  • Callbacksはイベント発生時に特定の処理(ログの出力等)を実行する

参考文献
田村悠 (著)/インプレス/2023/LangChain完全入門 生成AIアプリケーション開発がはかどる大規模言語モデルの操り方
ますみ/2023年11月21日取得/【完全版】文字数制限のないChatGPTを実装するためのアルゴリズムまとめ【Python / LangChain / RAG】

「Python / JavaScriptによるOpen AIプログラミング」を読む

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

第1章_OpenAI APIを開始しよう

学んだこと

  • いかにプロンプトを用意するか、プロンプトデザインが重要

第2章_API利用の基本をマスターしよう

学んだこと

  • プロンプトとはクライアントから受け取るテキストのこと

第3章_プロンプトデザイン

学んだこと

  • プロンプトエンジニアリングとは正しい応答を得るためにプロンプトを構築すること
  • AIは入力したプロンプトに続くテキストを推測しているだけ
  • メタプロンプト(正確な回答を得るための補足部分)が重要
以下を⚪︎⚪︎しなさい。←メタプロンプト(指示)
...対象となる内容...←プライマリコンテンツ
  • 「ID」を割り当てる、「例」を与えることで応答の精度を高めることができる
以下はAIボットとの会話です。←キュー(指示ではない)
AIボットは礼儀正しく丁寧に対応します。←キュー(指示ではない)

人間:こんにちは、あなたは誰ですか。←例
AI:はじめまして、私はAIボットです。あなたをサポートします。←例

人間:東京の観光名所を教えて。
AIボット:←ID割り当て
  • 「コマンド」で複雑な処理を簡単な入力により実行させる
下のテキストをプログラムによるコマンドに変換する。

コマンド:問い合わせ(A、B)
出力:AさんにBの件で連絡する。

以下のコマンドを実行する。

コマンド:←改行しない

第4章_Completionを探求する

学んだこと

  • 様々なオプションにより応答を調整可能
  • presence_penaltyで単語の利用頻度の調整が可能
  • streamでリアルタイムでテキスト生成

第5章_Chat CompletionとTranscription

学んだこと

  • Completion(古い)とChat Completion(新しい)は別のAPI
  • Chat CompletionのAPIコードが古いため最新のGitHubを参照する
# GitHubのUsageより
from openai import OpenAI

client = OpenAI(
    # defaults to os.environ.get("OPENAI_API_KEY")
    api_key="My API Key",
)

chat_completion = client.chat.completions.create(
    messages=[
        {
            "role": "user",
            "content": "Say this is a test",
        }
    ],
    model="gpt-3.5-turbo",
)
  • Chat Completionのプロンプトデザインは、ロールを決め、特にシステムがAIモデルに指示をするsystemで基本的な方針を決めるのが重要
{"role": "system", "content": "あなたは会計の専門家です"}
  • Transcription APIで文字起こしのAPIも可能(オーディオデータからテキスト生成)

第6章_Image Generation

学んだこと

  • Image Generation(DALL_E)でイメージ生成のAPIが可能

第7章_モデルとデータのチューニング

学んだこと

  • ファインチューニングとは独自のAIモデルを作ること

第8章_Power Platform環境からのAPI利用

学んだこと

  • ノンプログラミング環境からのAPI利用

第9章_ノーコード/マクロからの利用

学んだこと

参考文献
Python / JavaScriptによるOpen AIプログラミング
掌田津耶乃 (著)/ラトルズ/2023
2023/11/14取得/GitHub

「ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装①」を読む

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

Numpyまとめ

  • Numpyの配列の作成①(N次元配列)
A = np.array([1, 2], [3, 4])
=> [[1 2], [3 4]]
  • Numpyの配列の作成②
x = np.arange(0, 6, 0.1)
=> [0, 1, ..., 5.9]
  • Numpyによる多次元配列の計算
B = np.array([[5, 6], [7, 8]])
np.dot(A, B)
=> array([[19, 22], [43, 50]])
# 2つの行列で対応する次元の要素数を一致させるのがポイント
  • 次元の数え方
# 一次元配列
# 1 x 2(縦(列)、横(行)の数え方)
[1, 2]
# 二次元配列
# 3 x 2
[[1, 2], 
 [3, 4], 
 [5, 6]]
  • 指数関数の作成
a = np.array([0.3, 2.9, 4.0])
exp_a = np.exp(a) # 指数関数
=> [1.34.., 18.17.., 54.59..]
  • サンプルアップ
np.random.choice(60000, 3)
=> [2, 326, 33927]

ニューラルネットワークの学習の全体図

  • ステップ1:ミニバッチ
    • 訓練データの中からランダムにサンプルアップ
  • ステップ2:勾配の算出(誤差逆伝播法の領域)
    • パラメータに関する損失関数の勾配を求める
  • ステップ3:パラメータの更新
    • パラメータを勾配方向に微少量だけ更新する
  • ステップ4:繰り返す
    • ステップ1、2、3を繰り返す

第1章_Python入門

学んだこと

  • 1次元配列はベクトル、2次元配列は行列と呼ぶ

第2章_パーセプトロン

学んだこと

第3章_ニューラルネットワーク

学んだこと

# xは配列も可
def sigmoid(x):
    return 1 / (1 + np.exp(-x))
  • ステップ関数は0か1、シグモイド関数は0-1を出力する
  • ステップ関数、シグモイド関数は中間層の活性化関数
  • 恒等関数(回帰)、ソフトマックス関数(分類)は出力層の活性化関数
  • 分類問題と回帰問題の考え方
人の画像を用意
→男か女か予測して分類するのは分類問題
→体重kg(数値)を予測するのは回帰問題
  • ソフトマックス関数
a = np.array([0.3, 2.9, 4.0])
exp_a = np.exp(a) # 指数関数
=> [1.34.., 18.17.., 54.59..]
sum_exp_a = np.sum(exp_a) # 指数関数の和
=> 74.12..
y = exp_a / sum_exp_a
=> [0.01.., 0.24.., 0.73] # 総和は必ず1になる(確率と考える)
# オーバーフロー対策がポイント
  • 分類問題の出力層のニューロンの数は分類したいクラスの数にする

第4章_ニューラルネットワークの学習

学んだこと

  • 訓練データで学習、テストデータでモデルの汎化能力を評価
  • 損失関数には二乗和誤差と交差エントロピー誤差がある
# 二乗和誤差
E = 1/2Σ(y-t)**2
---------------------------------
def sum_squared_error(y, t)
    return 0.5 * np.sum((y-t) ** 2)

# 交差エントロピー誤差
E = -Σtlogy
---------------------------------
def cross_entropy_error(y, t)
    delta = 1e-7
    return -np.sum(t * np.log(y + delta))
# deltaはマイナス無限大を発生させないように微小な値を追加している
  • 訓練データからサンプルを選び学習することをミニバッチ学習という
  • 学習は損失関数の値が小さくなるようにパラメータを更新すること
  • パラメータの損失関数に対する微分(勾配)を手がかりにパラメータを更新していく
  • 微分とはある瞬間の変化量を表すのでパラメータを変化させた時の損失関数の変化量を見ていく
考え方
L:損失関数、W:パラメータ
微分ΔL/ΔWの値がマイナスであればパラメータは正の方向へ変化させることで損失関数を減少させることができる
微分ΔL/ΔWの値がプラスであればパラメータは負の方向へ変化させることで損失関数を減少させることができる
  • 勾配法の学習率は1回の学習でどれだけパラメータを更新するかの指標
  • 学習率は大きすぎても小さすぎてもNG
  • 学習率のような人間の手で設定されるパラメータをハイパーパラメータという(重みやバイアスとは異なる)
  • 数値微分より誤差逆伝播法の方が高速に勾配を求めることができる

第5章_誤差逆伝播

学んだこと

  • ノード間を結ぶ直線をエッジという
  • 計算グラフの利点は逆伝播によって微分を効率よく求めることができること
  • 連鎖率とは合成関数の微分であり合成関数の微分は合成関数を構成する各々の関数の微分の積となる
  • 逆伝播が行なっていることは連鎖率、つまり計算グラフの逆伝播が連鎖率により成り立っている
  • 加算ノードの逆伝播は入力信号を次のノードへ出力するだけ
  • 乗算ノードの逆伝播は順伝播の時の入力信号をひっくり返した値を乗じる
  • Sigmoidレイヤの逆伝播は順伝播の出力のみから算定可能
  • ニューラルネットワークの順伝播で行う行列の積をアフィン変換という
  • ソフトマックス関数は入力された値を正規化して出力する(=出力の和が1になるように変形する)
  • ニューラルネットワークの逆伝播ではソフトマックス関数の出力と正解データの差分が前レイヤへ伝わっていく
  • ニューラルネットワークの学習の目的は出力を正解データに近づけるようにパラメータを調整すること
  • よって出力と正解データの誤差を効率良く前レイヤに伝える必要がある
  • 数値微分誤差逆伝播法の検算用に使う
  • レイヤごとにforwardとbackwardを実装し勾配を効率的に求める

第6章_学習に関するテクニック

学んだこと

  • SGD確率的勾配降下法)は伸びた形の関数だと勾配によるパラメータの探索が非効率となる
  • よって他にMomentum, AdaGrad, Adamなどがある
  • 重みの初期値が重要
  • Batch Normalizationによりデータ分布の正規化を行い学習を早く進めることができ初期値に対してロバスト(依存しない)になる
  • 過学習を抑えるためにWeight decay, Dropoutがある
  • ハイパーパラメータの探索は良い値が存在する範囲を徐々に絞り込みながら進める

第7章_CNN(畳み込みニューラルネットワーク→画像を扱う分野で必須)

学んだこと

  • Affineレイヤ(全結合層)は隣接する全てのニューロン間で結合がある
  • 全結合のNNは「Affine-ReLU」の組み合わせが何層も続き、最後に「Affine-Softmax」で最終的な結果を出力する
  • CNNはConvolutionレイヤ(畳み込み層)とPoolingレイヤ(プーリング層)が加わる
  • 「Convolution-ReLU-(Pooling)」の組み合わせが何層も続き、最後に「Affine-ReLU」を通して「Affine-Softmax」で最終的な結果を出力する
  • Affineレイヤの問題点は形状を無視して全ての入力データを1次元(平ら)にする
  • CNNは形状を維持して全ての入力データを同じ次元で次の層に送る(だから画像データ等に適用される)
  • 畳み込み層ではフィルター演算を行う
    • フィルターのパラメータが「重み」に対応する
    • チャンネルが複数ある場合はチャンネルごとに入力データとフィルターの畳み込み演算を行いそれらの結果を加算して一つの出力を得る
    • 入力データとフィルターのチャンネル数は必ず同じにする
    • 出力を複数持たせるにはフィルターを複数にする
  • プーリング層は入力データの縦横方向の空間を小さくする
    • 縦横の最大値を取るMaxプーリングが主流
    • 学習するパラメータなし
    • チャンネル数に変化なし
  • CNNでは各層を流れるデータは4次元(ex. (10, 1, 28, 28)は1チャンネル、高さ28、幅28のデータが10個)
  • im2col関数で入力データを4次元から2次元にして(展開して)フィルターを乗じて最終的に4次元データに直して計算していく
  • CNNは学習によって規則性のあるフィルターに更新される
  • 規則性のあるフィルターによりエッジ(色が変化する境目)やブロブ(局所的に塊のある領域) が抽出される
  • 代表的なCNNにLeNet, AlexNet
CNN用語集 説明
特徴マップ 畳み込み層の入出力データ
フィルター 入力データに乗じるもの。カーネルともいう
パディング 入力データ周囲に固定のデータを入れる。出力サイズを調整するため。大きいと出力サイズが大きくなる
ストライド フィルターを適用する位置の間隔。出力サイズを調整するため。大きいと出力サイズが小さくなる
チャンネル 奥行き

第8章_ディープラーニング

学んだこと

  • ディープラーニングとは層を深くしたニューラルネットワーク
  • 層を深くすることでパラメータを減らせる
  • 層を深くすることで学習データを減らせる
  • 有名なネットワークとしてはVGG, GoogLeNet, ResNet
  • GPU, 分散学習等で高速化
  • 物体検出, 画像生成等で幅広い分野で応用可能

参考文献
ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装
斎藤 康毅 (著)/オライリージャパン/2022

「最短コースでわかる ディープラーニングの数学」を読む

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

第1章_機械学習入門

学んだこと

第2章_微分積分

学んだこと

  • 微分とは「関数のグラフ上のある点を中心にグラフを無限に拡大していくとグラフは限りなく直線に近づく。この時の直線の傾き」(つまり接線の傾き)のこと
  • 微分とは「xを少し増やした時のyの増える量を比で表したもの」(dy/dx)
  • 勾配降下法の根本原理は微分f'(x)(傾き)が0になるxの時に関数は極大又は極小になる
  • 連鎖律とは合成関数の微分のこと
  • 導関数とは微分した結果のこと
  • 積分とは「関数のグラフと直線y=0の間にできる図形の面積」(=積分すると面積が求まる)
  • 面積を表すS(x)の微分が元の関数f(x)になっている

第3章_ベクトル・行列

学んだこと

  • ベクトルは向きと大きさを持つ量(ex. 南西に4km)
  • スカラーは単なる数値(向きを持たない)
  • 内積はベクトル間の掛け算
θ ベクトルaとbの関係 内積の値
0 同方向 最大値
90 直交 0
180 逆向き 最小値
  • ベクトル・行列を線形代数という
  • 行列とベクトルの積
# wの固まりが行列、xの固まりがベクトル
# ポイントは横(行列)✖️縦(ベクトル)の内積の計算
(w11 w12)  (x1) = (w11x1 + w12x2)
(w21 w22) (x2)    (w21x1 + w22x2)
(w31 w32)            (w31x1 + w32x2)

第4章_多変数関数の微分

学んだこと

  • 多変数関数の微分は分解して1つの変数に着目して微分していく、これを偏微分という
  • 勾配降下法で移動量を出す計算は損失関数の偏微分の計算

第5章_指数関数・対数関数

学んだこと

  • 指数関数はf(x)=ax
  • 指数関数の逆関数が対数関数
  • 対数関数はx=logaf(x)
  • 自然体数はeを底とする対数関数
  • eを底とする対数関数を微分すると1/x、eを底とする指数関数を微分するとex

第6章_確率・統計

学んだこと

  • 尤度関数はモデルの特徴を表す変数を式に含んでいる確率式のこと
  • 最尤推定は尤度関数をパラメータで微分してゼロになる時のパラメータを求めること

第7章_線形回帰モデル

学んだこと

  • 入力変数1つが単回帰、2つ以上が重回帰
  • 勾配降下法の式の中で学習率αが非常に重要なパラメータとなる
  • αが大きすぎると正しい方向(谷底)を目指してはいるが一歩が大きすぎて谷底を通り過ぎてしまい何度も行ったり来たりする
  • αが小さすぎると谷底に着くのに何度も回数を繰り返す必要がある
  • 実務では学習率の設定は1/10ずつ小さくしていって微調整していく (0.01-0.001くらいに設定し明確な決め方はない)

第8章_ロジスティック回帰モデル(2値分類)

学んだこと

  • 回帰の直線は処理対象の点を全て直線に近づけるように使う
  • 分類の直線はグループ分けのための境界線(決定境界)として使う
  • 予測関数はシグモイド関数の利用

第9章_ロジスティック回帰モデル(多値分類)

学んだこと

  • アルゴリズムの流れは変わらず、1. 予測関数の作成、2. 評価関数の作成、3. 勾配降下法による最適なパラメータ値探索
  • 並列の分類器の中で確率値の最も高い分類器のクラスを予測値とする
  • 予測関数はソフトマックス関数の利用

第10章_ディープラーニングモデル

学んだこと

  • 予測フェーズは入力層から出力層に向けて順方向に計算処理が進む(順伝播)
  • 学習フェーズは出力層から入力層に向けて逆方向に 誤差計算 が進む(誤差逆伝播)
  • 件数が多い場合は学習データ全体からランダムに一部のデータを取りそのデータで学習する(ミニバッチ学習法)

第11章_実用的なディープラーニング

学んだこと

参考文献
最短コースでわかる ディープラーニングの数学
赤石 雅典 (著)/日経BP/2023

「現場で使える Ruby on Rails 5速習実践ガイド」を読む

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

第1章_RailsのためのRuby入門

学んだこと

  • オブジェクトの機能はクラスで決まる
  • 変数名はスネークケースで書く
  • レシーバとは「メソッドを持つオブジェクト」のこと
  • メソッドの途中で抜けたい場合はreturn、メソッドの最後で返り値を指定する場合は何も使わない
  • オブジェクト毎に異なるデータを保持したい場合はインスタンス変数を用いる
  • オブジェクトの抱えるデータのことを「属性」と呼ぶ
  • オブジェクトはインスタンス変数(データ)とメソッド(振る舞い)を抱える
  • Rubynilとfalseが偽
  • 処理が一行で済むケースは後置ifを使う
puts "hello" if true
  • キーワード引数はメソッド呼び出しをわかりやすくし、かつ、引数の順番を気にせず必要な部分だけを記述可能
def name(full: true, age: true)←キーワード引数にデフォルト引数も設定
...
...
person.name(age: false)←呼び出しがわかりやすく、かつ、必要な引数のみ記述
  • 子クラスのメソッドの中で親クラスの同名メソッドを呼びたい場合は「super」を使う
class BaseTask
  def puts_message
    puts "A"
  end
end

class Task < BaseTask
  def puts_message
    ** super **
    puts "B"
  end
end

>A
>B
  • モジュールは「部分的な設計書」でありクラスという設計書に共通の部分的な設計書を取り込むことで複数の似たクラスを簡単に実現できる
module Chatting
  def chat
    ...
  end
end

class Human
  ** include Chatting **
end

class Dog
  ** include Chatting **
end
  • フレームワーク独特の構文のように見えるものの多くがクラスメソッドとなっている
class Book < ApplicationRecord
  ** has_many :authors **
end
number | | = 10
# numberがnilの時にデフォルトで10を指定
  • %記法を使うと文字数が減り可読性が良くなることがある
ary1 = ["apple", "banana", "orange"]
ary2 = %w(apple banana orange)
# 上記は同じ意味
  • 配列の各要素から特定の属性だけを取り出す
class User
  attr_accessor :name
end

user1 = User.new
user1.name = "Alice"
user2 = User.new
user2.name = "Bob"
users = [user1, user2]

names = users.map(&:name)
# ["Alice", "Bob"]
# 「&:メソッド名のシンボル」により属性の取り出しが可能

第2章_Railsアプリケーションをのぞいてみよう

学んだこと

  • Bundlerによりどのgemのどのバージョンを使うかを指定する
  • Bundler環境ではgemコマンドではなくBundler経由でgemを操作する
  • Railsサーバを起動してアプリにアクセスする際に「PG::ConnectionBad(could not connect to server:・・・)」というエラーが発生したらDBサーバが立ち上がっていないことが原因のため注意
  • MVCの流れ
    • まずはCが呼ばれる (ルーティングを実行)
    • Cにはリクエストに対する処理(アクション)がメソッドで定義されている
    • そのメソッドの中にMを定義する
    • MはDBとオブジェクト間のやり取りを隠蔽し外部から簡単にオブジェクトのデータをCRUDすることを可能にする
    • Vはブラウザに表示する画面を定義
  • ルーティングとはリクエストに対応するレスポンスを作るためにどの処理を実行するかを定義したもの (Cの中で機能する)
  • MVCとはソフトウェアをどのような構造にするかの考え方の一つ
  • MVCはUIに関わる部分(V)とアプリケーションに関わる部分(M)を分けて、VやMを制御するCにより管理しやすくしよう、というのが目的
  • ある機能をアプリに加える際は、まずその機能をどのCのどのメソッドで処理するかを決める
名称 役割
M ・オブジェクトとして実装
・DBへの保存や読み込みも担当
V ・HTTPレスポンスの中身を組み立てる
・CからMのオブジェクトを受け取り画面表示に利用する
C ・クライアントからのリクエストに応じて適切なレスポンスを返す
・MやVを利用してレスポンスを作る

第3章_タスク管理アプリを作ろう

学んだこと

railsコマンド 内容
rails new アプリ名 -d postgresql ・アプリケーションの雛形と作成
・-dでDBを指定
bin/rails db:create DBの作成
bin/rails s サーバの起動
bundle Gemfileに記述した「gem "xxx"」をインストール
bin/rails g model [モデル名] [属性名:データ型] モデルの作成(+マイグレーションファイルの作成)
bin/rails g migration 名前 マイグレーションファイルの作成
bin/rails db:migrate テーブルの作成
bin/rails g controller [コントローラ名] [アクション名] コントローラとビューの作成(+ルーティングの作成)
  • 1つの環境(開発、テスト、本番)に対して1つのDBを対応させる
  • これはconfig/database.ymlで設定する
  • Vを効率良く書くためにSlimを使う
  • CSSを効率良く書くためにSassを使う
  • Railsはアプリ全体で「application.css」を読み込み、他のCSSファイルは「application.css」をさらに読み込む形で記述する
  • Mはクラスとテーブルから構成される
    • クラス名はキャメルケース、テーブル名はスネークケース
    • テーブル名はクラス名の複数形
    • Mの属性はテーブルのカラムに対応
  • M, V, Cはいずれもジェネレータで雛形の作成が可能(rails g ~)
  • アクションの機能
    • アクションからVに受け渡しをしたいデータをインスタンス変数に入れる
    • ブラウザに返す画面をどうするかを記述する (省略した場合はアクション名に対応する画面を表示)
  • リクエストパラメータとはリクエストに添えたデータのこと
  • Cでparamsというメソッドを用いてPOSTかGETに関係なくリクエストパラメータを取得する
  • レンダーとリダイレクト
    • レンダーはアクションの直後にVを表示させる(1つのリクエスト)
    • リダイレクトはアクションの直後に別のURLに案内させる(2つのリクエスト)
  • 何も記述しないとアクションに対応する場所・名前のVファイルをrenderする
  • redirect_toにはFlashメッセージ(次のリクエストにデータを渡す)を渡すことができる
redirect_to tasks_url, notice: "タスクを登録した"
# 「notice: "タスクを登録した"」がFlashメッセージ
  • 共通部分はrenderメソッドのパーシャルテンプレートを使う
V内に、「_form.html.slim」を作る
# パーシャルテンプレートには_を付ける
  • rails5ではJavaScriptをサポートするためのライブラリが異なる
  • つまり、rails5ではrails-ujsを使用していたが、rails7からはtuboとなっている
変更前:
= link_to "削除", task, method: :delete, data: { confirm: "タスク「#{task.name}」を削除します。よろしいですか?" }, class: "btn btn-danger"

変更後:
= link_to "削除", task, data: { turbo_method: :delete, turbo_confirm: "タスク「#{task.name}」を削除します。よろしいですか?" }, class: "btn btn-danger"

第4章_現場の複雑さに対応する(制限、検証、コールバック、フィルタ、ログイン機能追加、関連、検索)

学んだこと

マイグレーション

カラムの「制限」

  • カラムにはデータ型、NOT NULL制約、長さの指定、ユニークインデックスで制限をかける
  • テーブル作成時、作成後に制限をかける

モデルの「検証」

  • saveメソッドでDBの登録・更新を行う前に検証を行い、エラーがあればfalseを返す
  • valid?メソッドで検証処理を単独で行う(DB関係なし)
  • バリデーションのエラーメッセージが表示されない時は参考文献①参照
  • saveなどの登録・更新系のメソッドは自動的にバリデーション機能を持つ

モデルの状態を制御する「コールバック」

  • コールバックは「然るべきタイミングが来たらこの処理を呼んで下さい」
  • モデルオブジェクトの重要なイベント(登録・更新等)の前後に任意の処理を組み込む(コールバック)

ログイン機能

  • セッションとは一つのブラウザから連続して送られている一連のリクエストの間で「状態」を共有できるようにする仕組み
  • Cからsessionメソッドを呼び出すことでセッションにアクセスする
  • クッキーとは複数のリクエストの間で共有したい「状態」をブラウザ側に保存する仕組み
  • セッションの仕組みの一部がクッキーによって実現されている点がポイント(クッキーの中のセッションIDにより状態を維持)
  • ブラウザ側でクッキーを消すとセッションもリセットされる
  • ログイン機能はログインをする=「セッションを作る」と捉えてSessionsControllerという名前でCを作る
  • Cの「フィルタ」でログインしていなければ利用できないようにする
  • 「関連」によりDB上の紐付けを前提にモデルクラス同士の紐付けを定義

データを絞り込む

  • DBデータの絞り込むは3点意識
    • 1、起点
    • 2、絞り込み状況
    • 3、実行部分
User.where(admin:true).fisrt
# 左から1、2、3
# adminカラムにtrueの入っているUserの内最初に見つかったものを取得

scopeを活用する

  • scopeにより繰り返し利用される絞り込み条件(上記2、の部分)をまとめられる

フィルタにより重複を避ける

  • 上記のフィルタを使う

URLをリンクとして表示する

  • gemのrails_autolinkを使う

第5章_テストをはじめよう

学んだこと

  • 自動テストは環境のバージョンアップやリファクタリングの必須条件
  • テストのツールは色々ありRSpecがよく使われる
# RSpecの基本の書き方
describe [テスト対象], type: [テストの種類] do
  context [ある状況] do
    before do
      [事前準備]
    end

    it [仕様] do
      [期待する動作]
    end
  end

end
# テストケースを整理する:describe, context
# テストケースを実行する:before, it
  • 画面に特定の内容が存在することを検査
it { expect(page).to have_content "最初のタスク" }
# 最初のタスクが画面にあることを期待する
# 「have_content "最初のタスク"」部分をマッチャという
  • letで先に処理される外側に共通処理を書き、内側の複数のdescribe/contextごとに細かい違い部分を書く

第6章_Railsの全体像を理解する

学んだこと

  • ルーティングのresources :tasks
一覧:GET /tasks
詳細:GET /tasks/:id
など典型的なCRUD7つのパターンを一括定義
  • 日時はデフォルトではUTC
  • ログレベルは設定可
  • ストロングパラメーターはCでリクエストパラメーターを受け取る際に想定通りのパラメーターかホワイトリスト形式でチェックする
params.require(:task).permit(:name, :description)
  • アセットパイプラインはjs, css, 画像等を効率的に扱う仕組み
  • Sprocketsが使われている

第7章_機能を追加してみよう

学んだこと

登録や編集の実行前に確認画面を表示

  • 意外に手間なので本当に必要か要確認

一覧画面に検索機能を追加する

  • Ransackのインストール
gem "ransack"

# taskモデルに手動で追加
def self.ransackable_attributes(auth_object = nil)
    ["created_at", "description", "id", "name", "updated_at", "user_id"]
  end

メールを送る

  • アクションメイラーの作成
bin/rails g mailer TaskMailer
  • mailchatcherのインストール
# smtpサーバーを立てて送信メールをブラウザ上で確認できる
# Gemfileに書くとエラーとなるためローカルでインストール(参考文献②)
gem install mailcatcher

ファイルアップロード+モデルに添付

  • アクティブストレージの準備
bin/rails active_storage:install
# 2つのテーブル(active_storage_blobs, active_storage_attachments)が作成される
  • active_storage_blobsはファイルをデータベース外で管理する
  • active_storage_attachmentsはactive_storage_blobsと他モデルを関連付ける中間テーブル

CSVファイルの取り扱い

  • config/application.rbに追記
require "csv"
  • M, V, Cの設定が必要

ページネーション

  • kaminariのインストール
gem "kaminari"
  • V, C(index)の設定が必要
  • デフォルトの表示件数は1ページ25件(調整する場合は設定ファイルを作成する)
bin/rails g kaminari:config
  • 見栄えの調整
bin/rails g kaminari:views bootstrap4

非同期処理と定期実行(jobスケ)

brew install redis
# サーバーの起動
redis-server

gem 'sidekiq', '~> 5.0' ※最新のものはエラーになる(参考文献③参照)
gem "listen" ※これがないとエラーになる
# サーバーの起動
bundle exec sidekiq
  • jobの実行はjob雛形を作成し記述する
bin/rails g job sample
  • jobの実行日時の指定も可能

第8章_RailsJavaScript

学んだこと

  • Railsでアプリの基本的な機能を実装し、JSで画面遷移なしの動作を実装する
  • DOMはHTMLをJS等のプログラムから利用するための仕組み
  • イベントハンドラはイベントが発生した時に呼び出される処理
  • Ajaxはブラウザ上で非同期通信を行いページの再読み込みなしにページを更新する
  • 削除はサーバー側で非同期で削除してフロント側で非表示にする
  • Turbolinksはページ遷移を高速化する
  • Turbolinksを利用する時のscriptはhead要素に書く
  • バグやエラーを避けるためにTurbolinksを無効化するのもあり
  • YarnとWebpackerでJSを構造化して大規模アプリ開発をする
  • Yarnはパッケージマネージャー(npmより高速)
# Yarnのインストール
サイトを参考に
bin/以下にyarnファイルを格納する

# パッケージの追加
bin/yarn add パッケージ名

# package.jsonに記述されたパッケージを一括インストール
bin/yarn install
  • WebpackerはライブラリでアセットパイプラインのSprocketsをサポートする
  • WebpackerとSprocketsの併用は管理が煩雑になるので非推奨
  • Webpackerの利用にはYarnのインストールが必須
gem "webpacker"
bin/rails webpacker:install
  • Reactのインストール
bin/yarn add react react-dom

# エラーが起きたのでpackage.jsonファイルのdevDependenciesに以下追加
"devDependencies": {
  "@babel/plugin-proposal-private-property-in-object": "^7.0.0"
}

その後yarn install

第9章_複数人で開発

学んだこと

  • railsアプリを作成すると自動的に.gitignoreファイルが作成され不要なファイルが選別される
  • ローカルで作業ブランチを切って作業をした後にmainにマージする手順は二つ
    • 作業ブランチをmainの最新にrebase
    • mainを作業ブランチにマージ
  • 作業ブランチで(git getch, git rebase 作業ブランチ)git rebase main
  • mainでgit pullの後に作業ブランチでgit merge main
  • push -fで強制的にローカルの内容をリモートに反映させる
  • seedで初期データを登録
# db/seeds.rb
bin/rails db:seed
# バージョンを下げて問題なければ上げる
bin/rails db:migrate:redo

第10章_Railsアプリと長く付き合う

学んだこと

  • Gemifileに記述されたバージョンの範囲内でbundle updateが行われる
  • コントローラーは複雑になりがちなのでモデルに寄せることを考える
  • モジュールを作って複数のモデル・コントローラーからincludeする、その他継承やApplicationRecordへの記述により共通化する
  • ビューはパーシャルテンプレートやカスタムヘルパーで共通化
  • ActiveModel等で整理
  • コードがわかりづらくなるためモジュールの利用しすぎに注意
  • アップデートと複雑性への対応が必須

最後に

良かったところ

  • Railsの基本的な仕組みがわかった

悪かったところ(もしあれば)

  • Railsのバージョンが古く何箇所も修正が必要なところ

難しかったこと

  • 何回もインプットしないと分からない所が多くアウトプットして覚えていく

他参考ブログ記事

Rails基礎

参考文献
現場で使える Ruby on Rails 5速習実践ガイド
大場寧子 (著), 松本拓也 (著), 櫻井達生 (著), 小田井優 (著), 大塚隆弘 (著), 依光奏江 (著), 銭神裕宜 (著), 小芝美由紀 (著)/マイナビ出版/2018

①@P-man_Brown, 2023/10/10取得, 【Rails】Rails7.0でバリデーションのエラーメッセージが表示されない時の解決法
Github, 2023/10/14取得, Mailcatcher crashing #76
③@SyoInoue(井上 翔), 2023/10/15取得, 【Rails】NameError: uninitialized constant Sidekiq::Loggingの解決方法【非同期処理】

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) ...」