【Rails】個人開発のアプリにGitHub風のチャートを実装

個人開発のWebアプリ**「[まちかどルート](https://machiroute.herokuapp.com/)」v6.2**に、名づけて**《アクティビティ・チャート》**なるものを実装したときのメモです。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/%E3%81%BE%E3%81%A1%E3%81%8B%E3%81%A9%E3%83%AB%E3%83%BC%E3%83%88?src=hash&amp;ref_src=twsrc%5Etfw">#まちかどルート</a> v6.2 リリース<a href="https://t.co/JaIlGWYGmA">https://t.co/JaIlGWYGmA</a><br><br>冒険者のステータスに 「アクティビティ・チャート」 を追加！投稿やクリアといった活動（アクティビティ）が多いほどチャートにたくさん草が生えます <a href="https://t.co/EZOYgTFs4H">pic.twitter.com/EZOYgTFs4H</a></p>&mdash; 西村 治久（ハル） (@west2538) <a href="https://twitter.com/west2538/status/1140637497979838464?ref_src=twsrc%5Etfw">June 17, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 参考記事

こちらの記事を参考にさせていただきました。
ありがとうございます！

**Railsアプリで Cal-Heatmap を表示してみる**
https://qiita.com/volpe28v/items/3a5a2c05f1c0ee3fa5ad

以下、じぶんなりのアレンジも含めてメモとして残します。

# view

```erb:\app\views\layouts\application.html.erb
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/cal-heatmap/3.6.2/cal-heatmap.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cal-heatmap/3.6.2/cal-heatmap.css" />
```
レイアウトテンプレートの`application.html.erb`にこの3行を書き加えることで、今回必要なライブラリ「[D3.js](https://d3js.org/)」と「[Cal-heatmap](https://cal-heatmap.com/)」を読み込ませています。

```erb:\app\views\users\show.html.erb
<div id="heatmap"></div>
<script>
    var startDate = new Date();
    startDate.setMonth(startDate.getMonth() - 5);

    var parser = function(data) {
        return eval("(" + data + ")");
    };

    var cal = new CalHeatMap();
    cal.init({
        itemSelector: "#heatmap",
        data: "/api/v1/comments?user_id=<%= @user.id %>&start=<%= Time.now.ago(6.months) %>&stop=<%= Time.now %>",
        afterLoadData: parser,
        cellSize: 5,
        domain: "month",
        subDomain: "day",
        range: 6,
        tooltip: false,
        start: startDate,
        domainLabelFormat: "%b",
        legend: [1,2,3,4],
        weekStartOnMonday: false,
        legendVerticalPosition: "center",
        legendOrientation: "vertical"
    });
</script>
```
[公式のドキュメント](https://cal-heatmap.com/)を参考にしながら`cal.init({ })`内の各種オプションを指定しています。また、前述の参考記事と違うのは、とくに`data`の部分です。下記のAPIに渡すパラメーターの`start`と`stop`に`Time.now`メソッドを使うことで、APIから取得するJSONデータの期間（ここでは6か月分）を指定しています。

# API

```ruby:\app\api\resources\v1\comments.rb
module Resources
  module V1
    class Comments < Grape::API
      resources :comments do

        # /api/v1/comments
        desc 'アクティビティ・チャートJSON出力'
        params do
          requires :user_id, type: Integer, desc: 'ユーザーID'
        end
        get do
          user = User.find(params[:user_id])
          from = params[:start]
          to = params[:stop]
  
          comments1 = Post.where(post_uid: user.uid, created_at: from..to)
          comments2 = Comment.where(user_uid: user.uid, created_at: from..to)
          comments1 = comments1.to_a
          comments2 = comments2.to_a
          comments = comments1 + comments2
          comments.map{|c| c.created_at.to_i}.inject(Hash.new(0)){|h, tm| h[tm] += 1; h}.to_json
        end

      end
    end
  end
end
```
わたしのアプリでも、ちょうど参考記事と同じく[Grape](https://github.com/ruby-grape/grape)を使ってAPIを構築しています。

参考記事と違うのは`Post`と`Comment`というふたつのテーブルにある投稿日時のカラム`created_at`を抽出＆結合し、アクティビティ・チャートに反映させている点です。

# 課題

参考記事にも書かれていますが**「rails から json で返したデータをパースするメソッドを定義して afterLoadData に設定する(eval しないと json として読み込めなかったので)」**とのこと。

確かにこれをしないとチャートが表示されません。

しかしながら、このせいか、ログを見るといちどに2回、APIを叩いています。

```
Started GET "/api/v1/comments?user_id=1&start=2018-12-18%2009:57:23%20+0900&stop=2019-06-18%2009:57:23%20+0900" for 192.168.14.1 at 2019-06-18 09:57:23 +0900
  [1m[36mUser Load (0.1ms)[0m  [1m[34mSELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?[0m  [["id", 1], ["LIMIT", 1]]
  ↳ app/api/resources/v1/comments.rb:12
  [1m[36mPost Load (0.2ms)[0m  [1m[34mSELECT "posts".* FROM "posts" WHERE "posts"."post_uid" = ? AND "posts"."created_at" BETWEEN ? AND ?[0m  [["post_uid", "townsguild@another-guild.com"], ["created_at", "2018-12-18 09:57:23"], ["created_at", "2019-06-18 09:57:23"]]
  ↳ app/api/resources/v1/comments.rb:18
  [1m[36mComment Load (0.1ms)[0m  [1m[34mSELECT "comments".* FROM "comments" WHERE "comments"."user_uid" = ? AND "comments"."created_at" BETWEEN ? AND ?[0m  [["user_uid", "townsguild@another-guild.com"], ["created_at", "2018-12-18 09:57:23"], ["created_at", "2019-06-18 09:57:23"]]
  ↳ app/api/resources/v1/comments.rb:19
Started GET "/api/v1/comments?user_id=1&start=2018-12-18%2009:57:23%20+0900&stop=2019-06-18%2009:57:23%20+0900" for 192.168.14.1 at 2019-06-18 09:57:23 +0900
  [1m[36mUser Load (0.2ms)[0m  [1m[34mSELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?[0m  [["id", 1], ["LIMIT", 1]]
  ↳ app/api/resources/v1/comments.rb:12
  [1m[36mPost Load (0.7ms)[0m  [1m[34mSELECT "posts".* FROM "posts" WHERE "posts"."post_uid" = ? AND "posts"."created_at" BETWEEN ? AND ?[0m  [["post_uid", "townsguild@another-guild.com"], ["created_at", "2018-12-18 09:57:23"], ["created_at", "2019-06-18 09:57:23"]]
  ↳ app/api/resources/v1/comments.rb:18
  [1m[36mComment Load (0.6ms)[0m  [1m[34mSELECT "comments".* FROM "comments" WHERE "comments"."user_uid" = ? AND "comments"."created_at" BETWEEN ? AND ?[0m  [["user_uid", "townsguild@another-guild.com"], ["created_at", "2018-12-18 09:57:23"], ["created_at", "2019-06-18 09:57:23"]]
  ↳ app/api/resources/v1/comments.rb:19
```
なんとかして1回で済むようにviewの`<script>`に`data-turbolinks-eval=false`を追記したり、いろいろ試したのですが解決できていません。

まだまだ未熟です...。
これからも精進していきたいと思います。