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

# はじめに
はじめまして！ 

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

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

今回はこのサービスについて少しまとめてみます。
![見出しを追加 (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2517817/601d5f4c-daa6-80fa-1963-97330079d35f.png)

# 追記(2023/11/8)

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

https://twitter.com/_RUNTEQ_/status/1718199358380982344







# 目次

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

# サービス概要


https://current-user.vercel.app


::: note warn
現在メンテナンス中です。
:::


# サービスを作成した背景

スクールに入ってから勉強会を開催したり、初学者同士で勉強を教え合う機会が何度もありました。
その際、`Ruby on Rails`を学習する中で初学者がつまずきやすいポイントがあることがわかりました。
- MVCのModelに関して、どのような役割があるかわからない
- `ActiveRecord`が何かわかっていない = 内部で`O/Rマッパー`の仕組みにより**SQLに変換されていることがわかっていない**
- `User.all`や`Post.find_by`を**暗記のように書いている人が多い**

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

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

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

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

[![Image from Gyazo](https://i.gyazo.com/94827f3d8c32a092b761381b14420c55.gif)](https://gyazo.com/94827f3d8c32a092b761381b14420c55)


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


# 主要機能

| SQL 変換機能                                                                                                                        | コード判定機能                                                                                                                      |
| ----------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| [![Image from Gyazo](https://i.gyazo.com/6811fa702258324ff713273bd3f50119.gif)](https://gyazo.com/6811fa702258324ff713273bd3f50119) | [![Image from Gyazo](https://i.gyazo.com/3bf91e296642baf2aafe6fbf29d18164.gif)](https://gyazo.com/3bf91e296642baf2aafe6fbf29d18164) |
| コードを実行することで、ActiveRecord の SQL への変換と実行結果を確認することができます。                                            | 書いたコードを任意のタイミングで判定することができます。                                                                            |

| 学習記事閲覧機能                                                                                                                    | メソッド検索機能                                                                                                                    |
| ----------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| [![Image from Gyazo](https://i.gyazo.com/0690ccde96bca8a0f564fecb9a167f25.gif)](https://gyazo.com/0690ccde96bca8a0f564fecb9a167f25) | [![Image from Gyazo](https://i.gyazo.com/09215f7156663f09ba24327daf8700a2.gif)](https://gyazo.com/09215f7156663f09ba24327daf8700a2) |
| QiitaAPI を用いて、 学習参考記事を表示しています。                                                                                  | 取得系メソッドをオートコンプリート検索で確認することができます。                                                                    |

| Twitter シェア機能                                                                                                                  | ログイン/ログアウト機能                                                                                                             |
| ----------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| [![Image from Gyazo](https://i.gyazo.com/7f40129ab51ba226d9344a7b7aac0ce7.gif)](https://gyazo.com/7f40129ab51ba226d9344a7b7aac0ce7) | [![Image from Gyazo](https://i.gyazo.com/08890496d42bf718de523db31c4abc8f.gif)](https://gyazo.com/08890496d42bf718de523db31c4abc8f) |
| OGP を設定しています。                                                                                                              | NextAuth.js を採用し、手軽な認証体験を実現しています。                                                                              |

| コード実行時に結果タブがアクティブに遷移                                                                                            | Active Record の説明用モーダル                                                                                                      |
| ----------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| [![Image from Gyazo](https://i.gyazo.com/8a06fa20c7de5f92dd0b818e4cc70b0b.gif)](https://gyazo.com/8a06fa20c7de5f92dd0b818e4cc70b0b) | [![Image from Gyazo](https://i.gyazo.com/dcf5471f0f40a4b58736e490161ae365.gif)](https://gyazo.com/dcf5471f0f40a4b58736e490161ae365) |
| 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](https://i.gyazo.com/21e3ebb19efd67bb5a6cba49d93685b1.png)](https://gyazo.com/21e3ebb19efd67bb5a6cba49d93685b1)

# 選定理由

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

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

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

### フロントエンド
フロント側にはRails7系の`Hotwire`という選択肢もありましたが、
- 本番環境だと動作があまり速くないことを体感した。(個人開発のアプリ作成時) 
- CSSのデザインを１から構築するには時間がなかった、自信がなかった。
- UI/UX、実装スピード、認証セキュリティの面などを考慮し、`Next.js`の`NextAuth.js`を採用したかった。

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

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

### 主要ライブラリ
#### monaco-editor
表示させるエディタには、`monaco-editor`もしくは`codemirror`の選択肢がありました。調査すると、以下の違いがわかりました。
今回はエディタをカスタマイズする事での利点はあまりなかった為、軽量な`monaco-editor`を採用しました。
| パッケージ       | カスタマイズ性                                          |バンドルサイズ |
| :------------- | :-------------------------------------------- |:-------------------------------------------- |
| monaco-editor  |  低い、シンプル|軽い|
| code-mirror | 高い    |重い|



https://www.npmjs.com/package/@monaco-editor/react

https://www.npmjs.com/package/codemirror

#### next-auth


https://next-auth.js.org/

#### react-hot-toast

https://react-hot-toast.com/

#### framer-motion

https://www.framer.com/motion/




# 工夫したポイント



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

https://swr.vercel.app/ja

https://blog.jxck.io/entries/2016-04-16/stale-while-revalidate.html

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

```javascript
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](https://i.gyazo.com/ece0f895b9ccbcfc86daedb425c00875.gif)](https://gyazo.com/ece0f895b9ccbcfc86daedb425c00875)




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

[![Image from Gyazo](https://i.gyazo.com/53bb70bd3ca8a5f58b0b80c7b3f3691c.gif)](https://gyazo.com/53bb70bd3ca8a5f58b0b80c7b3f3691c)

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

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

https://next-auth.js.org/

https://qiita.com/TakahikoKawasaki/items/e37caf50776e00e733be

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

```javascript: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 };

```

```javascript: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` のバージョンを確認する内部クエリなども出力される為、実行時のクエリのみが取得できるようにロジックを組みました。

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

```ruby
  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
```

```ruby
# 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](https://i.gyazo.com/6513e3501a3720d1f6abf8fe51bc61e7.gif)](https://gyazo.com/6513e3501a3720d1f6abf8fe51bc61e7)




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

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

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

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

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

https://qiita.com/keisuke_sakuma/items/57ac6d1a41e537f76180

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

https://www.udemy.com/course/react-complete-guide/

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

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

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

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


https://twitter.com/_RUNTEQ_/status/1706617138021319160


https://battleof.runteq.jp/?utm_campaign=battleofrunteq&utm_content=battleofrunteq_vol4_lp&utm_medium=post&utm_source=twitter