【Ruby+Sinatra】ゴミの収集日と天気予報がLINEで確認できるアプリを制作しました

### 1.はじめに

閲覧していただき、ありがとうございます。
フロントエンドは主にBootstrap、バックエンドはRuby（フレームワーク：Sinatra）で作成しました。
この記事では、アプリ開発にあたって苦労した点や、
各機能実装の際に参考にした記事についてもご紹介していければと思います。

### 2.アプリのURL

- サイトトップページ：https://gomiten-bot.herokuapp.com (※現在は停止しております)
- LINEの友達追加用のQRコード
- LINEの画面からQRコードを読み込み、友達追加をすると操作ができます。
<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689555/777a20cf-3190-9a33-324a-a4013d8ea26d.png">

- アプリのアイコン
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689555/18088de6-5c35-66e0-2896-713ccbefdd92.png">

### 3.アプリの概要

サービスの概要は、LINEをプラットフォームにしたゴミの収集日と天気予報を確認できるアプリです。
**ゴミ**と**天気**を掛け合わせ、**『gomiten（ゴミテン）』** と名付けました。

※サイトトップページ（デプロイ先）のURLでは、アプリの使い方や特徴を紹介しています。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689555/b2c69668-4b90-0e2c-5639-c2c76c44a062.gif">


想定しているユーザーとしては、ゴミの収集日がなかなか覚えられない人や朝に天気予報を確認する人を想定しています。

### 4.制作背景

私自身、一人暮らしをしているため毎朝ゴミを出して出勤するのですが、
**「今日不燃ゴミだったか...出したかったんだけど...」** と思うことがありました。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689555/792e6cb4-1710-31e1-f56e-df153a47bfcf.png">
月に1回の収集日の場合は、来月に持ち越しになります。

また、我が家にはテレビがなく毎朝携帯電話で天気を確認していました。
**「調べたら良いんだけど、朝は忙しい。LINEで通知が決まった時間に飛んできたら便利だよなぁ」** と思ったため制作しました。

アプリを使用することで解決したい課題は2点です。
①ゴミの出し忘れを防ぐこと
②天気予報を見ずに外出し、傘やタオルを購入したり、 自転車で雨に降られ
体調不良になることへの解決

### 5.機能一覧と使用画面のイメージ
#### ①リッチメニューとテキストから、全国の天気予報が取得可能
- 位置情報を送信すると、地方が設定されます。(デフォルト：愛知県西部地方）
- 今日・明日・明後日の3日間の情報取得が可能です。
- 下記の動画では、位置情報を送信して、リッチメニューの`今日の天気→明日の天気→明後日の天気`を順に操作しています。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689555/2327303b-194c-6035-31a6-a8b6bc7f1a0c.gif">

##### 工夫点
①LINEの公式ドキュメントを読み、リッチメニューを実装しました。

②雪の天気の時は傘が必要であることを考慮して、スタンプが送信されるよう実装しました。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689555/626291b5-b894-157c-b256-8ed332a2b3b8.png">

③天気・気温・6時間ごとの降水確率が取得できるAPIを使用しました。

気象庁が発表している天気予報情報を、XML形式にしたデータを使わせていただきました。（https://www.drk7.jp/weather/）

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689555/ededf04a-6867-1c6a-eef1-84162f19c7f5.png">

#### ②毎朝7時に天気予報がLINEの通知で届く
**「LINEで通知が決まった時間に飛んできたら便利だよなぁ」** という欲求の解決のため、実装しました。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689555/e18bc115-5ec9-dae4-fc5e-3d59921d6b43.png">

##### 工夫点
①httpリクエスト時にセキュリティ(basic認証)を設定し、外部の不正な情報の侵入を防ぐ対策をしました。

webページにアクセスしようとすると、ユーザー名とパスワードの入力が求められます。
※staging環境のURLとなっていますが、本番環境も同様の表示がされます。
<img width="550" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689555/ed7a90d1-0c47-d709-19dd-a762d83da22c.png">

basic認証のコードは、sinatraの「[公式ドキュメント:よくある質問](http://sinatrarb.com/faq.html#escape_html)」を参考にしました。

```app.rb
helpers do
  def protected!
    return if authorized?
    headers["WWW-Authenticate"] = 'Basic realm="Restricted Area"'
    halt 401, "Not authorized\n"
  end

  def authorized?
    @auth ||= Rack::Auth::Basic::Request.new(request.env)
    @auth.provided? and @auth.basic? and @auth.credentials and @auth.credentials ==[ENV["BASIC_AUTH_USERNAME"], ENV["BASIC_AUTH_PASSWORD"]]
  end
end
```
②cron-jobを採用し、決まった時間に送信ができるようにしました。（heroku schedulerは数分の誤差が発生）

7時に`/send`アクションが実行されるように設定しています。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689555/6498c298-ecf8-9104-93e4-76d2a847cc95.png">

```app.rb
get '/send' do 
  protected! #basic認証
  weather_info_conn = WeatherInfoConnector.new
  begin
  $db.get_all_notifications.each do |row|
    set_day = 0 # weatherapiは朝6時に更新 0は当日
    forecast = weather_info_conn.get_weatherinfo(row["pref"], row["area"], row["url"].sub(/http/, "https"), row["xpath"], set_day)
    puts forecast
    message = { type: "text", text: forecast }
    p "push message"

    case forecast
    when /.*(雨|雪).*/ # 天気が雨または雪のときはスタンプも送信
      message_sticker = {"type": "sticker", "packageId": "446", "stickerId": "1994"}
      messages = [message, message_sticker] 
        
      p client.push_message(row["user_id"], messages)
    else
      p client.push_message(row["user_id"], message)
    end
  end
  rescue => e
    p e
  end
  puts "done."
end
```
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689555/40c3118b-7e1f-0ba9-8d68-de6394c7e0e8.png">


#### ③テキストから、翌日のゴミの収集日が取得可能
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689555/dbd43fff-a65c-2723-429b-2df5e36e8b1e.png">

- 数字を入力して送信すると、翌日のゴミの収集日が返信されます。
- 現在、愛知県名古屋市の一部の地区に対応しています。
- 対象地域：名古屋市西区数奇屋、名古屋市西区砂原町、名古屋市西区浅間一丁目、名古屋市西区浅間二丁目

##### 工夫点
①隔週の収集日にも対応できるよう、日付・曜日・週のデータを使用して実装しました。

②ひらがなと漢字のどちらを入力しても、返信が来るようにしています。

（例）名古屋市西区数奇屋　→ 数奇屋 または　すきや

### 6.使用技術
- 言語：Ruby(2.7.3)
  - フレームワーク：Sinatra(2.1.0)
  - フロントエンド：HTML&CSS/Bootstrap
  - DB：PostgreSQL
  - インフラ：Heroku(ステージング環境→本番環境)
  - ソースコード管理：GitHub(Projectsのカンバン方式でタスクを管理)
  - その他(使用ツール、素材など)
  - LINEMessagingAPI
  - ngrok(2.3.40)(ローカルでの動作確認)
  - Japan Weather Forecast(天気情報の取得)
  - cron-job.org(httpリクエストを定時実行→LINEに通知)
  - FontAwesome
  - Visual Studio
  - draw.io
  - unDraw(イラスト素材)
  - material design palette(カラーパレットツール)
  - canva(リッチメニューデザイン)
  - ロゴメーカー/STORES(ロゴ)
  - ICOOON MONO(アイコン)

#### Sinatraを選んだ理由
Ruby on Railsを使用した場合でもLINEbotの作成は可能ですが、主に下記の2点の理由から
フレームワークにSinatraを選びました。

- CRUDの処理が必要なく、LINEとの連携が目的であるため
- 軽量であり、開発速度を上げるため

### 7.ER図
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689555/0a4931c7-f49f-439b-0825-02b6ffd00411.png">

### 8.工夫したところ
#### ①チーム開発を意識
実務でのチーム開発を想定した開発を行いました。

① Git、GitHubを用いたソース管理

② Projectsのカンバン方式でタスクを管理

③ ブランチ運用は、ブランチの運用のミスを軽減できる点や複数人でのチーム開発を想定してGitflowを採用

④ こまめにcommitし、プルリクエストを出しマージする流れで開発

| ブランチ用 | 目的 | 備考 |
| :--- | :--- | :--- |
| main| 本番用 | 本番リリース用のブランチ |
| develop | 開発用 | 機能実装用のブランチはここから切る |
| feature | 機能実装用 | 派生元はdevelopブランチ |

#### ②UI/UX
##### (1)サイトトップページ
- サイトトップページは、元気や明るさをイメージしてオレンジをメインカラーに設定しています。
- 配色は下記を参考に行い、統一感を出すように工夫しました。
<img width="450" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689555/d1bdb2c8-23ef-a04d-983d-33fdbbf4a72b.png">

##### (2)リッチメニュー
- 太陽をイメージする赤をメインカラーに設定しています。
<img width="450" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689555/8a7ca399-a4c7-2504-4a7c-472ea70e51d5.png">

#### ③インプットしながらアウトプット
フレームワークはSinatraを選択しました。アプリ制作を行うまで学習したことがなかったので、
公式ドキュメントを中心に、ヘルパーメソッドやbasic認証機能などを学習しながらアプリ制作を行いました。

### 9.苦労したこと

① ゴミの収集日のデータ投入

当初は位置情報を送信すると、ゴミの収集日の地域選定も合わせて行えるよう検討していました。
LINEで位置情報を送信すると、下記のような情報が取得できます。

```json
{
    "type": "location",
    "title": "my location",
    "address": "〒160-0004 東京都新宿区四谷一丁目6番1号", 
    "latitude": 35.687574,
    "longitude": 139.72922
}
```
郵便番号または地域を抜き出して位置情報を設定できたらと考えましたが、
下記の理由から**「テキストを送信すると、返信が返ってくる」**現在の形に実装しました。

- 郵便番号：県内や県またぎで重複がある
- 地域：「◯○より東/西」のような建物を境界として収集日が区分けされている地域があり、詳細な取得が難しい
- データが膨大であること:国土交通省が提供するcsv形式の全国のデータを投入したところ、HerokuのDB数の上限である一万を超えてしまった

現在4地域のデータを直接入れている状況ですが、より多くの地域に対応できるよう対策をしていきます。

② データベース

ActiveRecordの導入も検討しましたが、最終的には導入せずSQL文を記述しました。
直接記述することで実装と結果の繋がりの理解が深まり、勉強になりました。

③ 情報収集

「Ruby ◯◯」で検索するとRuby on Railsに関連する内容が多く、Ruby単体の実装に関する方法を探すのがなかなか大変でした。
また、Sinatraに関する内容は日時が古いものもあり、検討しながら取り入れていきました。

### 10.開発で意識したこと
#### ①エラー解決について
時間を決めて集中して取り組みました。
また、`binding pry`を活用する、putsで変数を表示させてみるといったことを行い、
「どこまで処理が行うことができているのか」という原因の切り分けを行いました。

#### ②質問について
自分で調べても解決できない際は、ビジネスチャットツールであるslackにて質問を行っていました。
下記のことを意識して詳細を伝え、相手の時間を奪わないよう努めました。

- やりたいこと
- 試したこと
- どこで詰まっているか


### 11.今後の課題

- ~~ゴミの収集日のDB設計の見直し（SQLで管理する）~~ ※対応済
- FlexMessageの導入（ゴミの収集日に関しては文字を入力する必要があることから、タップのみで完結できるようにする）

FlexMessageとは、下記のような横にスクロールできてボタンを設置できるメッセージのことです。
<img width="450" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689555/df1e0064-39cd-fb31-77b2-b7fd71ac20da.png">

（参考:[Flex Messageの要素](https://developers.line.biz/ja/docs/messaging-api/flex-message-elements/)）

- 通知の切り替え（現在は友達追加すると7時に通知が届く仕様のため、ユーザー側で切り替えができるようにする）
- ゴミの収集日の通知
- 対応地域の拡大

### 12.感想
作成はとても大変でしたが、「どういうものを作りたいのか？」を掘り下げて考えることでより具体的になり
完成まで意欲的に継続することができました。
エラーに何度もぶつかり苦しんだ時期もありましたが、解消できた時の喜びはとても大きく、
周りの方に協力していただきながらデプロイまで行うことができました。

引き続き、積極的にアウトプットを行なっていきたいと思います。

### 13.最後に
記事の感想や意見、ご指摘等あれば伝えていただけるとありがたいです。
長くなりましたが、読んでいただき、ありがとうございました。