https://current-user.vercel.app/
※ レスポンシブ未対応です。Google Chrome での利用を推奨しています。
環境構築不要のRuby on Rails
の O/R マッパーであるActiveRecord
の学習サービス
スクールに入ってから勉強会を開催したり、初学者同士で勉強を教え合う機会が何度もありました。
その際、Ruby on Rails
を学習する中で初学者がつまずきやすいポイントがあることがわかりました。
- MVC の Model に関して、どのような役割があるかわからない
ActiveRecord
が何かわかっていない = 内部でO/Rマッパー
の仕組みによりSQL に変換されていることがわかっていないUser.all
やPost.find_by
を暗記のように書いている人が多い
Rails
には良くも悪くもブラックボックスな部分が多く、ActiveRecord
の理解が浅くてもアプリを作れてしまうという点があります。
そのため、学習が進む中で複数テーブルの結合でレコードを取得する際に、つまずいてしまう状況を多く見かけました。
そのような中で SQL の学習サービスは複数存在しますが、ActiveRecord の学習サービスは現状存在しなかった為、今回開発することに挑戦してみました。
シンプルです! 問題集一覧からチャプターと好きな問題を選択すると、練習ページで問題を解くことができます。
問題 | 問題数 | 概要 |
---|---|---|
トライアル編 | 5 問 | 基本的な操作方法について練習できます。 |
初級編 | 10 問 | 一対多の基本的なリレーションを練習できます。 |
中級編 | 10 問 | 少し複雑なレコードの取得を練習できます。 |
上級編 | 5 問 | 複数テーブルの複雑な条件のレコードの取得を練習できます。 |
SQL 変換機能 | コード判定機能 |
---|---|
コードを実行することで、ActiveRecord の SQL への変換と実行結果を確認することができます。 | 書いたコードを任意のタイミングで判定することができます。 |
学習記事閲覧機能 | メソッド検索機能 |
---|---|
QiitaAPI を用いて、 学習参考記事を表示しています。 | 取得系メソッドをオートコンプリート検索で確認することができます。 |
Twitter シェア機能 | ログイン/ログアウト機能 |
---|---|
OGP を設定しています。 | NextAuth.js を採用し、手軽な認証体験を実現しています。 |
コード実行時に結果タブがアクティブに遷移 | Active Record の説明用モーダル |
---|---|
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 |
API | Qiita API |
その他 | SWR / MUI / monaco-editor / Prettier / ESLint / rack-cors / rspec / rubocop |
開発期間が 3 週間ほどしかありませんでした。その為、①実装スピードと ②ユーザの離脱率を下げるような UI/UX にしたいという 2 点から技術選定を行いました。
環境ごとの差異をなくしたいこと、またDocker
での環境構築に慣れていた為、Docker
/ docker-compose
をベースの技術として選びました。
バックエンドにはカリキュラムで多く学んできたRuby on Rails
を採用する事でキャッチアップコストを最小限にしました。
フロント側には Rails7 系のHotwire
という選択肢もありましたが、
- 本番環境だと動作があまり速くないことを体感した。(個人開発のアプリ作成時)
- CSS のデザインを1から構築するには時間がなかった、自信がなかった。
- UI/UX、実装スピード、認証セキュリティの面などを考慮し、
Next.js
のNextAuth.js
を採用したかった。
以上の 3 点から、あまり触れた事がない技術ではありましたが、全体的な工数を考えたときにNext.js
を採用しました。
デプロイ先であるRender
、Github Actions
等は導入コストが低かったため、Vercel
に関してはNext.js
とのデプロイ時の相性が良いこと、ブランチごとに新しいドメインでデプロイも行ってくれる為、build
時のエラーがわかりやすいことからも今回採用に至りました。
キャッシュ管理ライブラリの 1 つであるSWRを採用し、レコード取得のパフォーマンスを意識しました。SWR は、まずキャッシュからデータを返し(stale)、次にフェッチリクエストを送り(revalidate)、最後に最新のデータを持ってくるというStale While Revalidateの略称です。
axios
やfetch
に比べて処理速度が速く、キャッシュを再利用することによりデータを即時反映できることから、サクサクとしたページ表示を実現する事ができます。問題がトライアル編、初級編、中級編とそれぞれありますが、一度のリクエストで該当の問題群を全て取得してくることで、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
);
導線がわかりやすいようにアイコン
を多めにすること、Tooltip(アイコンhover時に説明の吹き出しが出る仕様)
を配置することで次のアクションを行いやすい設計にしました。
配色も意識し、落ち着いた配色のみを採用する事で視覚的な印象を最小限にする = 利用者を絞らせない、幅広い方に使ってもらえるように意識しました。
今回、UI/UX、セキュリティ面を考慮し、NextAuth.js
でのログイン機能を実装しました。
OAuth
ベースのNext.js
向けに作られたライブラリで、Google
やTwitter
、GitHub
など、認証やセッション管理を手軽に行うことができます。
PagesRouter
向けに作られたドキュメントなので、AppRouter
向けのドキュメントがなく調査に少し苦戦しました。
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 };
'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
ベースで保証されており、ユーザ側としても手軽にログインすることができ、離脱率を下げる上で効果的な選択だと感じました。
当初の設計では、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)
1.管理画面の作成 現状 seed のみでマスタデータを管理しているため、管理画面を作成し円滑に運用していきたいです。
2.ダッシュボード画面の作成 ユーザの滞在率、利用率を上げるためにダッシュボード機能の作成と関連機能の拡張を考えています。
3.テスト テストカバレッジを上げていきたいので、フロント、バック合わせて書いていきたいです。