こんがりぃ

都内在住フリーランスのエンジニア。主にRuby on Rails。Twitter(@Kongari_Bug)で格ゲー。

Googleマイマップの更新情報をSlackに通知する

やりたいこと

Googleマイマップにラーメン屋の感想を入力し、Slackに内容を通知して共有したい

調べた限り、マイマップには更新を検知するためのwebhook設定などはない。
今回はGoogle Apps Script, Fusion Tablesを利用して実現する。
※Fusion Tablesは2019年12月でサービス終了のアナウンスが流れているため注意してください

f:id:KongariBug:20181215041931p:plain

各種利用するサービスについて

マイマップ

マイマップ – 概要 – Google マップ

自分専用のGoogle Mapを作り、メモとかピンができる

https://i.gyazo.com/c68be0a1c5033d08c08c51427be08558.png

Fusion Tables

About Fusion Tables - Fusion Tables Help

スプレッドシートRDBMSのようにSQLで編集することができる。

実装

既存マイマップをFusion Tablesに落とし込む

マイマップはkmlというxml形式でエクスポートすることができる。

f:id:KongariBug:20181215042807p:plain

ダウンロードしたkmlファイルを開いてURLをたどっていくと以下のような形式のkmlがダウンロードできるリンクにたどり着く

<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
  <Document>
    <name>hogehogeマップ</name>
    <description>hogehoge</description>

上記kmlをダウンロードし、新規作成したFusion Tablesにインポートする

f:id:KongariBug:20181215043309p:plain

正しくインポートされたら name , description カラムが追加される

f:id:KongariBug:20181215044107p:plain

Google Apps Scriptでkmlを監視する

以下に全文ソース記載。やっていることは

  1. 現在のFusion Tablesのデータを取得
  2. 現在のkmlのデータを取得
  3. kmlからpinデータのみを抽出
  4. Fusion Tablesのデータと比較
  5. Fusion Tablesにないものは新規レビューとしてFusion Tablesに追加、Slackに通知
function myFunction() {
  // table id を入力
  var tableId = '*****************************************';
  var selectSql = 'SELECT name, description FROM ' + tableId;
  var res = FusionTables.Query.sql(selectSql);
  
  // fusion tableのデータをハッシュに
  var fusionTable = {};
  
  for(var i = 0; i < res.rows.length; i++) {
    var name = res.rows[i][0];
    var description = res.rows[i][1];
    fusionTable[name] = fusionTable[name] || [];
    fusionTable[name].push(description);
  }
  
  // kml urlを入力
  var kmlUrl = 'https://www.google.com/maps/d/kml?forcekml=1&mid=******************************';
  var kmlResponse = UrlFetchApp.fetch(kmlUrl);
  var xml = XmlService.parse(kmlResponse.getContentText());
  
  // ハードコーディングしているが取得したほうが安全
  var namespace = XmlService.getNamespace('http://www.opengis.net/kml/2.2')
  
  // kmlのFolder Elementの中にデータあり
  var folders = xml.getRootElement().getChildren()[0].getChildren('Folder', namespace);
  
  for(var i = 0; i < folders.length; i++) {
    var folder = folders[i];

    // name, descriptionのみを取り出す
    var placemarks = folder.getChildren("Placemark", namespace);
    for(var j = 0; j < placemarks.length; j++) {
      var nameElement = placemarks[j].getChild("name", namespace);
      var descriptionElement = placemarks[j].getChild("description", namespace);
      var name = '';
      var description = '';
      
      if(nameElement) name = nameElement.getValue().trim();
      if(descriptionElement) description = descriptionElement.getValue().trim();
      
      // 現在のFusion Tablesとの比較
      if(!fusionTable[name] || fusionTable[name].indexOf(description) == -1) {
        Logger.log(name + ": false");
        Logger.log("name")
        Logger.log(name);
        Logger.log("description");
        Logger.log(description);
        Logger.log("fusionTable");
        Logger.log(fusionTable[name]);
        var insertSql = 'INSERT INTO ' + tableId + '(name, description) VALUES (\'' + name + '\', \'' + description + '\');';
        FusionTables.Query.sql(insertSql);
        sendSlackMessage(name, description);
      }
    }
  }
}

function sendSlackMessage(name, description) {
  // Slack incoming webhook URLを入力
  var slackWebhook = 'https://hooks.slack.com/services/****************************;
  var options = {
    "method": "post",
    "contentType": "application/json",
    "payload": JSON.stringify({ "text": "*" + name + "*\n" + description })
  };
  UrlFetchApp.fetch(slackWebhook, options);
}

定期的に実行する

GASを定期実行するトリガーを設定する

f:id:KongariBug:20181215044913p:plain

Done

参考URL

Fusion Tables×Google Apps Script(Webアプリケーション作成編1) - Qiita

RubyのClass#inheritedの実行タイミングはクラス定義文の実行直前

サブクラスを呼び出したかったがそうはならない。

class A
  def self.inherited(child)
    p child
    child.aaa
    B.aaa
    aaa
  end
  
  def self.aaa
    p "A"
  end
end

class B < A
  def self.aaa
    p "B"
  end
end

p "#" * 40
B.aaa

実行結果

B
"A"
"A"
"A"
"########################################"
"B"

docs.ruby-lang.org

yarn install 中の node-sass エラー

make: *** [Release/obj.target/binding/src/binding.o] Error 1
gyp ERR! build error 
gyp ERR! stack Error: `make` failed with exit code: 2
gyp ERR! stack     at ChildProcess.onExit (/home/vagrant/rails/yuyuyui/node_modules/node-gyp/lib/build.js:258:23)
gyp ERR! stack     at ChildProcess.emit (events.js:182:13)
gyp ERR! stack     at Process.ChildProcess._handle.onexit (internal/child_process.js:237:12)
gyp ERR! System Linux 4.15.0-20-generic
gyp ERR! command "/usr/local/bin/node" "/home/vagrant/rails/yuyuyui/node_modules/node-gyp/bin/node-gyp.js" "rebuild" "--verbose" "--libsass_ext=" "--libsass_cflags=" "--libsass_ldflags=" "--libsass_library="
gyp ERR! cwd /home/vagrant/rails/yuyuyui/node_modules/node-sass
gyp ERR! node -v v10.6.0
gyp ERR! node-gyp -v v3.6.2
gyp ERR! not ok 
Build failed with error code: 1

nodeのバージョンの問題らしい

nodeのバージョンを 10.6.0 -> 9.0.0 にしたら治った。9.0.0は別の環境と揃えただけのバージョンなので良いバージョンを探すと良さそう。

rails/webpackerを使ってRailsで定義した定数をTypeScript(JavaScript)内で使う

環境

  • Rails5.1.4
  • rails/webpacker 3.2.1

公式のdocumentにあるerb-loaderを利用する。 https://github.com/rails/webpacker#erb

ubuntu@ubuntu-xenial:~/rails/yuyuyui$ bundle exec rails webpacker:install:erb
rails aborted!
Don't know how to build task 'webpacker:install:erb' (see --tasks)

taskがないと言われた(この時点ではrails/webpacker 3.0.1あたりだった)。webpackerをupgradeする

ubuntu@ubuntu-xenial:~/rails/yuyuyui$ bundle update webpacker

ubuntu@ubuntu-xenial:~/rails/yuyuyui$ bundle exec rake -T | grep erb
rake webpacker:install:erb              # Install everything needed for Erb

タスクができたので再実行する

ubuntu@ubuntu-xenial:~/rails/yuyuyui$ bundle exec rails webpacker:install:erb
Webpacker is installed � 
sing /home/ubuntu/rails/yuyuyui/config/webpacker.yml file for setting up webpack paths
Copying erb loader to config/webpack/loaders
      create  config/webpack/loaders/erb.js
Adding erb loader to config/webpack/environment.js
      insert  config/webpack/environment.js
      insert  config/webpack/environment.js
Updating webpack paths to include .erb file extension
      insert  config/webpacker.yml
Copying the example entry file to /home/ubuntu/rails/yuyuyui/app/javascript/packs
      create  app/javascript/packs/hello_erb.js.erb
Installing all Erb dependencies
         run  yarn add rails-erb-loader from "."

いろいろファイルができた。とりあえずwebpack-dev-serverを起動してみる

ubuntu@ubuntu-xenial:~/rails/yuyuyui$ bundle exec bin/webpack-dev-server                                                    
/home/ubuntu/rails/yuyuyui/config/webpack/environment.js:4
environment.loaders.append('erb', erb)
                    ^

TypeError: environment.loaders.append is not a function

appendがないと言われる。理由はわからないがとりあえず config/webpack/environment.js 内のappendをsetにして回避する(情報求)

environment.loaders.set('erb', erb)

これで使えるようになる。今回はTypeScriptに書きたいので、config/webpack/loaders/erb.jstest: を以下のように書き換える。

module.exports = {
  test: /\.(erb|ts)$/,
  enforce: 'pre',
  exclude: /node_modules/,
  use: [{
    loader: 'rails-erb-loader',
    options: {
      runner: 'bin/rails runner'
    }
  }]
}

これで例えばconfig gemを利用して以下のようなTypeScriptが書ける。

    getCards(): Observable<Card[]> {
        let url = "http://<%= Settings['api_host'] %>/api/cards.json";
        return this.http.get<Card[]>(url);
    }

Rails5.1 + Angular4で templateUrl, styleUrlsの代替

以前紹介したとおりRails5.1+rails webpackでtemplateUrlを使うことはできない。

Rails5.1.2でAngularを使うとtemplateUrlでエラーが出る - こんがりぃ

ちなみにドキュメントがReadmeからdocディレクトリに移ったみたい。

webpacker/typescript.md at master · rails/webpacker · GitHub

ではstyleUrlsの代替は

@Component({
  selector: 'hogehoge',
  styles: [require('./app.component.css').toString()]
})

これでOK。

TS2304: Cannot find name 'require'. というエラーが出た場合は

yarn add @types/node

でOK。

angular - Cannot find name 'require' after upgrading to Angular4 - Stack Overflow

BootstrapのModal内でSelect2をうまく表示する

BootstrapのModal内でSelect2を使うとレイアウトが崩れてしまう。

Modalの tabindex = -1 を取るといいというのもあるらしいが、それをしてもレイアウトが崩れて困っていたが以下のリンクの通りすればいけた。

github.com

$('.hoge').select2({
  dropdownParent: $('.fuga')
})