みんなの「作ってみた」

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

2019/06/18

west2538
west2538
40代でセミリタイアして中山間地域の豪雪地帯に新しい住まいの形「ギルドハウス十日町」を設立。そこでWebアプリ「まちかどルート」を開発しています

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

参考記事

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

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

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

view

\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」と「Cal-heatmap」を読み込ませています。

\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>

公式のドキュメントを参考にしながらcal.init({ })内の各種オプションを指定しています。また、前述の参考記事と違うのは、とくにdataの部分です。下記のAPIに渡すパラメーターのstartstopTime.nowメソッドを使うことで、APIから取得するJSONデータの期間(ここでは6か月分)を指定しています。

API

\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を使ってAPIを構築しています。

参考記事と違うのはPostCommentというふたつのテーブルにある投稿日時のカラム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", "[email protected]"], ["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", "[email protected]"], ["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", "[email protected]"], ["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", "[email protected]"], ["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を追記したり、いろいろ試したのですが解決できていません。

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