【個人開発】ソロ活をお助け！ひとり外食専用投稿アプリを作成しました🍽️【Rails】

# はじめに
こんにちは、なかじ(@nakayama-bird)と申します。
現在、プログラミングスクールに通ってRuby on Railsを学習しております。
今回スクールの卒業制作として、ひとり外食に特化した飲食店の投稿アプリを作成しました。

![ogp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3760414/426caa33-0097-02b6-a57c-10479f3a495b.png)

**リポジトリ**

https://github.com/nakayama-bird/metime-meals

:::note warn
私はプログラミング学習中で、初学者です。
内容に誤りのある場合がございます。
もし間違いがあればご指摘いただけますと幸いです。
:::
## 目次
[1.アプリの紹介](#1-アプリの紹介)
[2.技術構成](#2-技術構成)
[3.こだわった点](#3-こだわった点)
[4.改善したい箇所](#3-改善したい箇所)
[5.参考にした記事](#5-参考にした記事)

# 1. アプリの紹介
### サービスの概要
「MeTime Meals（ミータイムミールズ）」は、「ひとり外食」に特化した飲食店投稿アプリです。一人だからこそ快適な空間で、周りを気にすることなく食事を楽しめる飲食店を探したいという方々が情報を共有することで、ソロ活の強い味方になります。地図上や個性的なタグからご自身のニーズにあったお店を探すことができます。
### このサービスへの思い・作りたい理由
前職が当直のシフト勤務であったため、友人と休みが合わず一人で外食する機会が多々ありました。その際、食べログなどのレビューサイトやGoogleマップのレビューを参考にしながらお店を探すのですが、一人で入店しても大丈夫なお店なのかを調べるのに苦労しました。そこで、同じように一人で外食している方の生の声を集めることで、ひとり外食のお店探しの一助になればいいと思いこのサービスの開発を決意しました。
### 機能紹介（🔑はログインが必要な機能）
|トップ画面 |カード一覧・検索機能|
|:--:|:--:|
|<a href="https://gyazo.com/e7d86e337092d2b4e857b1fdbafe1b64"><img src="https://i.gyazo.com/e7d86e337092d2b4e857b1fdbafe1b64.gif" alt="Image from Gyazo" width="280"/></a>|<a href="https://gyazo.com/778438deab8f2a6e04be565eb37f8a38"><img src="https://i.gyazo.com/778438deab8f2a6e04be565eb37f8a38.gif" alt="Image from Gyazo" width="278"/></a>|
|アプリの使用場面とログイン後に使える機能を紹介しています|リストで投稿の一覧を確認でき、店名・場所・ジャンルのでの検索も可能です|

|地図一覧|地図検索・現在地取得|
|:--:|:--:|
|<a href="https://gyazo.com/0cb6bfd92b5778d9dbf4a8bdb566443c"><img src="https://i.gyazo.com/0cb6bfd92b5778d9dbf4a8bdb566443c.gif" alt="Image from Gyazo" width="276"/></a>|<a href="https://gyazo.com/5bf26dbcc401bdc666312702ea407a88"><img src="https://i.gyazo.com/5bf26dbcc401bdc666312702ea407a88.gif" alt="Image from Gyazo"/></a>|
|地図上で投稿一覧を確認でき、ピンをタップするとモーダルが開きます|現在地や店名・場所・ジャンルでの検索からお店を探せます|

|投稿詳細|投稿共有の動的OGP|
|:--:|:--:|
|<a href="https://gyazo.com/17a9a4cf2be63a13c98090194613ff21"><img src="https://i.gyazo.com/17a9a4cf2be63a13c98090194613ff21.gif" alt="Image from Gyazo" width="500"/></a>|<a href="https://gyazo.com/b21be5cabb491d106510fd7f0408acbb"><img src="https://i.gyazo.com/b21be5cabb491d106510fd7f0408acbb.png" alt="Image from Gyazo"/></a>|
|各投稿の詳細画面では表を使って情報をまとめています|詳細ボタンから各投稿のシェアを行うとその投稿の写真が動的OGPとして表示されます|

|投稿作成🔑|ブックマーク機能🔑|
|:--:|:--:|
|<a href="https://gyazo.com/3df4b956a17396fa7142aeb35b7aa947"><img src="https://i.gyazo.com/3df4b956a17396fa7142aeb35b7aa947.gif" alt="Image from Gyazo"/></a>|<a href="https://gyazo.com/dd6c57af2e9267129c869a6aea296d79"><img src="https://i.gyazo.com/dd6c57af2e9267129c869a6aea296d79.gif" alt="Image from Gyazo"/></a>|
|住所のオートコンプリート、直感的なレーティング、おすすめのタグによってひとり外食の投稿を作成可能です|気になるお店をブックマークして、マイページでまとめて確認できます|
# 2. 技術構成
### 使用技術
| カテゴリー | 使用技術 |
:----|:----
| フロントエンド | Rails 7.1.3.4 / TailwindCSS / DaisyUI / Javascript|
| バックエンド | Rails 7.1.3.4 (Ruby 3.2.3)  |
| インフラ | heroku / AmazonS3 |
| DB | MySQL |
| 開発環境 | Docker |
| CI/CD |GitHub Actions|
| Web API | Google Maps API（Places API / Maps JavaScript API / Geolocation API）/ Cloud Vision API|
### ER図
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3760414/423f9ddf-12ca-c023-562d-8ac18b8235d7.png">

詳細は[こちら](https://dbdiagram.io/d/metime_meals-66e3f2b56dde7f4149ffb45d)

### 画面遷移図
https://www.figma.com/design/MjY3Xjh4caHqLA78iD3zfW/metime_meals_%E7%94%BB%E9%9D%A2%E9%81%B7%E7%A7%BB%E5%9B%B3?node-id=0-1&node-type=canvas&t=zLWW3hSpCvvwGzyq-0

# 3. こだわった点
### アプリのコンセプト・ターゲットとなるユーザーの明確化
今回作成したアプリがCGMのアプリケーションであったことから、**自分のアプリケーションで提供できる価値**が何であるかを明確にしておきたいと考えました。そのために、より多くの方からの意見を集めたいと考え「アプリアイデアの壁打ち」と「外部サイトでのアンケート」を実施しました。

また個人的に類似サービスとの比較・検討も行い、差別化できるよう検討も行ないました。
<details>
<summary>類似サービスとの比較⚖️</summary>

#### 【比較したサービス①】飲食店レビューサイト（[食べログ](https://tabelog.com/)/[ぐるなび](https://www.gnavi.co.jp/)/[Retty](https://retty.me/)）
【差別化ポイント】
- 「一人」などのタグで検索して探すことはできるものの、一人用のお店に特化しているわけではないので、必ずしも正しい情報が集められるわけではない
- 例えば食べログで、[【お一人様OK！】東京でおすすめの居酒屋をご紹介！](https://tabelog.com/tokyo/rstLst/cond04-00-02/izakaya/)を見ると、「一人3品頼めて60分飲み放題」や「串は一人一本にしました」といったように一人で行ったのではない情報も混在しているため探しにくい状況

=>このアプリでは「お一人様外食」に特化しているため、必ず一人で外食したところから投稿を探すことができる
#### 【比較したサービス②】 [ひとり外食専用アプリ「ソロメシ」](https://www.solomeshi.jp/#sec-qa)
【差別化ポイント】
- 一人で確実に入りやすい店を紹介していますが、エリアと店舗が限定されており使えるユーザーが限定的

=>このアプリではエリアは日本全国の「お一人様外食」の投稿を想定しており、さらに幅広い層が利用可能になると考えられる

</details>

#### アプリアイデアの壁打ち
通っているプログラミングスクールの方々にご協力いただき、壁打ちを3回ほど実施しました。この壁打ちの中でひとり外食のお店選びの際は、**お店の空間**が重要な要素になってくるという知見を得ることができました。

- [アプリアイデア壁打ちメモ](https://www.figma.com/board/s7I8T541QtSoToEfV415KK/metime-meals%E3%82%A2%E3%82%A4%E3%83%87%E3%82%A2%E5%87%BA%E3%81%97?node-id=0-1&t=UhIk8y8eSQgQLA13-1)

#### サービスの開発にあたっての事前アンケートの実施
今回のアプリを開発するにあたり一人外食のお店選びの実情及び実装する機能に関して、調査システム：Questantを利用してアンケートを実施しました（回答数:110）。こちらのアンケートを通じてサービス利用のイメージ、ユーザー層、必要な機能を明確にすることができました。
- [アンケート結果](https://questant.jp/s/9R32RGRZTJMP5WAQ)

<details>
<summary>アンケート結果の分析📍</summary>

#### サービス利用のイメージ
- 一人で外食した時のみ投稿できるサービス
- 事前アンケート（Q3）で、一人外食の検索ツールの課題として、29.7%の方が「一人向けの情報が少ない」と回答しているため

#### ユーザー層
- 一人で外食する20~30代女性
- 事前アンケート（Q2）で、一人で外食する際、飲食店レビューサイト（29.7%）やInstagramやXのハッシュタグ（#一人飯、ソロ飯）（28.4%）で検索していると回答した層に向けたアプリケーションをイメージ

#### 地図を使った機能
- 投稿の際、Google Maps APIを利用してお店の場所の情報を保存できるようにする
- 事前アンケート（Q6）で地図上での一覧表示と現在地からお店を探す機能はニーズが高かった

#### 飲食の大まかな金額
- 飲食に使った金額をおおまかに入力できるようにする
- 事前アンケート(Q5)で、一人外食をする際お店選びで重要視する点として価格が手頃という回答をしていたため

</details>


### 投稿内容の項目について
ユーザーの投稿によってコンテンツが充実するアプリであるため投稿を簡単にできるというのは重要です。一方で、ひとり外食に特化しているアプリであるためその意図に沿った投稿をユーザーにしてもらう必要もありました。そこで、投稿内容にレーティングとタグ入力の下におすすめタグの表示を盛り込むことにしました。

#### レーティング
ひとり外食のおすすめ度を分かりやすくするために、レーティング機能を実装しました。
フォームでの入力は簡単に、投稿一覧・地図上のモーダル、地図上のピン、投稿詳細でそれぞれレーティングを表示できるようにしています。詳細画面のレーティングの表示はdecoratorに処理を書いています。
|入力画面 |表示画面|
|:--:|:--:|
|<a href="https://gyazo.com/eb93d06da2f6605c076f06e12540cc29"><img src="https://i.gyazo.com/eb93d06da2f6605c076f06e12540cc29.gif" alt="Image from Gyazo" width="450"/></a>|<a href="https://gyazo.com/a8439b9fd09626b2f351916f2a9c2330"><img src="https://i.gyazo.com/a8439b9fd09626b2f351916f2a9c2330.gif" alt="Image from Gyazo"/></a>|
|フォームで簡単にレーティングが入力できます|星4つ以上の場合地図上でピンに星が表示され、モーダルや詳細画面にも評価が表示されます|
<details>
<summary>コード一部抜粋（スター表示）</summary>

```ruby
#app/decorators/post_decorator.rb
def star_rating
    full_star = '<svg class="w-6 h-6 ml-2 text-secondary" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 22 20">
        <path d="M20.924 7.625a1.523 1.523 0 0 0-1.238-1.044l-5.051-.734-2.259-4.577a1.534 1.534 0 0 0-2.752 0L7.365 5.847l-5.051.734A1.535 1.535 0 0 0 1.463 9.2l3.656 3.563-.863 5.031a1.532 1.532 0 0 0 2.226 1.616L11 17.033l4.518 2.375a1.534 1.534 0 0 0 2.226-1.617l-.863-5.03L20.537 9.2a1.523 1.523 0 0 0 .387-1.575Z"/>
        </svg>'
    empty_star = '<svg class="w-6 h-6 ml-2 text-gray-300" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 22 20">
            <path d="M20.924 7.625a1.523 1.523 0 0 0-1.238-1.044l-5.051-.734-2.259-4.577a1.534 1.534 0 0 0-2.752 0L7.365 5.847l-5.051.734A1.535 1.535 0 0 0 1.463 9.2l3.656 3.563-.863 5.031a1.532 1.532 0 0 0 2.226 1.616L11 17.033l4.518 2.375a1.534 1.534 0 0 0 2.226-1.617l-.863-5.03L20.537 9.2a1.523 1.523 0 0 0 .387-1.575Z"/>
        </svg>'

    # full_starを評価数分繰り返して表示。enumが0からなので1プラス。
    stars = full_star * object.rating_before_type_cast

    # 残りの星はempty_starで補う。
    stars += empty_star * (4 - object.rating_before_type_cast)

    stars.html_safe
  end
```
</details>

#### おすすめタグの表示
タグの入力フォームの下にひとり外食の際、嬉しいポイントをおすすめタグをして表示しています。またそのタグをタップするとフォームにカンマとともに入力されるようにJavascriptで実装しました。
|入力画面 |
|:--:|
|<a href="https://gyazo.com/36a3678317c700069da2e613466d6465"><img src="https://i.gyazo.com/36a3678317c700069da2e613466d6465.gif" alt="Image from Gyazo" width="278"/></a>|
<details>
<summary>コード一部抜粋（レーティング）</summary>

```ruby
#app/views/posts/_form.html.erb
<div class="flex justify-center flex-wrap">
      <%= link_to '券売機', '#', class: 'tag-link badge badge-ghost badge-lg mr-4 mt-4 mb-3' %>
      <%= link_to 'カウンター', '#', class: 'tag-link badge badge-ghost badge-lg mr-4 mt-4 mb-3' %>
      <%= link_to 'スマホ注文', '#', class: 'tag-link badge badge-ghost badge-lg mr-4 mt-4 mb-3' %>
      <%= link_to 'タブレット注文', '#', class: 'tag-link badge badge-ghost badge-lg mr-4 mt-4 mb-3' %>
      <%= link_to 'ひとり用メニュー', '#', class: 'tag-link badge badge-ghost badge-lg mr-4 mt-4 mb-3' %>
      <%= link_to '静か', '#', class: 'tag-link badge badge-ghost badge-lg mr-4 mt-4 mb-3' %>
      <%= link_to '広々', '#', class: 'tag-link badge badge-ghost badge-lg mr-4 mt-4 mb-3' %>
</div>
```
```javascript
// app/javascript/tags.js
document.addEventListener('turbo:load', () => {

    // テキストボックスを取得
    const tagInput = document.querySelector('input[name="post[tag_names]"]');
  
    // タグをクリックした時の処理
    const tagLinks = document.querySelectorAll('.tag-link');
    tagLinks.forEach(link => {
      link.addEventListener('click', (e) => {
        // リンクのデフォルト動作を無効化
        e.preventDefault();
        // テキストボックスの内容にタグを追加
        const clickedTag = e.target.innerText.trim();
        if (tagInput.value) {
          tagInput.value = `${tagInput.value},${clickedTag}`;
        } else {
          tagInput.value = `${clickedTag}`;
        }
      });
    });
  
  });
```
</details>

### ユーザビリティ
使いやすいアプリを目指してユーザビリティを意識したUI/UXの設計を意識しました。デザインに関する学習はこちらにまとめております。

https://zenn.dev/nakayama_bird/scraps/c7e529c8cf60b9

特にスマートフォンでの使用を想定していたため、モバイルファーストのデザインを意識しました。[タッチターゲット](https://zenn.dev/link/comments/2133077a746e7c)を意識した実装やボトムナビゲーションによってスマートフォンで使いやすいようなデザインにしました。
|タッチターゲット|ボトムナビゲーション|
|:--:|:--:|
|[![Image from Gyazo](https://i.gyazo.com/30a536fd13c4fc94c9e5c1e9505cc085.png)](https://gyazo.com/30a536fd13c4fc94c9e5c1e9505cc085)|<a href="https://gyazo.com/5bf84b75a98d8092e996130a425e2ee0"><img src="https://i.gyazo.com/5bf84b75a98d8092e996130a425e2ee0.gif" alt="Image from Gyazo" width="276"/></a>|

# 4. 改善したい箇所
今後下記の点を改善していきたいと思います。
- テストコード
  - 現状rubocopの導入にとどまっています
  - RSpecを順次追加していきバグやエラーを早期発見できるようにしたいです
- リファクタリング
    - 特にJavascriptに関して、現状erbファイルにscriptを書いている箇所が多いのでリファクタリングを進めたいです
# 5. 参考にした記事・使用したイラスト
### Google Maps API関連
https://qiita.com/lemonade_37/items/51ca0f18fd9da842bb00

https://qiita.com/lemonade_37/items/0cf88a654b18a73b16f5

https://zenn.dev/kame0707/articles/99af5c13c48ddd
### Cloud Vision API
https://qiita.com/kumaryoya/items/7d2ee56a10172b7f0b29
### Google認証
https://zenn.dev/yoiyoicho/articles/c44a80e4bb4515#google-api%E3%81%AE%E8%A8%AD%E5%AE%9A%E3%82%92%E8%A1%8C%E3%81%86

https://qiita.com/okada84108983/items/0c84c01b88072c004873

### 画像投稿関連

https://qiita.com/MIDO-ruby7/items/9ec83b4da4e397075865
### フラッシュメッセージ
https://qiita.com/super-appy/items/0465b716e682a453caf8
### ローディングアニメーション
https://zenn.dev/catnose99/articles/19a05103ab9ec7
### Googleアナリティクス
https://www.creal.co.jp/column/analytics/1917/
### イラスト
https://loosedrawing.com/
# おわりに
今回のアプリ作成にあたりお力添えしてくださった全ての方々に心よりお礼申し上げます。ありがとうございました！
何とか完成まで辿り着くことができ、とても嬉しい気持ちです。
今後個人開発される方の参考になりましたら幸いです！
ここまで読んでいただきありがとうございました！
- Xもやっておりますのでよろしくお願いします！

https://x.com/nakayama_tori
- 今回の開発に関連した記事・メモ

https://qiita.com/nakayama-bird/items/9bf459ed06abd341f7d3

https://zenn.dev/nakayama_bird/scraps/9ce57e53281dfe

https://zenn.dev/nakayama_bird/scraps/6532df62e1e375

https://www.miniita.com/articles/142