【個人開発】新郎新婦へ最高の結婚祝いギフトを選べるサービス『Best Gifter』をリリースしました！（Rails×Vue）

いただいたフィードバックをもとに、機能の追加修正を行なっています。
よりユーザーファーストで使いやすく、多くの幸せを届けられるサービスに仕上げていければとの思いです。
機能面等で、アドバイスをいただけるようであれば、ご連絡いただけますと幸いです。

- [作成したアンケートのTwitterシェア機能（2021.5.23追記）](#作成したアンケートのtwitterシェア機能2021523追記)

## はじめに

20代半ばに差し掛かると、結婚する友人が出始めませんか？
巷ではこれを**「第1次結婚ウェーブ」**と呼ぶようで、
最近ではご祝儀に加えて、ギフトを贈る人が多いようです。
ただ、ギフト選びって本当に悩みますよね。
そんな**「第1次結婚ウェーブ」渦中の方の悩みを解決**してくれるサービス **『Best Gifter』**を開発しました

実装中のエラー討伐の道のりは下記のブログにまとめてみました。
同じようなエラーにぶち当たっている方のエラー解決の一助となれば幸いです。
[コードロード｜エラー討伐ブログ](https://nakano-vil.com/code-rode/)

## サービス概要
**アプリのURL：https://www.best-gifter.work**
PC・タブレット・スマートフォンに対応しています。

**GitHubのリポジトリ：https://github.com/naka-no-mura/best_gifter**

![top_pink.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/823516/3d036266-5f87-05dd-b6c8-bb907a6d5d4f.jpeg)
**Best Gifter**
対象：第1次結婚ウェーブ（20代半ば）渦中の方
場面：新郎新婦へ、結婚祝いのギフトを選ぶ時
悩み：

- どんなジャンルが適しているのか
- いくらくらいが常識の範囲内なのか？
- いくつか厳選したけど本当にこれでいいのか？

を1つずつ解消してくれるサービスです。



## このサービスを作った背景

- 仲良くしている友人が24歳で結婚し、結婚祝いを贈ろうと思ったときに、「なにを？」「いくらで？」「これで良いのかな...？」といった疑問があった実体験からです。
- 自分にとっては結婚する友人はたくさんいるかもしれませんが、結婚する友人からしたら人生で一度きりの出来事かと思います。最高の思い出にスパイスを施してあげたい！という想いから開発しました。

## 望む未来

第1次結婚ウェーブ渦中の20代半ばのみなさんが、
新郎新婦へ最高のギフトを贈り、みんなハッピーになれることを祈っています💐

## 使い方

機能は大きく分けると3つあります。

1. キーワード検索
1. ギフトを贈りたい相手との間柄別検索（目玉機能！👀）
1. アンケート投稿/投票（目玉機能！👀）

### 1. キーワード検索

こちらがトップページになります。
キーワードを入力すると検索画面に飛びます。
例えば、「タオル」と入力してみます。
（希望の価格帯が決まっていれば、価格も検索条件に入れることができます）

![スクリーンショット 2021-04-29 18.03.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/823516/feae347a-0d4b-35d3-15ce-35579c53a5d7.png)

すると、結婚祝いに適した「タオル」の関連商品が表示されます。
自分で設けた「タオル」の検索条件以外にも、**裏では「結婚」というキーワードと、ギフト包装「あり」の検索条件をデフォルトで設けています**。

`vue`の`store`から一部抜粋

```javascript
function searchItem(keyword, genreId, minPrice, maxPrice, sort, changePage) {
  return axios.get("/v1/rakuten_apis/search", {
    params: {
      keyword: "結婚" + " " + keyword,
      genreId: genreId,
      minPrice: minPrice || 1000,
      maxPrice: maxPrice || 150000,
      sort: sort || "standard",
      giftFlag: 1,
      imageFlag: 1,
      page: changePage || 1,
    },
  });
}
```

![スクリーンショット 2021-04-29 17.47.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/823516/23ef55f0-fd31-3b07-784a-abc1d4994cce.png)


「並び替え」で検索結果の順番を変更することができます。
![スクリーンショット 2021-04-29 17.52.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/823516/6794c785-8d51-fe7e-6353-41f042923ace.png)

商品をホバーすることで、「楽天市場へ」ボタンと「お気に入り登録」ボタンが出現します。

「お気に入り登録」ボタンをクリックすると、マイページに保存されます。
![スクリーンショット 2021-04-29 17.56.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/823516/50bf5ca5-529b-0700-c08f-1498f481d607.png)

「楽天市場へ」ボタンをクリックすると、楽天市場の商品ページにジャンプします。

### 2. ギフトを贈りたい相手との間柄別検索（目玉機能！👀）

ギフトを贈りたい相手との間柄ごとに検索ができます。
間柄は「友人」「親族」「同僚」「上司」の4パターンを設けています。

「友人」にギフトを贈ることを例にとってみます。

こちらも同じくトップページから検索ができます。

**「友人」であれば、「10,000〜30,000円」で下記のジャンルから選ぶのがおすすめ！**ということが、ひと目で分かるようにしたことがこだわりポイントです。
![スクリーンショット 2021-04-29 18.02.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/823516/8820f60c-6e50-19c7-1ef7-4a352fe32d45.png)

例えば、「インテリア・雑貨」をクリックしてみると、検索ページに飛びます。
検索条件は自動的に「インテリア」「10,000円〜」「〜30,000円」となっています。
もちろん再度、検索条件を設けることも可能です。
![スクリーンショット 2021-04-29 18.12.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/823516/ee591bc3-49ae-6dc2-64a4-784440e8dbcc.png)


### 3. アンケート投稿/投票（目玉機能！👀）

自分でギフトをいくつか選んでみたは良いものの、最後一つに絞れない！というときに使う機能です。流れは下記の通りです。

1. 自分で考えたギフトを３つまで選択肢として設けて、**アンケート投稿**する（**匿名で投稿**）
2. 別の誰かが自分のアンケートに対して、**投票**してくれる（**ワンクリックで匿名で投票**）

**投稿/投票ともに、匿名**となっていることがポイントです。

TwitterやYouTubeで、見ず知らずの人のアンケートでも、**ワンクリックで匿名だから**、と、手軽に投票してあげている人は多いのではないかと思い、その心理をここで利用してみました。

まずは**「投稿画面」**です。
下記のように、ギフトを贈りたい相手の情報を入力し、現状考えているギフトを3つまで選択肢に設けることができます。
![screencapture-best-gifter-work-questionnaire-form-2021-04-29-18_20_38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/823516/800517ce-698f-f352-8f70-8a9863334c42.png)

そしてこちらが**「投票画面」**です。
![screencapture-best-gifter-work-questionnaire-list-2021-04-29-18_26_50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/823516/5aa1188a-3a8d-cd2d-6b2e-43966d0feda2.png)

先程自分で投稿したアンケートが掲載されています。

**同じくギフト選びに迷っている人が、みんなのアンケートを参考にしたい時があるかと思います。
アンケート結果を見るためには、投票することが条件**となっており、閲覧されるごとに投票数が蓄積されていき、重みのあるアンケート結果になっていきます。

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

さらに、**アカウント作るのは面倒だけど、手っ取り早くアンケートだけ見て参考にしたい・・・**という方向けに、トップページからアンケート投票画面へ飛ぶボタンも設けました。

下記のように、ログインしていない状態からでもアンケート投票を行うことができます。

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

### マイページ

お気に入りに登録した商品や、投稿したアンケート結果を確認できます。

- 過去に投稿したアンケートの削除（削除ボタン）
- お気に入り登録の解除（★マークをクリック）

![screencapture-best-gifter-work-mypage-2021-04-29-19_51_09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/823516/e0c69249-0029-0fff-9540-22ff6318e657.png)


## 使用技術

### バックエンド
- Ruby 2.7.2
- Rails 6.0.3.6
- RSpec 3.10.1
- 楽天商品検索API（外部API）

### 機能における主要なGem
- counter_culture 2.7.0（投票数カウント）
- sorcery 0.16.0（ログイン）
- JWT 2.2.2（トークン発行）
- rakuten_web_service 1.13.0（楽天商品検索API）
- httpclient 2.8.3（ネットワーク通信）
- capistrano 3.16.0（自動デプロイ）
- unicorn 6.0.0（アプケーションサーバ）


### フロントエンド
- Vue 2.6.12
- vuex 3.6.0
- Vue Router 3.4.9
- axios 0.21.1
- vee-validate 3.4.5
- vue-poll 0.1.8（アンケートの投稿/投票）


### CSSフレームワーク
- Buefy 0.9.4
- Bulma 0.9.1

### ER図

![スクリーンショット 2021-04-25 20.31.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/823516/71d33700-b8b7-28f8-df92-4269aca695d0.png)

### インフラ構成図
![スクリーンショット 2021-04-25 10.44.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/823516/c63830f8-a6b3-e0a5-9095-38c78e6745d2.png)

## 開発期間
デプロイまでの所要時間は400〜450時間（3.5ヶ月）ほどかかりました。
現職（IT業界とは全く別業界です）を続けながらの開発であったため、
大まかに、平日は朝1.5時間・夜1.5時間、休日に8時間といったスケジュールでした。

## 苦労したところ

たくさんありますが、特に瀕死状態になったエラーと、そこから学んだことを振り返っていきます（時系列順）。

### 1. 楽天APIの叩き方（実装期間：1ヶ月）  
1. そもそもどうやって外部のAPI叩くのか謎
2. 楽天商品検索APIはRailsの[キット](https://webservice.rakuten.co.jp/api/ichibaitemsearch/)が存在するらしく、ググって実装してみた。キットを使った実装方法は、ググればたくさん記事は見つかった。（※注意：キットからだと限られたJSONの階層データしか取得できない！）
3. 実装はできたけど、RailsからどうやってVueに渡すのか謎
4. 最初からVueで叩けばいいじゃんと気づく
5. Vueで楽天商品検索APIを叩く場合の記事は全く見つからなかったので、ググりまくった結果、**VueでHTTP通信を行うにはaxiosを使うのが通例**らしいと知る。なんとかVueで叩くように変更できた。
6. あれ？**Vueで叩こうとするとCSRF問題**にぶち当たるじゃん... CSRFについては[こちら](https://qiita.com/wanko5296/items/142b5b82485b0196a2da#csrf%E3%81%A8%E3%81%AF%E4%BD%95%E3%81%8B)を参考にした。
7. CSRF問題は楽天APIサーバー側の問題だからどうしようもできないのでRails側で叩くしかないと知る。
7. 最終的に、**「Vueでformの内容（検索条件）をRailsに送って、受けとった検索条件をもとにRails側から楽天APIを叩いて、その結果をVueにJSONで返す」**というように実装した。

こんな感じで行ったり来たりして1ヶ月かけてやっと実装できました。
結果どのようなコードになったかは、下記のブログにまとめてみました。
https://nakano-vil.com/code-rode/portfolio_vue_vuex_api_rakuten/

#### 学んだこと
APIを扱うことにおいて、**Vueではaxios**を、**Railsではhttpclient**を扱えるようにすることが大事

### 2. 楽天商品検索APIの商品検索のページネーション（実装期間：2週間）
1. よくあるページネーションは`kaminari`等を使って、`Item.all`等で**一度全てのデータを取り出して、そのうちから1ページに表示させたい件数を調整**し、ページネーションするものだった。
- が、しかし、**楽天APIにおいては一度の通信で引っ張ってこれる件数が30件と決まっていた**ため、今まで学んだ理論ではページネーションが実装できなかった。つまり、普通にAPIを叩いても、**一度のリクエストで「1〜30件まで」の検索結果しか取得できない**。
- 例えば**2ページ目を表示したければ、再度31〜60件目に該当するデータをAPIを叩いて表示させる必要があった**。
- ページネーションの「2」をクリックしたときに「2」をRailsの`params`に渡して、それを元に再度APIを叩きに行く（楽天商品検索APIでは、「2」を送ると「31〜60件まで」のデータを返してくれる）。
- そしてページネーションの表示では、現在のページが「2」になるように認識させて、「前に戻るボタン」と「次に戻るボタン」ではちゃんと「1」ページ、「3」ページに移動させる必要があった。
- 結果、ググリ倒して同じような実装をしている記事を発見し、そちらを参考に実装した。[こちら](https://wood-roots.com/web/vue-js/2677)の記事。

#### 学んだこと
paramsや引数の扱い方。ググリ倒して似たような記事を見つけて、理解しながらカスタマイズする。

### 3. TwitterAPIで投票機能が使えない問題
1. 元々は、機能の目玉としていたユーザーのアンケート機能をTwitterAPIで実装しようとした。
- Webサービス側からTwitterAPIを通して、普通のコメントやSNS共有をTwitterに流す方法はいくらでもググれば見つかった。が、Webサービス上でアンケートを作成し、APIを通してTwitterのアンケート投稿を行う方法が全く分からなかった。
- やっと見つけたのは、`gem 'twitter'`とは全く別のgem ['twitter-ads'というgem](https://github.com/twitterdev/twitter-ruby-ads-sdk)が存在するということ。
- が、しかし、この`gem 'twitter-ads'`は企業アカウントであったり、承認されたアカウントでないと使用できないgemであるらしく、事実上、実装不可なことが分かった。
ちなみに`twitter-ads`で実装したかったのは[この部分](https://developer.twitter.com/en/docs/twitter-ads-api/creatives/api-reference/poll)。

#### 学んだこと

下調べは入念に。

### 4. Vueの'vue-poll'というライブラリでアンケート機能を実装（実装：3週間）
1. TwitterAPIが使えなくなれば、このままだと、ただ商品を検索するだけの機能であり、どこにでもあるアプリ。どうしてもアンケート機能を取り入れたくググりまくった結果、[vue-poll](https://github.com/ppietris/vue-poll)というライブラリを発見。
- が、しかし、これを扱っている記事が全くなく、READMEに記載されているサンプルコードを真似して実装してみても、Vue上で「一時的な投票」ができるのみ。Railsと連携させて、「アンケートの投稿」「投票数をDBに保存」という大事な機能の実装がどうしてもできなかった。
- そこで、READMEだけでなく、そのライブラリがどのように動いているのか？まで自力で調べるために、**ソースコードまで見る**ようにし、なんとかヒントを見つけ実装することができた。

#### 学んだこと
ライブラリは便利！入れるだけで動く！
が、ブラックボックスとなっているため、**ライブラリのソースコードまで確認し、どうやって動いているのか？まで理解**すれば手探りで実装できるようになる。

### 5. 非同期通信でアンケートの選択肢が順番にならない問題（実装期間：1週間）
1. Vueは画面遷移なくスムーズな表示の切り替えができて便利〜くらいの気持ちでいたが、それゆえの非同期通信問題にぶち当たった。「選択肢1」「選択肢2」「選択肢3」を投稿しても、投稿する度に順番が変わってしまう。（「選択肢3」「選択肢1」「選択肢2」の順番になったり）
- ただ、非同期通信に関してはググれば記事が溢れていたのでなんとかなった。
- `promise`や`async/await`を駆使してなんとか順番通りにさせることができた。
- でもよく考えると、カリキュラムのVue編でちゃんと`async/await`扱ってたから、いかに理解せず進めていたのかというのを痛感し、反省した。

#### 学んだこと
高校受験、大学受験、国家試験と、統計を把握してテクニックで解答するのと、答えを丸暗記するのでなんとか試験を乗り切ったので、長年の経験から、それが自分に合った勉強法だと思いこんでいた。
が、プログラミングに関しては違った。みんなが言うように**手を動かして半べそかきながら自走するのが一番身に付く**。人によるかもしれないが、自分の場合はそう感じた。

### 6. AWSにデプロイ（実装期間：2週間）
```
 No space left on device
```

1. 容量が100％になり全くコマンドが打てなくなる問題にぶち当たった。
- そもそもAWSに関しては、「記事を見ながらコピペした」レベルだったので、エラーの際にどこを見ればいいか？のあたりをつけることすらできなかった。
- 結果、ググリ倒して少しずつ点と点を繋げていく作業をしていたところ、RUNTEQのみなさんがアドバイスを下さり助けていただきました。ありがとうございました！

#### 学んだこと
ターミナル上での操作に慣れないといけない。全く分からないからといってコピペは禁物。
対処法は下記のブログで記録しました。
https://nakano-vil.com/code-rode/no_space_left_on_device/

### 7. Capistranoが全然実装できない問題（実装期間：2週間）
1. こちらも正直、記事を見ながら見様見真似でコピペしていたのが仇となった。とりあえず自動デプロイ便利そうだから実装しちゃおっとという軽い気持ちで取り組んではダメ。絶対。
- `unicorn`とはなんなのか、`nginx`とはなんなのかがほんわりしていた状態で取り掛かったので手間取った。
- とはいえ、ここまでで自作アプリを開発して3ヶ月経っている。開発当初よりは`log`の見方や当たりをつけることが出来るようになり、手探りでなんとか実装できた。
- でも結局最後はRUNTEQ校長の[菊本さん](https://twitter.com/hisaju01?ref_src=twsrc%5Egoogle%7Ctwcamp%5Eserp%7Ctwgr%5Eauthor)に助けていただきました。ありがとうございました！

#### 学んだこと
`log`はダイイングメッセージ。`$ tail -f XXXX.log`がめっちゃ便利。



## 工夫したところ

### ロゴ
|濃いめ|中間|明るめ|
|---|---|---|
|![logo_dark_pink.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/823516/6adf056f-c242-e441-f7aa-b5b802ad3a62.jpeg)|![logo_medium_pink.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/823516/daa6f971-1737-d2d8-9bdf-7999c14109f5.jpeg)|![logo_light_pink.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/823516/cdb5ae52-b5ab-8c31-0420-1be739d51933.jpeg)|

インパクトがあり、使い回しやすいロゴを高校の後輩に作ってもらいました。
ロゴはサービスの顔なので、「シンプルで、華やかなものを！」と依頼し、サービスのイメージにぴったりのロゴを作ってくれました。大満足です！

### デザイン

#### Buefy/Bulma
Bootstrapっぽさはあまり好みではないので、ほかにオシャレに仕上がりそうなフレームワークを探し、結果、[Buefy](https://buefy.org/)と[Bulma](https://bulma.io/)で装飾しました。
（※最初、全てBuefyを使っていましたが、Buefyの仕様上、クリックイベント等の挙動が本来のものと変わってしまったので、form部分はBulmaをメインに使用しています。）


#### UI/UXの向上
「ぱっと見で何ができるか分かること」に拘りました。
シンプル且つ機能的にするために、ボタンの配置、ホバー効果、検索フォーム等において、自分がよく利用するサイトを参考にしました。自分がよく利用するということは、自分にとって使い勝手がいいサイトだと思うので、大いに活用させていただきました。
hoverアニメーションや、サイトの色味は日頃からTwitterで心惹かれたツイートを保存しておき、参考にさせていただきました。
Webデザイナーの小林さん[@pulpxstyle](https://twitter.com/pulpxstyle)の図解ツイートが本当におしゃれでマネしたくなるものばかりです！参考にさせていただき、ありがとうございました！

## 今後実装したいこと
実際に使っていただいたユーザーさんからの声を取り入れてブラッシュアップしていきたい所存です！
ご要望がありましたら、本記事、もしくはTwitterのDMにてご連絡いただけますと幸いです🙇‍♂️

4/29時点で、現在、私自身が実装したいと考えていることは下記の通りです。

### N+1問題
アンケートの投票数をDBから取得するときは下記のコードとなっています。
`includes`を使えばいいと思い、一度修正を行ってみましたが上手くいきませんでした。
ですのでデータ数が少ない現状では実装をスキップしています。
ただ、データ数が多くなったときにはDBに負荷がかかるはずなので、このN+1問題を解消する予定です。

```Ruby
  def index

    @answer_counts = Answer.group(:questionnaire_choice_id).count
    render json: @answer_counts
  end
```

### webmockによるAPIのテスト
RSpecでテストをする際、楽天商品検索APIをテストする箇所で、テストが通るときもあれば、エラーになることもあります。
外部APIを叩いているため、毎回レスポンスが違うことや、連続でAPIを叩くため制限がかかってしまうためです。
せっかくCircleCIを用いているので、mockを施す予定です。

## 実際に使用していただいたユーザー様からのフィードバックを元に機能の追加・修正を行なった部分

### 作成したアンケートのTwitterシェア機能（2021.5.23追記）

アンケート機能は面白いと仰っていただけることが多かったのですが、同時に、認知度を広めるためにもSNSへのシェア機能は必須との声も多くいただきました。
そのため、作成したアンケートをTwitterにシェアできる機能を追加しました。

下記のように、「Twitterでシェアする」ボタンを設置。

![スクリーンショット 2021-05-23 16.32.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/823516/32a1be7c-e600-08d3-3249-abea1d942457.png)

クリックすると、アンケートごとの個別ページに飛びます。
URLには`id`パラメータを付与しました。
![スクリーンショット 2021-05-23 16.34.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/823516/04d43f39-35a4-6731-9d05-baea20612565.png)

「シェアする」ボタンをクリックすると、Twitterに投稿できます。
作成した内容は、ツイート画面にも自動で入力されるようにしました。
![スクリーンショット 2021-05-23 16.35.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/823516/aed9d393-bd5a-96c9-5454-bd60b5765c29.png)

![スクリーンショット 2021-05-23 16.37.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/823516/effb58ba-e1df-1e2f-e398-2a7813e757c4.png)

この機能を追加するにあたり、

- OGPの大切さ（metaタグの設定）
- SPAでのパラメータの付与
- axiosでのパラメータの取得（`$route.params.id`）

について学ぶことができました。
この機能が、より重みのあるアンケート結果になるための一助となれば幸いです。

## おわりに

本当に仲の良い友人だからこそ、「適当なギフトは贈りたくない！日常的に使えて、いつまでも幸せな家庭であってほしい！」と悩みまくった結果、思いついたサービスでした。
人を幸せにしたい！という私の想い・個性が反映されたサービスになったのではないかと思っています。
ちなみに当時の私は、丸の内を探し歩き「[女性も振れるIH対応の最強の鉄中華鍋](https://www.wahei.co.jp/products/enzo-tsubamesanjo/20016.html)」を贈りました💐

本サービスを開発中、使ってみたいAPIや、あったら便利だな〜というサービスを思いついたので、早速そちらにも取り掛かりたいです。

以上、ご覧いただきありがとうございました！

## 宣伝

私が所属しているプログラミングスクール**「RUNTEQ」**にて、超有益な記事がアップされました！
これを発見できた方は本当にラッキー過ぎます・・・
これでエモーショナルに語れる愛のこもったサービスを開発できるはずです！
https://blog.runteq.jp/programming-career/portfolio/4287/