ゆっくりのんびり。

いの (@inox_ee) です

【mongoDB+Node.js】mongoDBの公式Docを読んで軽く触ってみた(つもりが思わぬ沼にハマった話)

はじめに

GraphQLの話をするって前回のブログで宣言したのにちょっと寄り道してmongoDBのお話です。ちょっと触ってみた感想としては、やはりNoSQLは新たに言語を取得する必要がない(今回私はNode.jsを使いました)ので学習コストが低く、それでいて多アクセスからの負荷耐性が強い(だった気がする。違うかも)とくれば人気が出るのも納得です。
今回は筆者のメモ書き程度の記事なので、いつも以上に正確さが疎かに、そしてそれ以上にです/ます調がテキトーになっています。間違い等ございましたらコメントにて指摘いただけると幸いです。

mongoDBのインストール

mongoDBの公式サイトからインストーラをダウンロードする。インストールの途中でInstall MongoD as a Service(公式Doc)と言われたが、Windows Serviceについて何も知らなかったのでとりあえずチェックを外してインストールした。

windowsの場合、ローカルで動かすにはまずデータベースのディレクトリを作る必要がある。

$ cd C:\
$ mkdir \data\db

これでおk。

次に今作ったディレクトリにデータベースのパスを通す(".\MongoDB\Server\4.0\bin"をPATHに追加したものとする)。

$ mongod --dbpath "C:\data\db"

ここで実行するのはmongod.exeであることに注意。よく調べてないけどmongodがデータべース自体を起動する実行ファイル、mongoはデータベースに接続するためのMongo Shellを起動するためのものっぽい。

これで設定は完了。mongodを起動すると、waiting for connections on port 27017(初期状態)と出てくるはずである。

mongoDBのデータ形式

mongoDBはデータをBSON(JSONのBinary形式)で記録している。
構造自体はJSONと同じ。

{
    field1: value1,
    field2: value2,
    ...
    filedN: valueN
}

filedはstring型。.$も一応使える。

valueは様々な型になる。ObjectId, Object, Date type, array of string, NumberLong typeなどなど。

データ構造

先ほど挙げた{ fieldN: valueN }のひとまとめを、documentという(たぶん)。そしてこれらの集合をcollectionと呼び、RDBでいうtableにあたる(たぶん)。 mongoDBはこの collection を多数集めたものでひとつのデータベースを構築している。

図示するとこんな感じ

db
 --- collection
        --- documents
        --- documents
        --- ...
 --- collection
        --- documents
        --- documents
        --- ...
 --- ...

db
 --- collection
 --- ...

mongoDB + Node.jsでCRUD

公式ドキュメントを読んだので簡単にまとめてみる。特に発展的なことは何もしていないです。

準備

npmでmongodbをインストールしておく

$ npm i -l mongodb

データベースへの接続の仕方は以下の通り。

const mongodb = require("mongodb")
const Client = mongodb.MongoClient

Client.connect("mongodb://127.0.0.1:27017/myDb", (err, db) => {
    // ここに以下のCRUDを書いていく
})

CREATE = Insert Documents

最も簡単なのはCollection.insertOne()を用いる方法。

awit db.collection('inventory').insertOne({
    item: 'canvas',
    qty: 100,
    tags: ['cotton'],
    size: {h: 28, w: 35.5, uom: 'cm'}
});

ちなみに戻り値はPromise型。また複数のdocumentsを挿入するCollection.insertMany()も存在する。

READ = Query Documents

documentsを読むAPIとしてはCollection.find()を用いることができる。
次の例1のように空のオブジェクトを渡すと、inventorycollectionにあるdocumentsすべてを呼び出すことができる。例2はANDを、例3はさらにORのクエリを用いている。

// example 1
const cursor = db.collection('inventory').find({})

// example 2
const cursor = db.collection('inventory').find({
    status: "A",
    qty: { $lt: 30 }
})

// example 3
const cursor = db.collection('inventory').find({
    status: "A",
    $or: [{ qrt: { $lt: 30 } }, { item: { $regex: '^p' } }]
})

ここで$ltとあるが、これはless thanを表す演算子$+αのかたちはクエリやアップデートの演算子によく見られる(詳細はこちら)。

ちなみにこのメソッドはSQLの次の文に対応しているっぽい。 ```sql

example 1

SELECT * FROM inventory

example 2

SELECT * FROM inventory WHERE status = "A" AND qty < 30

example 3

SELECT * FROM inventory WHERE status = "A" AND ( qty < 30 OR item LIKE "p%" ) ``` SQLの文法は分からないがまぁそんな感じがする。

UPDATE = Update Documents

Collection.updateOne(filter, update[, options, callback])を使っていきます。insertOne()find()と異なり、第一引数にfliterを渡し、更新するdocumentsを選択します。そして第二引数に更新内容を書きます。
また、updateの文にはupdate operatorを使い、更新内容を記述します。

// 以下のようなinventorycollectionが存在するとする。
//  {
//    item: 'notebook',
//    qty: 50,
//    size: { h: 8.5, w: 11, uom: 'in' },
//    status: 'P'
//  },
//  {
//    item: 'paper',
//    qty: 100,
//    size: { h: 8.5, w: 11, uom: 'in' },
//    status: 'D'
//  },
//  {
//    item: 'planner',
//    qty: 75,
//    size: { h: 22.85, w: 30, uom: 'cm' },
//    status: 'D'
//  }

await db.collection("inventory").updateOne(
    { item: "paper" },
    {
        $set: { 'size.uom': 'cm', status: 'P'},
        $currentDate: {lastModified: true}
    }
)

ちなみにupdateOne()が用いられたとき、filterによって複数のdocumentsが抽出された場合は一番初めにfilterされたdocumentがupdateの対象となる。

もちろん複数のdocumentsを更新するupdateMany()もあり、また書き込みではなく置き換えを行うreplaceOne()も存在する。

※1 書き込みの操作はAtomicallyに行われるらしい。OSで勉強したことが活きてくるネ!

※2 _id fieldの書き換えは禁止されているらしい。

※3 optionにupsert: trueを加えておくと、filteringされたdocumentsがあればそれを更新し、見つからなければ新規に作成する。UPDATE + INSERTの造語(?)らしい。

DELETE = Delete Documents

URLはremoveなんですね笑 最後はDeleteです。Collection.deleteOne(filter[, options, callback])またはCollection.deleteMany(filter[, options, callback])を使えばおk。これはfilterを引数に渡してやることで、該当のdocumentsを削除するというもの。

「よっしゃ!サンプルプログラム動かすぞ!」→いのでん「エラーだと?貴様この野郎......」

サンプルコードが動かない事態にいのでんが激怒した理由がやばすぎる...ブログの読者も動揺を隠せない最悪な事態に一同驚愕!!!

はい、執筆現在深夜2時のテンションでお送りしておりますが、上記のとおりサンプルコードそのままではエラーを吐いて動きません。

そのコードがこちら。

// Example of a simple insertMany operation

var MongoClient = require('mongodb').MongoClient,
  test = require('assert');
MongoClient.connect('mongodb://localhost:27017/test', function(err, db) {
  // Get the collection
  var col = db.collection('insert_many');
  col.insertMany([{a:1}, {a:2}], function(err, r) {
    test.equal(null, err);
    test.equal(2, r.insertedCount);
    // Finish up test
    db.close();
  });
});

これを実行すると、以下のように怒られます。

C:\ (略) \node_modules\mongodb\lib\operations\mongo_client_ops.js:474 
     throw err;
      ^

TypeError: db.collection is not a function
    at insertDocs (C:\ (略) \test_mongodb.js:20:8)
    at Client.connect (C:\ (略) \test_mongodb.js:9:5)
    at result (C:\ (略) \node_modules\mongodb\lib\utils.js:414:17)
    at executeCallback (C:\ (略) \node_modules\mongodb\lib\utils.js:406:9)
    at err (C:\ (略) \node_modules\mongodb\lib\operations\mongo_client_ops.js:294:5)
    at connectCallback (C:\ (略) \node_modules\mongodb\lib\operations\mongo_client_ops.js:249:5)
    at process.nextTick (C:\ (略) \node_modules\mongodb\lib\operations\mongo_client_ops.js:471:7)
    at process._tickCallback (internal/process/next_tick.js:61:11)

どうやらdb.collection()が原因らしい。筆者はjavascriptが最もまともに書ける言語と自称している割に、Promiseを深く理解しきれていないのできっと自分の書き方が悪いんだろう...とはじめは思いましたが、このエラーはどう考えても変数dbが予想されるものと確実に異なります。諦めてgoogle大先生に聞いてみると、こちらの記事が見つかりました。以下その記事より引用。

qiita.com

さっきnpmインストールしていつも通り接続しようとしたら TypeError: db.collection is not a function になるので調べてみたら、3系から仕様が変わったみたいです。

MongoClient.connect now returns a Client instead of a DB
http://mongodb.github.io/node-mongodb-native/3.0/upgrade-migration/main/

今のversion、4.0なんですが、、、

ということで、MongoClient.connectの返り値が変更されていたようですね。
(以下のコードも先ほどの記事から引用させていただいております。)

MongoClient.connect(_url, (err, client) => {
  // callbackに渡されるオブジェクトが変わった
  // db名を明示的に指定してdbオブジェクトを取得する必要がある
  const db = client.db('heroku_xxxxxxxx');

  db.collection('foobar', (err, collection) => {
    collection.find().toArray((err, docs) => {
      :
      :

    });
  });
});

~3.0とcallbackに渡されるオブジェクトが変更され、dbではなくclientと同じものが返ってくるようになりました。それに伴いcallback関数の中で明示的にdb名を指定してあげる必要ができます。

ちなみにlocalで実行している場合、MongoClient.db()の引数にIPv4のかたちで渡すことはできないので"localhost"でおk。

こんな大事な仕様変更、ちゃんとサンプルコードにも反映させておいてくださいよ…

ちょっと引っかかったこと

上記の仕様変更をmongo shellと行き来しながら確認している間、いくつか不明点が生じた。

自分が行ったことを事細かに記しておく。

/* test_mongodb.js内 */
const db = client.db("localhpst")
// とtypoしてしまった。この時mongo shellでdbを見てみると
$ mongo
> show dbs
admin
config
localhpst # typoしたDBが存在
testDb
> db.localhpst.find({})
> --- # empty
> db.testDb.find({})
> --- # empty

次に

/* test_mongodb.js内 */
const db = client.db("localhost")
// typoを修正。この時
$ mongo
> show dbs
admin
config
localhost # 新たにlocalhostが追加されていた
localhpst
testDb
> db.localhost.find({})
--- # empty
> db.testDb.find({})
--- # empty!!!
# この時移動する前に`db`コマンドでどこにいたのか確認したらよかったと後悔。
> use testDb
> db.localhost.find({})
--- # empty
> db.testDb.find({})
{"_id" : ObjectId("..."), "a" : 3 } # テスト用にmongo shellからinsertしていたものを発見。個人的にはここで{ "a" : 4 }{ "a" : 5 }が出てくるもの思っていただけに混乱してしまった。
> use localhost
> db.localhost.find({})
--- # empty
> db.testDb.find({})
{"_id" : ObjectId("..."), "a" : 4 }
{"_id" : ObjectId("..."), "a" : 5 } # test_mongodb.js内でinsertしていたもの

(はてなではハイライトされていないかもしれません。読みづらくてごめんなさい。)

要はデータベース名コレクション名を区別できていなかったため、自分が意図した(結果的に勘違いしていたわけだが)コマンドで、documentsが表示されなかったのである。

どうやらMongoClient.db()は引数にとったものをUPSERTで受けとるようだ。このメソッドの引数や、mongo shellでのdbコマンドやshow dbsで出力されるものは総じてデータベース名であるが、検索で指定するのはコレクション名であり、指定のデータベース下にいることは確保されていなければならない。その意味ではmongoDB v3からの仕様変更は、データベース名を明記する必要性があり確かなメリットが存在するので、手間などではなく改良されたものであることが頷ける。


P.S.

軽い気持ちでmongoDBをインストールしあれこれイジっていたら、こんな時間(am3:34)になってしまった。進捗産めたのはいいけど、翌日に響くからみんなは早く寝ようね。睡眠大切。

Webアーキテクチャのお勉強(前半)

今更ながら「Webを支える技術 ~HTTP、URI、HTML、そしてREST~」を読んだので自分の理解のためにも軽くまとめてみました。
RESTfulとはどういうことか、RESTに代わると言われる最近HOTなGraphQLについても述べていくよ。
間違いありましたらぜひぜひコメントください。


RESTとは?

歴史

90年代のwebの急速な発展に伴い、HTTP,URI,HTMLの標準化を執り行う必要性が生じてきた。この問題を解決するためBerners-Leeが中心となって設立したのが W3C(World Wide Web Consortium) である。このような背景で、Roy Fieldingという人物がwebアーキテクチャを分析し、とりまとめを行ったのがRESTである。

Resourceとは

RESTについてまとめる前にResourceを軽く説明。
リソースとは、簡単には「Web上にあるありとあらゆる情報」のことを指す。このリソース一つ一つを識別するための名前こそが、URIとなっている。あるURIが示すリソースは一意に定まり、URIを指定すればコンピュータは期待のリソースへアクセスできる。
なお、URIが指すリソースは一意だが、リソースは複数のURIを持つことができる。これによりリソースにアクセスしやすくなる。

  1. アドレス可能性(Addressablity)

    アドレス可能性とは、URIが備える、リソースを簡単に指し示せる性質のことである。

  2. リソースの「表現」

    リソースとは「web上の情報」という曖昧な概念であったが、実際にはクライアント・サーバ間でデータのやり取りを行う。このデータのことを「リソースの表現」と呼ぶ。概念的であったリソースが表層に出てきたものを指す。もちろん、このデータの現れ方は何通りも存在しうる。例えば天気情報は、HTMLで表現されることもあれば、画像やテキスト形式にもなりうる。

  3. リソースの「状態」

    またリソースは「状態」が変わりうるものである。例えば「今日の天気」というリソースは、時間とともに変化してゆき、「晴れ」から「雨」、はたまた「曇り」へとその状態を変え得る。

RESTとは

RESTとは、Representational State Transferの略称。これは「HTTPとは、ハイパーテキストだけでなく、実際には『リソースの状態(Resource Dtate)』及び『リソースの表現(Representation)』を運んでいる」としたものである。

具体的にRESTの設計思想を列挙すると、以下のような特徴があげられる。

  1. クライアント/サーバ型 : ユーザインターフェースと、リクエストの処理を分離できる

  2. ステートレス : サーバがアプリケーションの状態を管理しないことで、実装が楽になる。これはReact.jsの設計思想とも似てるね(コンポネント/コンテナの関係性とか)

  3. キャッシュ : 通信を減らすことで、サーバの負荷を軽減。やりすぎると情報の信頼性が下がる。

  4. 統一インターフェース : インターフェースを統一する(HTTPのメソッドは8種類のみ)ことで、クライアントとサーバの実装がより独立性を高められる(=相手のことを意識しなくてよい)。

  5. 階層化システム : ロード(Load)バランサやプロキシを間に挟んで負荷分散を行う(これだけじゃないけど)。インターフェースが統一されているからこそできる。

  6. コードオンデマンド : プログラムコードをサーバからダウンロードし、クライアント側で実行すること。

なんだかどれも分散システムの授業でやったことと同じだった。コードオンデマンドもコードマイグレーションの一種と考えられる。
このように設計することで、リソース同士繋がりあったハイパーリンクが成り立ち、大規模分散システムのwebを構築できるのだ。


GraphQLとは?

...

ブログ執筆欲を強制的に向上させるため、近日中にブログを書くことを宣言します…。

【後期実験】「情報可視化とデータ解析」を終えて

まえがき

こんにちは、いのでんです。
3年後期になりぼちぼち忙しい日々を送っていたわけですが、ついに後期実験も終わりほぼ冬休みな状態です。やったね! とか言ってる間に試験期間になりました。つらい。
実験終了して1ヶ月くらい経ちますが、実験最後のタームで行った「情報可視化システム」について書いていこうと思います。次年度の参考になるかはわかりません。

「情報可視化システム」とは?

近年、世に溢れるデータの数は爆発的に増え、「ビッグデータ」や「Deep Learning」が巷で話題となっております。この実験では、そんな大量にある情報を集積し、より見やすく秩序立て、有意な知見を得られるようにするにはどうしたらよいか、を考える講義となっております。間違ってたら不可来そう。

2〜3人のチームに分かれ、実験10日分(週3回なのでおよそ3週間)の期間で1つ可視化システムを作り、最終日にプレゼンを行います。プレゼンの日は大手企業の方もいらしており、優秀者には特典があるかも?フツーにめっちゃ緊張しました。

何を作ったか

我々のチームは終電情報可視化システムの「しゅうでん!」を作りました。

  • 動機 : ふつうに便利そう
  • 目的 : ある出発駅に対し、任意の到着駅への時刻を色分けし、鉄道の利用状況や都市の発展との相関を見る
  • ターゲット : なにかと終電を利用することが多い大学生や社会人

成果物は以下のURLから見ることができます。

https://inox-ee.github.io/infovis18_shuden/

(データはライセンス的に怪しいので品川.csvのみ載せてあります。よって「品川」と検索した場合のみ色付けが開始されます。)

機能紹介

  1. 右上の検索ボタンから出発駅を指定すると色分けを開始します。
  2. 出発駅から各駅エリアまでの経路に対して終電まで1時間以上なら終電まで1時間以内なら(時間が無くなるほど濃くなる)、終電が終わってしまったならに塗り分けられる。
  3. デフォルトでは現在時刻を取得し、右下のスライドバーで±1時間の変化を見ることが可能。切り替えボタンを押すと、入力して指定することも可能。
  4. GPSボタンからは、現在地を取得し最寄駅を提案してくれる。
  5. レスポンシブデザインに考慮し、スマホからでも見やすいデザインに設計。

苦労した点

おそらく一番重労働となったのはデータ収集でしょう。(ライセンス的にやや怪しいですが)某路線検索サイトをスクレイピングし、出発時刻と到着時刻を取って来るだけ…のはずが、入手した駅一覧のデータと検索サイトの駅名とが一致しない・冬季限定運行の駅も含まれていた・同駅名が意外と多い などの問題が発生。さらにAzureが使えず手持ちのマシンでクローラーを回すことに…….
無事に関東2000駅のデータを取り終えたチームメンバーに感謝です。

また、当然ながら取得したデータの見せ方も悩みました。都市部と地方とで駅密度は大きく異なり、一目で分かるようどんな手法を取り入れるか……。最終的に、d3.jsにも標準機能として搭載されていたボロノイ図を用いました。これは二次元平面上では、複数個の点に対して各々の母点の二等分線で領域の境界を結んでいくというものです。これを用いることで地方は1駅あたりの領域を大きく、都市部では小さくとることができます。数学の雑学が思わぬところで役に立ちますね……

そのほかにも、可視化・インタラクションの7つの原則である

  • Select/Focus:マップ上にボロノイ図を描画する
  • Explore:スライドバーによる検索時間の変化
  • Reconfigure
  • Encode:色塗りによる時間表現
  • Abstract/Elaborate:ホバー時、ポップアップによる追加情報の表示
  • Filter
  • Connect

を意識し、作成しました。

反省点

正直に申し上げると、あまり有益な情報は得られませんでした。「〇〇線の終電はかなり遅い」だとか「東京の東部と西部で大きく異なる」と言ったような有意な差が見られず、凡そ結果と呼べるものが少なかったかもしれません。原因として考えられるのは以下の通り。

  • 実験結果とその原因への仮説検証の不十分さ
  • 用いたデータの単一性
  • 可視化表現の単一性

これらの反省点から導びけるのは、やはり「このデータを可視化して何が得られる(or何を得たい)のか」が明確ではなかったことにつきるでしょう。HCIの分野では、「何をテーマにしたか」を聞いただけで(その後の手法や結果がどうであれ)学会に論文が通るか否かがおおよそ目星がつくそうです。

またデータ解析とは別に、システムの応答時間の短縮も、ユーザビリティを考慮したときに非常に重要な因子になる[1]ことを感じました。
地図・路線・駅・ボロノイ図と複数のデータをレンダリングする際は、読み込みや描画の方法を十分に考慮しなければならない。たとえばGoogle Mapのように、地図をタイル分割し、範囲を限定して読み込み・レンダリングを行うといった工夫もあります。フロントエンドに興味がある自分にとって、一度勉強しなければならないなぁと思っています。

[1] Judy Chuan-Chuan Lin, & Hsipeng Lu (2000). Towards an understanding of the behavioural intention to use a web site. International Journal of Information Management, Volume 20, Issue 3, Pages 197-208

最後に

この実験は、短い期間でインタラクションの基礎を学び、実際にデータを収集し、システムを開発するところまで行くため、非常にハードな実験でした。多少なりともPythonJavaScriptの知識がないときついかもしれません(と言ってもJS初学者が自分よりも圧倒的に良いものを作ってしまうのが弊学科ですが…)。
ですが、担当教授が基礎からデータ可視化を指導していただけることや、オリジナルのwebアプリ(?)を開発できるので、得られるものはとても大きかったなと感じています。来年も開講されたら、是非挑戦されてみいかがでしょうか。

Node.js + HerokuでLINE Messaging APIを叩いてみる

とある用事でLINE BOTを作ってみよう!と思い立ち、すぐさま取り掛かることに。
しかし動かすまでに意外と手間取ったので、ブログとして書き残しておきました。

環境

  • Windows10
  • Node.js v8.11.1
  • Heroku Free

LINE Developerの登録

LINE BOTを作成するために、LINE Messaging API を利用します。
「今すぐ始めよう」をクリックするとログイン画面に飛ぶため、とりあえず自分の普通のアカウントでログインしましょう。ログイン後、チャネル(要はbot)を新規作成する プロバイダー を選択します。チャネルは普通のLINEアカウントとしてではなく、それと紐づけされた「プロバイダー」として作成者が定められるようです。
プロバイダーの設定が終わったら、いよいよチャネルの登録です。

f:id:puyobyee18:20181028171438j:plain

基本的に指示に沿って名前や説明欄を埋めるだけですが、上図のプランに注意。フリープランだと友達人数の上限がありませんが、[PUSH_MESSAGE]機能が使えなくなってしまいます。ここではとりあえず Developer Trial プランで始めることにしましょう。

とりあえずチャネルの登録はできました。しかしチャネル基本設定を見てもChanne IDやらアクセストークンやらwebhookなどイマイチ使い方が分からない……大丈夫です。アレコレ試行錯誤して使い方は理解したので後程お話します。

ローカルでコーディング

ここからはローカルの話。(以降ではNode.jsとnpm、gitは使えること前提で書きます)

$ mkdir sample-app
$ cd sample-app
$ npm init -y
$ npm install --save @line/bot-sdk express

上記のコードで適当にディレクトリを作成し、Node.js用のAPIである@line/bot-sdk、サーバ構築用にExpressをインストールします。

あとはプログラムですが、とりあえず公式のサンプルコードを拝借します。

'use strict';

const line = require('@line/bot-sdk');
const express = require('express');

// create LINE SDK config from env variables
const config = {
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN,
  channelSecret: process.env.CHANNEL_SECRET,
};

// create LINE SDK client
const client = new line.Client(config);

// create Express app
// about Express itself: https://expressjs.com/
const app = express();

// register a webhook handler with middleware
// about the middleware, please refer to doc
app.post('/callback', line.middleware(config), (req, res) => {
  Promise
    .all(req.body.events.map(handleEvent))
    .then((result) => res.json(result))
    .catch((err) => {
      console.error(err);
      res.status(500).end();
    });
});

// event handler
function handleEvent(event) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    // ignore non-text-message event
    return Promise.resolve(null);
  }

  // create a echoing text message
  const echo = { type: 'text', text: event.message.text };

  // use reply API
  return client.replyMessage(event.replyToken, echo);
}

// listen on port
const port = process.env.PORT || 3000;
app.listen(port, () => {
  console.log(`listening on ${port}`);
});

これをserver.js等名前をつけて保存します。ここで重要なのがconfigオブジェクトにCHANNEL_ACCESS_TOKENおよびCHANNEL_SECRETを代入しているところです。どこかで聞いたような変数名ですね。

また、以下のファイルをProcfileという名前で保存します。かなり重要なファイルなので忘れないように。

web: node server.js

以上でローカルの操作は一通り終了です。後々のためにこれらのファイルをGithubにあげましょう。Githubでてきとうにレポジトリを作成したらpushしましょう。Gitの使い方は割愛します。

Herokuの設定

いよいよHerokuを使っていきます。HerokuはPaaSの一種で、個人開発程度ならFreeプランでアレコレできるので簡単にbotを実装できます。Azureとか個人サーバからngrokを利用してトンネルする(よくわかってない)方法とか選択肢はありますが、おそらくHerokuが一番親切で簡単に始められるでしょう。Micr〇S〇ftとかユーザーにAzure使わせる気あるんですか?というレベルでドキュメントが分かりにくいので

登録が終わると以下のような画面が出てくるでしょう。とりあえずページ右上のopen app を押し、なにも表示されませんがhttps://[アプリ名].herokuapp.com に接続できたら大丈夫です。

f:id:puyobyee18:20181028171443j:plain

それではこのherokuにデプロイしていきます。herokuのデプロイは2通りあり、

  • Heroku CLIを用いる方法
  • Githubと連携させる方法

があります。ここでは先程pushしたGithubを使うことにします。

Deployページを開き、連携するGithub repository を選択します。ページ中部にconnected to [レポジトリ名] by [アカウント名]と表示されたら無事完了です。

最後に、前章でコメントしていたserver.jsの変数についてです。どこにも定義されずに登場したCHANNEL_ACCESS_TOKENおよびCHANNEL_SECRETは、LINE botのチャネルに依存する変数で、LINEで作成したチャネル基本設定に表示されていたアクセストーク(表示されていない場合は再発行しましょう)とChannel Secretがこれにあたります。
ではどのように定義すればよいでしょうか。server.jsでは、これらの変数は環境変数として扱われるので、herokuに登録することで使用可能になります。

f:id:puyobyee18:20181028171433j:plain

上図のようにSettingページ中部のConfig Varsから登録します。これで2つの変数が定義され、プログラム上でも使えるようになります。

webhook URLの登録

webhookとは、

A webhook in web development is a method of augmenting or altering the behavior of a web pages, or web application , with custom callbacks.

引用元:https://en.m.wikipedia.org/wiki/Webhook

とありますが、最近では外部サービスと連携させてアプリケーションを動かすことを指すことが多いです。

それでは、LINE Messaging APIとHerokuをひも付けしましょう。LINEのチャネル基本設定から、webhook URLを登録します。ここで/callbackを呼んでいますが、これはserver.jsのコードを読めば、ここにpostしていることが分かると思います。

f:id:puyobyee18:20181028171417j:plain

最後に、LINE botの諸設定を以下のようにすれば完了です。登録時のコメントは好きなように設定すればいいと思います。

f:id:puyobyee18:20181028171424j:plain

補足:コードの実行

1つ気になると思うのが、LINE botがプログラムをどう実行しているかだと思います。
これは、先程のProcfileが担っています。herokuでResourceページを見てみると、Free Dynosの欄にこのファイルの中身が記述されているのが確認できます。LINE botからHerokuにプログラムが呼び出されると、これが実行されてserver.jsが立ち上がる、という仕組みになっているようです。

動作確認

早速作成したLINE botを友だち登録し、なにか発言してみましょう。使用したソースコードは、発言した言葉をそのまま返す、オウム返しbotとなっているはずです。

以上で終了です。お疲れ様でした。

参考にしたサイト

【React.js】Electron + ReactでMastodonクライアントを作る

Reactの勉強を始めて早1ヶ月。環境構築やAtomのクラッシュなどの困難を乗り越えた夏休み…

ようやくElectronまでたどり着いたのでMastodonクライアントを作ってみた。

※コードは以下の書籍を参考にしています

環境

  • Windows 10 Home
  • Node.js v8.11.1
  • React v16.5.2
  • electron v3.0.0

まずはAPIを叩いてみる

アプリの認証(本来は「認可」というべきらしい。)

mastodon-apiを用いてMastodonのWeb API利用する。インスタンスは、Pixivが運営する https://pawoo.net にした。

このWeb APIはOAuth2という認証方法(*1)を行うため、以下の手順が必要。 1. アプリをインスタンスに登録 2. ユーザが認証し、アクセストークンを発行 3. アクセストークンを用いてAPIにアクセス

正直mastodon-apiのライブラリがexampleファイルを用意してくれてるので適当にコピペすればおk。ということで割愛。

タイムラインの取得

先ほどのアクセストークンを利用して以下のようなプログラムを書くと簡単にタイムラインを表示できる

// MastodonのAPIクライアントの作成
const Mstdn = new Mastodon({
access_token: token,
timeout_ms: 60 * 1000,
api_url: instanceUri + '/api/v1/'
})

// タイムラインの読み込み
Mstdn.get('timelines/public', {})   
.then(res => {
    const data = res.data
    console.log(data)
})

ちなみにtimelines/homeにすればホームタイムラインが、timelines/publicにすれば公開タイムラインが表示されます。

トゥートしてみる

トゥートするのも簡単。そう、mastodon-apiならね。

// MastodonのAPIクライアントの作成
const Mstdn = new Mastodon({
  access_token: token,
  timeout_ms: 60 * 1000,
  api_url: instanceUri + '/api/v1/'
})

// Toot
let message = process.argv[2]
Mstdn.post('statuses',
  {status: message},
  (err, data, res) => {
    if (err) {
      console.error(err)
      return
    }
  })

結論:GETメソッドとPOSTメソッドが優秀すぎる。

アプリ成型

構成

構成はいたってシンプル。Electronを立ち上げ、アクティブになったらindex.htmlを読み込むだけ。

index.htmlからReactで書いたindex.jsxを呼んであげましょう。

Electronの立ち上げ

テンプレプログラムそのまま。ちなみにcreateWindow()のなかのprotocolでコロンを書き忘れ、2時間溶かしました。つらい。

// Electron
let mainWindow
app.on('ready', createWindow)
app.on('window-all-closed', () => app.quit())
app.on('activate', () => {
  if (mainWindow === null) createWindow()
})

// ウィンドウの作成
function createWindow () {
  mainWindow = new BrowserWindow({width: 600, height: 800})
  mainWindow.loadURL(url.format({
    pathname: path.join(__dirname, 'index.html'),
    protocol: 'file:',
    slashes: true
  }))
  mainWindow.on('closed', function () {
    mainWindow = null
  })
}

表示するページ

Electronから呼ばれるindex.htmlですが、のちのちindex.jsxをwebpackでビルドしてあげるので./out/index.jsを読み込むようにしてあげましょう。プログラムは割愛。

メインコンポネント

長すぎるので大幅に割愛してます。そして残念ながらはてなブログはJSXのシンタックスに対応していない。

1 constructor()

まずはconstructor。loadInfo()内では、前項で取得したアクセストークンを読み込み、APIクライアントを作成。状態(state)にはトゥート内容を格納するtootdataと、タイムラインを構築するtimelinesを設定してあげる。

constructor(props) {
    super(props)
    this.apiUri = 'https://pawoo.net/api/v1/'
    this.loadInfo()
    this.state = {
        tootdata: '',
        timelines: []
    }
}

2 componentWillMount()

そして次にコンポネントがマウントしたときの動作を書いたのですが、componentWillMount()ってReact v16.3.0以降は非推奨、v17.0.0から撤廃されるらしいですね…。

Deprecation warnings will be enabled with a future 16.x release, but the legacy lifecycles will continue to work until version 17. Even in version 17, it will still be possible to use them, but they will be aliased with an “UNSAFE_” prefix to indicate that they might cause issues. We have also prepared an automated script to rename them in existing code.

出典: React v16.3.0: New lifecycles and context API March 29, 2018 by Brian Vaughn

書き終わってからeslintに指摘されて気が付きました。ググってもイマイチ理解できなかったので新しいライフサイクルがどのようになるのか誰か教えて~

ちなみにcomponentWillMount内ではタイムラインを読み込むloadTimelines()を30秒に1回リロードしています。

3 handleText(e) & tootFunc(e)

あと必要なのはトゥート処理。これも前項で紹介したPOSTメソッドを使い、「statuses」APIに投稿したのち、テキストボックスとタイムラインを更新してあげればよい。

4 render()

いよいよ描画。アプリの顔を作っていきましょう。

デザインにはあまり凝らなかったので、とりあえずトップに投稿フォーム、その下にタイムラインを表示するという構成に。以下のようにプログラムすれば十分でしょう。タイムライン表示のrenderTimelines()は後述。

render () {
    return (
        <div>
            <div>
                <h1>Mastodon Client</h1>
                <textarea
                value={this.state.tootdata}
                onChange={e => this.handleText(e)} />
                <div>
                <button onClick={e => this.tootFunc(e)}>Toot</button>
                </div>
            </div>
            <div style={{marginTop: 120}}></div>
            {this.renderTimelines()}
        </div>
    )
}

5 renderTimelines()

一番難しい、、、というか写経したあとなるほどな~って勉強してた()

タイムラインを構築するコツはmapメソッドを使うところですかね。あとGETメソッドで返ってくるHTTPレスポンスはHTMLタグで囲まれています(Reactではエスケープされる)。これをdangerouslySetInnerHTMLを利用して直接span要素に指定する という発想はなかった。というかそもそもプロパティが全く分からないのも苦労した…。下記参考リンクを見つけなければ迷宮入りしてたよ。(accountがトゥートした投稿者を表すオブジェクト、contentがトゥート内容)

というわけでプログラム。

renderTimelines () {
    const lines = this.state.timelines.map(e => {
        console.log(e)
        // when boosted
        let memo = null
        if (e.reblog) {
            memo = (<p>{e.account.display_name}さんがブーストしました</p>)
            e = e.reblog
        }
        // content every toot
        return (
        <div key={e.id}>
            <img src={e.account.avatar} />
            <div>{memo}{e.account.display_name}<span dangerouslySetInnerHTML={{__html: e.content}} /></div>
            <div style={{clear: 'both'}} />
        </div>
        )
    })
    return (
        <div>
            <h2>TimeLines</h2>
            {lines}
        </div>
    )
}

むずかしいね

6 css

お好みで。

アプリの実行

あとはwebpackでビルドしてelectron .で実行してあげるだけ。

以下のように立ち上がったら完成です。

f:id:puyobyee18:20180924113003p:plainf:id:puyobyee18:20180924113012p:plain

お疲れ様でした。(つかれた)

参考サイト一覧

API

OAuth2

【JavaScript】JS自動補完パッケージ:atom-ternjsの設定

atom-ternjsによるJavaScript補完機能を構築するには…


Ternとは

Github上で公開されているTern.js(またはこちら)はJavaScript用のコード解析エンジンのひとつ。atomでも「atom-ternjs」という名でパッケージが公開されており、ユーザ数は50万人近くにものぼる

環境

Windows × atom

ことの発端

  1. atom-ternjsをインストール。
  2. しかし補完してくれない(例:"docu..."と入力しても、doしか出てこない)。しかもインスタンスの補完もイマイチ
  3. 色々ググってみた結果…

どうやらconfigureしなければいけないっぽい?!

ググってみると以下のサイトどちらも ".tern-project" というJSONファイルを作っている(正確にはJSONではないらしい…?)

https://qiita.com/s-shin/items/33bdfc5b819dab320808 http://blog.aqutras.com/entry/2016/04/28/210000

しかしJSONという気味の悪い拡張子の知識がなくて読めない&書けない

なかなか解決策が思いつかない。補完機能が使えないまま書くしかないのか…と思った矢先

YouTubeにあったatom-ternjsのインストール動画をたまたま発見。
メニューバー→パッケージ→Atom-Ternjs→Config projectの手順でConfigureの書き方を紹介。

bowserとjqueryにチェックしてからサーバの再起動を掛けると…
プロジェクトフォルダ内に .tern-project ファイルが!!!

{
  "ecmaVersion": 6,
  "libs": [
    "browser",
    "jquery"
  ],
  "loadEagerly": [],
  "dontLoad": [
    "node_modules/**"
  ],
  "plugins": {
    "doc_comment": true
  }
}

無事補完機能が動いた!

ひとまず動くようにはなったけど

課題

  • 毎回configしなくちゃいけないの?
  • package.jsonがあるフォルダに.tern-projectファイルを作っても機能せず。
  • .tern-project のほかに .tern-configファイル が必要?!

などなど

追記

atom-ternjsの公式サポートを読んでみると普通に書いてたっぽい。(英語
おそらくThurd party pluginsに書かれているファイルを atom-ternjs直下に入れれば毎度configしなくてよさそう...(?)

【Python】csvやtxt形式でのデータの取り込み

csv形式やtxt形式で外部ファイルに保存されたデータをPython上で取り込みたいとき、みなさんはどうしているだろうか。
一般に方法は3~4通りあり、

  • csvモジュールを使う
  • numpyを使う
  • pandasを使う

に大別されるであろう。普段私は速度重視でnumpyを好んで使っているので以下のようなコードで取り込みを行っている。

import numpy as np
import matplotlib.pyplot as plt
data = np.loadtxt('./hogehoge.txt', delimiter = '\t', skiprows = hoge)
plt.plot(data[0,:], data[1,:])
plt.show()

至って基本的である。その他の方法も各々メリットデメリットがあるのだが、一つ困った点が…
それはどれも決められた単一の区切り文字でしか処理できない点にある。
データの抜けがある場合はgenfromtxtで対応できるのだが、これは未だに対応策が思いついていない。

リニアテクノロジー社が提供する回路シミュレータであるLTSpiceは機能こそ充実しているものの、出力の形式が独特で悩ましい。
(出力電圧、位相)をカンマ区切りで出力すればいいものを、"frecency(Hz) \t ( voltage(dB)dB, phase(degree) --)" としてくるものだから困ったものである。
おそらく力技で攻めるなら read() とsplit() を駆使する方法であろう。しかしそれではとても面倒…
現状ではExcelで成形することで対応しているが、良い方法があったら教えていただきたい。