77
66

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【個人開発】ActiveRecord学習サービスを作ってみました 【Next.js + Rails API + Docker】

Last updated at Posted at 2023-10-06

はじめに

はじめまして!

プログラミングスクールRUNTEQで転職を目指し、学習を始めて4ヶ月が経ちました。

最近、個人開発でRuby on RailsのO/RマッパーであるActiveRecordの学習サービスを作ってみました。

今回はこのサービスについて少しまとめてみます。
見出しを追加 (2).png

追記(2023/11/8)

こちらのWebサービスは、RUNTEQのWebアプリバトルイベントBATTLE OF RUNTEQにて最優秀賞オーディエンス賞を受賞する事ができました!

目次

タイトル 備考  
1 サービス概要 このサービスについて
2 サービスを作成した背景  開発に至った理由について
3 使い方 
4 練習問題について  概要の説明
5 主要機能 
6 使用技術 
7 インフラ構成 
8 選定理由  実装スピードと離脱率の意識
9 工夫したポイント  パフォーマンスとUI/UX
10 今後の開発について 
11 これまで学習した内容 
12 終わりに  結果の報告

サービス概要

現在メンテナンス中です。

サービスを作成した背景

スクールに入ってから勉強会を開催したり、初学者同士で勉強を教え合う機会が何度もありました。
その際、Ruby on Railsを学習する中で初学者がつまずきやすいポイントがあることがわかりました。

  • MVCのModelに関して、どのような役割があるかわからない
  • ActiveRecordが何かわかっていない = 内部でO/Rマッパーの仕組みによりSQLに変換されていることがわかっていない
  • User.allPost.find_by暗記のように書いている人が多い

Railsには良くも悪くもブラックボックスな部分が多く、ActiveRecordの理解が浅くてもアプリを作れてしまうという点があります。

そのため、学習が進む中で複数テーブルの結合でレコードを取得する際に、つまずいてしまう状況を多く見かけました。

そのような中でSQLの学習サービスは複数存在しますが、ActiveRecordの学習サービスは現状存在しなかった為、今回開発することに挑戦してみました。

使い方

シンプルです!
問題集一覧からチャプターと好きな問題を選択すると、練習ページで問題を解くことができます。

Image from Gyazo

練習問題について

問題 問題数 概要
トライアル編 5問 基本的な操作方法について練習できます。
初級編 10問 一対多の基本的なリレーションを練習できます。
中級編 10問 メソッドを用いて少し複雑なレコードの取得を練習できます。
上級編 5問 複数テーブルの複雑なレコードの取得を練習できます。

主要機能

SQL 変換機能 コード判定機能
Image from Gyazo Image from Gyazo
コードを実行することで、ActiveRecord の SQL への変換と実行結果を確認することができます。 書いたコードを任意のタイミングで判定することができます。
学習記事閲覧機能 メソッド検索機能
Image from Gyazo Image from Gyazo
QiitaAPI を用いて、 学習参考記事を表示しています。 取得系メソッドをオートコンプリート検索で確認することができます。
Twitter シェア機能 ログイン/ログアウト機能
Image from Gyazo Image from Gyazo
OGP を設定しています。 NextAuth.js を採用し、手軽な認証体験を実現しています。
コード実行時に結果タブがアクティブに遷移 Active Record の説明用モーダル
Image from Gyazo Image from Gyazo
UX を考慮しての実装です。 ActiveRecord の基礎を説明することで理解しやすくしています。

使用技術

カテゴリ 技術
フロントエンド TypeScript 5.2.2, React 18.2, Next.js 13.4
バックエンド Ruby 3.2.2, Ruby on Rails 7.0.8(API モード)
データベース PostgreSQL
認証 NextAuth.js
環境構築 Docker, docker-compose
CI/CD Github Actions
インフラ Vercel, Render

インフラ構成

Image from Gyazo

選定理由

RUNTEQ内のBATTLE OF RUNTEQというWebアプリ大会の応募に合わせて作成した為、開発期間が3週間ほどしかありませんでした。その為、①実装スピードと②ユーザの離脱率を下げるようなUI/UXにしたいという2点から技術選定を行いました。

開発環境

環境ごとの差異をなくしたいこと、またDockerでの環境構築に慣れていた為、Docker / docker-composeをベースの技術として選びました。

バックエンド

バックエンドにはカリキュラムで多く学んできたRuby on Railsを採用する事でキャッチアップコストを最小限にしました。

フロントエンド

フロント側にはRails7系のHotwireという選択肢もありましたが、

  • 本番環境だと動作があまり速くないことを体感した。(個人開発のアプリ作成時)
  • CSSのデザインを1から構築するには時間がなかった、自信がなかった。
  • UI/UX、実装スピード、認証セキュリティの面などを考慮し、Next.jsNextAuth.jsを採用したかった。

以上の3点から、あまり触れた事がない技術ではありましたが、全体的な工数を考えたときにNext.jsを採用しました。

インフラ

デプロイ先であるRenderGithub Actions等は導入コストが低かったため、Vercelに関してはNext.jsとのデプロイ時の相性が良いこと、ブランチごとに新しいドメインでデプロイも行ってくれる為、build時のエラーがわかりやすいことからも今回採用に至りました。

主要ライブラリ

monaco-editor

表示させるエディタには、monaco-editorもしくはcodemirrorの選択肢がありました。調査すると、以下の違いがわかりました。
今回はエディタをカスタマイズする事での利点はあまりなかった為、軽量なmonaco-editorを採用しました。

パッケージ カスタマイズ性 バンドルサイズ
monaco-editor 低い、シンプル 軽い
code-mirror 高い 重い

next-auth

react-hot-toast

framer-motion

工夫したポイント

1.パフォーマンス

キャッシュ管理ライブラリの1つであるSWRを採用し、レコード取得のパフォーマンスを意識しました。SWR は、まずキャッシュからデータを返し(stale)、次にフェッチリクエストを送り(revalidate)、最後に最新のデータを持ってくるというStale While Revalidateの略称です。

axiosfetchに比べて処理速度が速く、キャッシュを再利用することによりデータを即時反映できることから、サクサクとしたページ表示を実現する事ができます。問題がトライアル編、初級編、中級編とそれぞれありますが、一度のリクエストで該当の問題群を全て取得してくることで、2回目以降は全てキャッシュからデータを表示させるように設計しました。

const fetcher = (url: string) => axios.get(url).then((res) => res.data);
const { data, error } = useSWR(`https://xxxx.xxx.com/api/v1/practices?slug=${slug}`, fetcher);

Image from Gyazo

2.快適なUI/UX

2-1.視覚的な導線

導線がわかりやすいようにアイコンを多めにすること、Tooltip(アイコンhover時に説明の吹き出しが出る仕様)を配置することで次のアクションを行いやすい設計にしました。
配色も意識し、落ち着いた配色のみを採用する事で視覚的な印象を最小限にする = 利用者を絞らせない、幅広い方に使ってもらえるように意識しました。

Image from Gyazo

2-2.手軽で快適な認証

今回、UI/UX、セキュリティ面を考慮し、NextAuth.jsでのログイン機能を実装しました。
OAuthベースのNext.js向けに作られたライブラリで、GoogleTwitterGitHubなど、認証やセッション管理を手軽に行うことができます。

PagesRouter向けに作られたドキュメントなので、AppRouter向けのドキュメントがなく調査に少し苦戦しました。

app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth';
import GithubProvider from 'next-auth/providers/github';

const handler = NextAuth({
	providers: [
		GithubProvider({
			clientId: process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID || '',
			clientSecret: process.env.NEXT_PUBLIC_GITHUB_CLIENT_SECRET || '',
		}),
	],
	secret: process.env.NEXTAUTH_SECRET || '',
});
export { handler as GET, handler as POST };

src/providers/NextAuth.tsx
'use client';

import { SessionProvider } from 'next-auth/react';
import { ReactNode } from 'react';

const NextAuthProvider = ({ children }: { children: ReactNode }) => {
	return <SessionProvider>{children}</SessionProvider>;
};

export default NextAuthProvider;

とはいえ、セキュリティ周りがOAuthベースで保証されており、ユーザ側としても手軽にログインすることができ、離脱率を下げる上で効果的な選択だと感じました。

3.SQL 変換機能について

当初の設計では、to_sql を使用して、実行結果の文字列を返すような考えでした。

実際に調査したところ、戻り値のクラスによって使えない仕様であったため、別の方法を考えました。

ActiveSupport::Nortifications のイベントトリガーを用いて、ActiveRecord の実行されたタイミングで、ログを検知できるようにしています。

とはいえ、綺麗に SQL が吐かれるのではなく、schema のバージョンを確認する内部クエリなども出力される為、実行時のクエリのみが取得できるようにロジックを組みました。

プレースホルダの部分を実際の値で置換したり、即座にクエリが吐かれないメソッドもあるので、配列にすることで、イベントが発火されるようにするなど、かなり泥臭く仮説検証を繰り返し、シンプルに表示させることができました。

  ActiveSupport::Notifications.subscribe "sql.active_record" do |*args|
    event = ActiveSupport::Notifications::Event.new(*args)
    sql = event.payload[:sql]
    binds = event.payload[:binds].map(&:value)

    log_entry = {
      sql: sql,
    }

    unless logs.map { |log| log[:sql] }.include?(log_entry[:sql])
      logs << log_entry
      logger.debug(log_entry.to_json)
    end
  end
# ActiveRecordの内部クエリを無視
  next unless sql.start_with?("SELECT \"") || sql.start_with?("SELECT COUNT")

# プレースホルダを実際の値で置換
  binds.each_with_index do |value, i|
    placeholder = "$#{i + 1}"
    sql = sql.gsub(placeholder, value.to_s)
  end
# クエリを即時発行するように調整
  result = result.to_a if result.is_a?(ActiveRecord::Relation)

Image from Gyazo

今後の開発について

1.問題数を増やす
現状のテーブル構成から作り直しを行い、より実践的な問題を増やしていきたいと考えています。
現状が15問なので、50問近くに増やしたいです。

2.管理画面の作成
現状seedのみでマスタデータを管理しているため、管理画面を作成し円滑に運用していきたいです。

3.ダッシュボード画面の作成
ユーザの滞在率、利用率を上げるためにダッシュボード機能の作成と関連機能の拡張を考えています。

4.テスト
テストがほとんど書けていないので、フロント、バック合わせて書いていきたいです。

これまで学習した内容

Next.jsVercelは軽くキャッチアップしていた程度で、それ以外の技術に関しては、スクール内で学習をしてきました。これまでの学習記録に関しては、こちらの記事を良ければご覧ください。

このWebアプリを作成する前に、Next.jsをUdemy教材で学習しました。
体系的かつわかりやすく、はじめてキャッチアップするにはオススメできる教材でした。

終わりに

自分が作りたかったアプリをひとまず形にできたこと、締切駆動開発で実際のリリースのように緊張感を持って開発できたことは良い経験でした。

おかげさまで、こちらのWebサービスはBATTLE OF RUNTEQの予選を運良く勝ち抜き、決勝に進むことができました!

決勝は10月28日(土)15:00~より開催されます。

今後も自分を含め、初学者をサポートできるように開発を進めていきます!
最後まで読んでいただきありがとうございました!

77
66
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
77
66

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?