うさぎを飼ってWebアプリをつくった話

# 概要
* うさぎ飼い始めました
* その中で出てきたニーズをWebアプリにしました

## コード
* https://github.com/junara/usareco
* `https://usareco.herokuapp.com` で活用しています。しかし、登録されたユーザー（自分）以外だとエラーになりますのでリンクにしていません。

## うさぎのペレット量の計算は面倒くさい
* 体重 * XX %で決まる
* ＸＸは体重と月齢の関数で算出
* 毎日計算するの面倒くさい

## やりたいこと
* 体重量ったらペレット量がすぐ知りたい

## 作るもの
* 体重を聞いて、ペレット量を返すBOT

---

# 作ったもの
![image.png](https://qiita-image-store.s3.amazonaws.com/0/124445/84aa5a03-7cba-2bac-1fdb-026c4751aa7b.png)

## 全体像

![image.png](https://qiita-image-store.s3.amazonaws.com/0/124445/6b60fe86-ccca-0bb4-995b-8ffc78cef93b.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/124445/6977f4fe-d3ab-d561-3461-363f8fd6a9bf.png)


![image.png](https://qiita-image-store.s3.amazonaws.com/0/124445/6ad70321-46d9-1e4a-eeb0-74d674649d69.png)

---
## 動作動画
* （うさぎの誕生日は登録してあります）
* 1000と投稿した例
    * ~~我が子~~ BOTがペレット量50.0gと教えてくれます
* （おまけ）投稿した内容は今日の記録としてDBに蓄積されます
[![Image from Gyazo](https://i.gyazo.com/e11f613b41f0185d92bdfb1e6462d3fa.gif)](https://gyazo.com/e11f613b41f0185d92bdfb1e6462d3fa)

---
# Slack連携のところ
* 参考
    * https://qiita.com/takose/items/bc2b63426b9921604599
    * https://blog.engineer.adways.net/entry/2017/08/18/120000
    * https://blogahf.blogspot.com/2018/08/slack-event-api-bot-logicflow.html

* Slack Event API
    * slack event apiでアプリを作っておく
        * Event subscriptions: 
            * Request URL: `/api/rabbits/:token/slack` （ `:token` はrabbit毎に固有のtoken。）作っておく
            * Subscribe to Workspace Events: message.channels
            * Bot User: 適当な名前で作る。今回は `usagi` 
            * Incoming Webhooks: 特定のチャンネル（今回は `#rabbit` ）にBOTがレスポンスを返すためのwebhook URLを取得します。このURLをDBに入れておく（rabbit.slack_webhook_url）。
    * 上記アプリを管理画面からslackにインストールしておく

* Rails側は上記postを受けて以下を実行
    * メッセージ中の数字を今日の体重として保存
    * 体重からペレット量を計算
    * Incoming webhookでslackへ投稿
        * （ポイント）Botによるslackへの投稿で発火しないように `@body['event']['subtype'] == 'bot_message'` の場合はslack通知をしないようにする
    * slackへの投稿はgem `slack-notifier`をつかいました

```app/controllers/api/rabbits_controller.rb
class Api::RabbitsController < Api::ApplicationController
  protect_from_forgery except: [:slack]

  def slack
    rabbit = Rabbit.find_by(token: params[:token])
    return unless rabbit.present?

    body = JSON.parse(request.body.read)
    case body['type']
    when 'url_verification'
      render json: body
    when 'event_callback'
      unless from_bot?(body) # BOTが無限に会話しないようにユーザーからの発言のみに応答するため
        num = body['event']['text'].match(/\d+/).present? ? body['event']['text'].match(/\d+/)[0] : nil # 数値を正規表現で取得
        feeding = rabbit.feeding(date: Time.current) # 今日の日付
        if feeding.body_weight.present? # newの場合
          feeding.body_weight.update(gram: num)
        else # updateの場合
          feeding.builds
          feeding.body_weight.assign_attributes(gram: num, scaled_at: Time.current)
          feeding.body_weight.save
        end
        if rabbit.slack_webhook_url # slackへ投稿。予めwebhook_urlを取得してDBに入れておく。
          notifier = Slack::Notifier.new(rabbit.slack_webhook_url)
          message = "ぼくの体重は#{feeding.body_weight.gram}g。ペレットは1日#{rabbit.ideal_pellet_gram&.round(1)}gでおねがいね！[くわしくはこちら](#{root_url}) 。"
          notifier.ping(message)
        end
      end
      head :ok
    end
  end

  private

  def from_bot?(body)
    body['event']['subtype'] == 'bot_message'
  end
end
```

ペレット量の計算はこちら。

```app/models/rabbit.rb
  def ideal_pellet_gram
    return unless latest_body_weight
    latest_body_weight_gram = latest_body_weight.gram
    coefficient = if latest_body_weight_gram < 400
                    0.07
                  elsif age_of_month <= 6
                    0.05
                  elsif age_of_year < 5
                    0.02
                  elsif age_of_year >= 5
                    0.01
                  end
    latest_body_weight_gram * coefficient
  end
```

---
# おまけ（Webの画面）
* 投稿された体重はDBに蓄積
* 我が子の成長グラフでみて☺️
* chartkickでグラフ描画
    * `gem 'chartkick'`
![image.png](https://qiita-image-store.s3.amazonaws.com/0/124445/66a4248c-6903-a388-48e9-be7335cfe920.png)

---