ゆっくりのんびり。

いの (@inox_ee) です

【Scala】勉強ログ(更新一時停止)

追記(2020/04/10)

sbtを操作すると頻繁にPCが落ちるようになった(なんで?)ので一旦やめ。

まえがき

ただの個人用勉強ログです。
COVID-19 の おかげ せいで N 予備校の講座が無料で見れるのでやっていく。

Scala とは?

環境

  • WSL1
  • Open JDK 11.0.3(?) for Linux (以前入れたっぽい)
  • sbt 1.3.8 (Scala Build Tool?)
  • VSCode
  • Scala (Metals) : VSCode の拡張。これだけで補完と format が効くっぽい。2020?年から ENSIME から Metals へ移行したよう。

REPL 立ち上がらない

sbt console
>>> [warn] No sbt.version set in project/build.properties, base directory: /$CURRENT_DIRCTORY/Go-Tutorial
>>> [error] java.nio.file.AccessDeniedException: /$HOME/.cache/coursier
>>> [error] Use 'last' for the full log.
>>> Project loading failed: (r)etry, (q)uit, (l)ast, or (i)gnore? r

なぜか ~/.cache の所有者が root だったので自分に変更。

sudo chown [username]:[groupname] ~/.cache

数値表現

値型 範囲 記述例
Byte 1 Byte 符号付き整数 1.toByte
Short 2 Byte 符号付き整数 1.toShort
Int 4 Byte 符号付き整数(-214748368 ~ 2147483647) 1
Long 8 Byte 符号付き整数 1L
Float 4 Byte 浮動小数点数 1.0F
Double 8 Byte 浮動小数点数(-1.7976931348623157E308 ~ 1.7976931348623157E308) 1.0

Map()(連想配列) の更新

今までのイメージ

// JavaScript
aaa = { a: 1, b: 2 };
aaa["a"] = 3;
console.log(aaa) >>> { a: 3, b: 2 };

Scala

// 更新というか新規作成
val aaa = Map(2->1, 3->2)
val aaa2 = aaa + (3 -> (aaa.getOrElse(3, 0) + 1))  // 二重かっこにするのを忘れないように。
println(aaa2)
>>> Map(2 -> 1, 3 -> 3)

文字列検索

索引型検索

  • 文章のなかから予め単語の文字列を抜き出して、その単語ごとに索引を作っておく
  • 単語ごとに、対象の文章の中の頻度や場所に応じてスコアが計算される
  • 例)検索エンジンなど

非索引型検索

  • 与えられた文字の情報のみで検索
  • 検索のたびに、対象の文章データを全て走査

文字列String

  • Scala の String 型は、Java の String 型を踏襲。APIJava 11 String を参考。
  • 基本的にダブルクォーテーションを使う。シングルクォーテーションは Char 型。

ビット演算

計算 演算子 記述例 結果 補足
ビット単位 AND & -1 & 2 2
ビット単位 OR | -1 | 2 -1
ビット単位 XOR ^ -1 ^ 2 3
ビット単位補数 ~ ~0 -1 全ビットを反転させる
右ビットシフト(MSB 埋め) >> -1 >> 31 -1
右ビットシフト(0 埋め) >>> -1 >> 31 1
左ビットシフト(0 埋め) << 1 << 1 2 2 のべき乗

ビット演算を使ってアルゴリズムを考えられるようになりたい

用語

用語 説明
構文(Syntax) class, val, if プログラムが構造を持つための規則
式(Expression) 1, 1+1, "hoge" 評価することで値となるもの
評価 Scala の式をコンパイルすること
文(Statement) val i = 1 評価しても値にならないもの。

Unit

ifwhile といった式は()を返す。これはUnit と言われる型のオブジェクトの値である。

C 言語や Java でいうところの void にあたる。

クラスとオブジェクト

クラス

簡単に言うと、「複数のオブジェクトを作るためのひな形」。

クラスから作られるオブジェクトを「インスタンス」、状態を「フィールド」、振る舞いを「メソッド」と言う。またフィールドとメソッドを合わせて「メンバー」という。

ケースクラス

ふつうのクラスとほぼ一緒。
ケースクラスは不変なデータを作るのに適しており、パターンマッチングで有用である。

例えば変数宣言におけるパターンマッチは、タプルによる分割代入のような値の取り出しのことを指す。

case class CPoint(x: Int, y: Int)

val cp1 = CPoint(5,6)
val CPoint(a, b) = cp1

>>> a: Int = 5
>>> b: Int = 6

一瞬、val CPoint() って何やと思うけど、ここで大事なのはa, bの方。

その他、sealed classを用いることで enum を実装できる。逆に Java にはあるのになんで Scala はこれなんだろうか…

なお上記で「ほぼ一緒」と言ったが、通常のクラスと「同値性」が異なる。

簡単に言えば、ケースクラスではインスタンスが異なる場合であっても持っているフィールドが同じであれば同値と見なされる。値っぽい挙動。

ちなみに hashCodeequalsというメソッドをオーバーライドすれば通常のクラスにおいてもこの同値性を変更することが出来る。

オブジェクト

数あるオブジェクトの中でも、object キーワードを使って作成したオブジェクトは、他と異なりたったひとつのインスタンス(これを「シングルトンオブジェクト」という)しか持たない。Scala では「オブジェクト」という単語が、一般的なオブジェクト全般を指す場合と、シングルトンオブジェクトを指す場合とがあるので注意。

ユースケースとしては

  • グローバルな状態やユーティリティメソッドを表現するため
  • オブジェクトを作り出すためのメソッドを提供するため
  • アプリケーションなどのシングルトンオブジェクトをつくるため

があげられる。

コンパニオンオブジェクト

コンパニオンオブジェクトとは、クラス名と同じ名前を持つオブジェクトのこと。 applyメソッドを定義することで、new演算子を利用せずにインスタンスを作ること(これを「ファクトリメソッド」という)ができる。

なんでコンパニオンオブジェクトがあるの?(めっちゃ重要)

なんでclassだけじゃなくてobjectでコンパニオンオブジェクトを書かなきゃいけないのか全く分からなかったが、以下の記事を読んで納得できた。

https://qiita.com/geshi/items/0a789724b2419bd56204

意識しなければいけないのは、Scala は静的型付け言語であること。
先のブログの文章を引用すると、

しかし、静的型付き言語ではクラスに型を定義する必要があるので(Ruby と違うところ)、 型を定義されたクラスと、クラスを生成するものが必要となります。

ということになる。なので Scala で書くクラスには大概 object が必要となる(多分)。そしてその機能としては initialize メソッドとほぼ同じと考えていてよさそう。

ちなみにクラスに対して定義するものはコンパニオンオブジェクト、インスタンスに対して定義するものはクラスに書く。

【WSL】Windowsファイルの権限変更(+VSCode Remote Developmentの実行)

TL;DR

  1. WSLからマウントしたWindowsファイルシステム群のデフォルトパーミッションを変更した備忘録
  2. 脳死でファイルのパーミッション644 にすると、VSCode Remote Development が実行できなくて死ぬ
  3. 誰か適切な権限教えて乁(˙꒳˙乁)クレヨ......

WSLのパーミッション変更

デフォルトパーミッション

WSLからマウントしたWindowsファイルシステム群 (以下WSL-WinFS)は、デフォルトではパーミッションが777(= -rwxrwxrwx)とされています。つまり「『所有者・所有グループ・その他』が『読み込み・書き込み・実行』可能」ということ。さすがに気持ち悪いので、変更しましょう。

やり方

Linuxコマンド umask により変更することも可能ですが、WSLを立ち上げるたびに設定が初期化されてしまう(らしい)ので、/etc/wsl.conf に設定を書き込む*1 ことでこれを保持しましょう。初期状態ではおそらく存在しないので、新規作成します。

# /etc/wsl.conf の中身 <- comment
[automount]
enabled = true
root = /mnt/
options = "metadata,fmask=133,dmask=022"

権限に関するオプションを以下に抜粋。

  • fmask
    • すべてのファイルに対して除外するアクセス許可の 8 進数のマスク
  • dmask
    • すべてのディレクトリに対して除外するアクセス許可の 8 進数のマスク
  • umask
    • すべてのファイルとディレクトリに対して除外するアクセス許可の 8 進数のマスク

上記confファイルにより、dmask=022は777 - 022 = 755、すなわち drwxr-xr-xfmask=133は777 - 133 = 644 すなわち -rw-r--r-- となります。

補足1: 権限記号読み方

権限記号は、

  • 先頭1文字目がディレクトリorファイルの識別(dor-)
  • 以降3文字区切りで「所有者」「所有グループ」「その他」ごとの権限

を表現しています。

また権限は以下の表にもとづいて、数値で表されます。

記号 権限 数字
r 読み込み 4
w 書き込み 2
x 実行 1
- 権限なし 0

これより、-rwxrwxrwx777(= 4+2+1 / 4+2+1 / 4+2+1)、-rw-r--r--644(= 4+2+0 / 4+0+0 / 4+0+0)というように8進数表記に変換されます。

補足2: /etc/fstab

マウントに関する設定としては/etc/fstabを作成する方法が一般的(?)と思われますが、上記URLでも述べられているように

注: これらのオプションは、自動的にマウントされたドライブすべてのマウント オプションとして適用されます。 特定のドライブのみのオプションを変更するには、代わりに /etc/fstab を使用します。

という使い分けがなされるようです。自分の場合はCドライブしかないのでwsl.conf を用いました。

ちなみに、bashzshの場合 .profileumask 022 と記述する方法もありますが、Rails等で自動生成されるファイルはそれに従ってくれないらしいです(要検証)。

VSCode Remote Developmentの起動

VSCode Remote Development(以下VS-RD) により、WSLを利用した開発がかなり快適になりました。WSL2が実装されればもう言うことなしです(それもうUbuntuで良くない?)。

さて、VS-RDを起動してみましょう。前述のパーミッション変更により、当然Permission Denied と言われ、接続に失敗します。しかたないのでWarningのポップアップの隙間から見える $HOME(多分)/.vscode/extensions/ms-vscode-remote.remote-wsl-0.42.3/scripts/wslCode.sh をchmodで644->755に変更します。

しかしまだ起動せず……。
エラーログも見えないのでどのファイルを実行しているかもよく分かりません。うーむmm…。

結局

fmaskとdmaskで各々権限を設定せず、umask=022で一括755にして現状対処することに。このあたりの "Linux感" がまだ身についてないのでこのままで良いか怪しいところ…。

変にいじってパーミッション破壊を起こしたくもないので放置。書きながらユーザ・権限周りの知識が怪しいことに気づいたので再度OSの授業を復習しようと決めました。以上。

【latex】overleaf(uplatex) でディレクトリ構造の図を書く

まえがき

latexでツリー図を書こうと思ったが、Overleafでは tree.sty が使えない1ので、代替packageを探した。 意外と記事がヒットしなかったので簡単にまとめる。

描画したい図

一般的な木構造に加え、ディレクトリ構造も描画できると嬉しい。

結論: forest package

個人的にはforest が最適だったのでこれを紹介する。ちなみに公式docでもお気持ち紹介されている2

www.ctan.org

Tikzをベースにしているので、オプション等は共通した文法である。例) grow'=0

描画可能な図(一例)

f:id:puyobyee18:20191224132545p:plainf:id:puyobyee18:20191224132550p:plain
tree & dirctories

書き方

上述の図のソースコードは以下の通り。

% main.tex
\documentclass[11pt, uplatex]{jsreport}
\usepackage[edges]{forest}

% 場合によっては以下も必要かもしれない。自分の環境では不要だった。
% \usepackage{tikz}
% \usetikzlibrary{trees}
% tree

\begin{forest}
 [VP, for tree={parent anchor=south, child anchor=north}
 [DP[John,tier=word]]
 [V’
 [V[sent,tier=word]]
 [DP[Mary,tier=word]]
 [DP[D[a,tier=word]][NP[letter,tier=word]]]
 ]
 ]
\end{forest}
% directory

\begin{forest}
 for tree={grow'=0,folder,draw}
 [/
  [home
   [saso
    [Download]
    [TeX]
   ]
   [alja]
   [joe]
  ]
  [usr
   [bin]
   [share]
  ]
 ]
\end{forest}
% 念のためlatexmkrc
$latex = 'uplatex';
$bibtex = 'pbibtex';
$dvipdf = 'dvipdfmx %O -o %D %S';
$makeindex = 'mendex %O -o %D %S';
$pdf_mode = 3; 

詳細は http://ftp.jaist.ac.jp/pub/CTAN/graphics/pgf/contrib/forest/forest-doc.pdf を参照されたい。

注意

上述のdirectory を描画する際、コードにインデント(スペース1つで良い)をつけないと以下のように、Package pgfkeys Error: I do not know the key '/tikz/grow' というエラーが発生した。latexのブロック定義ってインデントだっけ?

f:id:puyobyee18:20191224132549p:plain
Package pgfkeys Error: I do not know the key '/tikz/grow'


  1. overleaf で使えるパッケージのリスト(公式Doc) に含まれていない。

  2. Using the forest package to create trees in LaTeX - Overleaf, Online LaTeX Editor

【GitHub】草、生やしていますか?

はじめに

いの です。
とある事情で2月までデスマ状態なのですが、VTuberを見ながらどうにか精神を保っています。

GitHubのContributionについて

いわゆる「草」です。
最近までインターンもやっており、研究の進捗もgit管理しているのできっと大草原になっていることだろう…と思ったものの、

f:id:puyobyee18:20191011130650p:plain
gh_pre_contribution

砂漠地帯ですね…
さすがにもう少し進捗を産んでいるのでは?と思い、公式doc*1を見直してみました。

曰く、

  • The email address used for the commits is associated with your GitHub account.
  • The commits were made in a standalone repository, not a fork.
  • The commits were made:
    • In the repository's default branch (usually master)
    • In the gh-pages branch (for repositories with Project Pages sites)

このうち自分は1つ目の「メールアドレスの設定」を見逃していたようです。早速修正しましょう。

contributionに関連するメールアドレス

今回登場するメードアドレスには以下の2種類があります。

  • GitHubのアカウントに登録されているメアド
  • commitに紐付いているメアド

一つずつ見ていきましょう。

GitHub

これは GitHubに紐づく メアドです。
Github > Settings > Public profile > Public email」から適当なメールアドレスを選択します。自分の場合、そもそもメアドをpublicにしていなかったので、「Settings > Emails」内の Keep my email addresses private のチェックを外しました。

Git側

ローカル の設定も確認しましょう。
該当するディレクトリで git config user.email コマンドを打つことで確認できます。先程GitHub側で設定したメアドと一致しているか確認しましょう。
なお、 --global オプションをつけることでグローバルの設定を一括で変更できますが、どうやらcontributionに反映させるには各ディレクトリからpushしないといけないようです。

おわり

以上の操作が済んだらprofileページで確認してみます。きっとcontributionに草が生い茂っていることでしょう。

f:id:puyobyee18:20191011130643p:plain
gh_contributions

まぁまぁだな…

あとがき

先日はVsingerの花譜を紹介しましたが、今回紹介するのはにじさんじ所属の「健屋花那(すこやかな)」さんです。

youtu.be

デビューから数週間にもかかわらず、登録者数は8万人越え。見た目はもちろんのこと、滲み出る博識さも気に入っています。 まぁなにより可愛いんですけど。

なにかと魅力的なナース姿であることもあってか、ファンアートも充実しています(ハッシュタグ #いらすこや で検索!)。
けろりらさん作のアニメーション(https://twitter.com/kerorira1/status/1182243727219163136)もかなりの人気度。本人のことを知らない人でも思わず手を止めてしまうのではないでしょうか…

【Node.js】Webpack でバンドルしたファイル群を Heroku にデプロイしたときに環境変数を適切に使いたい

タイトルながい

あらすじ

Github Pages をはじめ、シンプルな HTML と JS で構成された web ページなら無料で公開できるサービスは五万とあります。

ただ今回自分の作ったものには API key が含まれていたため、Heroku にデプロイすることにしました。
Heroku なら設定も楽だし大丈夫っしょ~~なんて思ってたら 1 日溶けました。つらい。
つらいのでブログにまとめました。誰かの 1 日を守れたら幸いです。

構成

<project>
|_ app/
    |_ index.html
    |_ src/
        |_ index.html
        |_ index.js
    |_ dist/
        |_ bundle.js
|_ .env(git管理外)
|_ webpack.config.js
|_ package.json
|_ app.js

やりたいこと

node ./app.js でexpressを用いたサーバを立て、src/ 以下の index.htmlindex.js を読み込む静的ページを構成。
この js ファイル内で API key を使用するため、.env環境変数を入れておきます。
また index.js は他モジュールを読み込んでいるので babel で es5 へ変換しつつ webpack でバンドルさせます。

1. dotenv

ググったらいっぱい出てくるので説明は割愛。

環境変数.env ファイルで管理するときに用いられるモジュールとして有名なものですが、webpack でバンドルしてしまうとバンドル後の js ファイルに直接書き込まれてしまうっぽい。secure じゃないので却下。

2. dotenv-webpack

上記の問題を解決した(?)モジュールが dotenv-webpack
これは dotenvWebpack.DefinePlugin をラップしたもので、環境変数の呼び出し方はほとんど dotenv と相違ありません。

github.com

dotenv は js ファイル内で require('dotenv').config() と呼び出していましたが、dotenv-webpackwebpack.config.js 内に諸設定を書き込みます(以下参照)。

/* webpack.config.js*/
const Dotenv = require('dotenv-webpack');
module.exports = {
  // 中略
  plugins: [new Dotenv()],
  // 中略
};

これで OK。 あとは呼び出したいところで process.env.SAMPLE_API_KEY と書けば API key が使用できます。

2. 余談

ちなみに .env: でなく = なので注意。

# .env
SAMPLE_API_KEY="hogehoge"

3.Heroku へのデプロイ

Heroku で環境変数の管理をするなら heroku-config をインストールしましょう。楽です。説明は割愛。

ローカルでも動くしあとはdeployだけすればおっけー!なーんて思ってたら

f:id:puyobyee18:20190714120431p:plain

うーん

結論

ローカルなら動く、さらに言えば手元でバンドルしてからデプロイしたらAPIが正しく設定されるようのでHeroku側でwebpackを動かしたときに config vars が読み込めていないっぽい。

一行一行設定を変えつつデプロイして、を繰り返した結果、 webpack.config.js に以下の設定を加えることで解決した。

plugins: [
  new Dotenv({
    systemvars: true,
  }),
],

公式のREADME曰く、

load all the predefined 'process.env' variables which will trump anything local per dotenv specs.

ということらしいのだが、なぜこの設定で行けるようになるのかは謎。

とりあえずデプロイできてよかった。

私の webpack.config.js

諸事情によりレポジトリは公開できませんが、package.jsonwebpack.config.js だけ公開しておきます(一部改変)。参考にしてください。

/* package.json */
{
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node ./app.js",
    "deploy": "git push heroku staging:master",
    "build": "npx webpack",
    "heroku-postbuild": "webpack -p"
  },
  "devDependencies": {
    "dotenv-webpack": "^1.7.0",
    "eslint": "^5.16.0",
    "eslint-config-prettier": "^4.3.0",
    "eslint-plugin-prettier": "^3.1.0",
    "prettier": "^1.17.1",
  },
  "dependencies": {
    "webpack": "^4.35.3",
    "webpack-cli": "^3.3.5",
    "@babel/core": "^7.5.4",
    "@babel/preset-env": "^7.5.4",
    "babel-loader": "^8.0.6",
    "@babel/polyfill": "^7.4.4",
    "express": "^4.17.1",
  }
}
/* webpack.config.js */
const path = require('path');
const Dotenv = require('dotenv-webpack');

module.exports = {
  // メインとなるJavaScriptファイル(エントリーポイント)
  // aync/await を使うには `@babel/polyfill` を以下のように設定する
  entry: ['@babel/polyfill', './app/src/index.js'],

  // ファイルの出力設定
  output: {
    //  出力ファイルのディレクトリ名
    path: path.resolve(__dirname, 'app/dist'),
    // 出力ファイル名
    filename: 'bundle.js',
  },

  node: {
    fs: 'empty',
  }, // これもよく分からないけどとりあえずemptyにしておけば動いた。
  mode: 'development',
  plugins: [
    new Dotenv({
      path: path.resolve(__dirname, './.env'), // いらないかも。
      systemvars: true,
    }),
  ],
  module: {
    rules: [
      {
        // 拡張子 .js の場合
        test: /\.js$/,
        use: [
          {
            // Babel を利用する
            loader: 'babel-loader',
            // Babel のオプションを指定する
            options: {
              presets: [
                // プリセットを指定することで、ES2019 を ES5 に変換
                '@babel/preset-env',
              ],
            },
          },
        ],
      },
    ],
  },
};

あとがき

最近、Vsinger の 花譜 ちゃんにめちゃくちゃハマっています。
15 歳と思えぬ力強い歌声、カンザキイオリさんの楽曲の魅力を持て余すことなく歌い切る表現力 ――――― 。

今後も、もっともっと活躍してほしいです。
8/1 の初の単独ライブ『不可解』、院試のせいで行けないので泣いてる。2nd LIVE に期待。

youtu.be

ラプラスかわいい

【Git】個人用備忘録

Git 備忘録

Git で知らなかったことまとめ。随時更新していきます

対象: なんとなく Git の仕組みが分かってきた人・add,commit,push,pull,checkout,branch は一応使える人

ToC

  • index, HEAD の違い
  • add のオプションまとめ
  • commit を削除する
  • commit をまとめる
  • 差分を見る
  • rebase 使い方
  • branch削除方法

☆index, HEAD の違い

要更新


☆add まとめ

$ git add ... の選択肢は以下の3つ。

  • file 名そのまま: ディレクトリを指定すればそれがステージングにあがる。 ex) $ git add ./app/models/clinic.rb
  • フォルダ名 フォルダ内を再帰的に add してくれる。 ex) $ git add ./app
  • まとめて追加 3 種類(⇒参考)

    • $ git add . バージョン管理されていないファイルは追加されない。 すなわち新たに追加されたもの変更があったものが対象。 Git ver.1 まではこうであったが、現在は追加変更削除いずれも追加される。
    • $ git add -u バージョン管理されているファイルをすべて追加。 すなわち変更があったもの削除されたものが対象。
    • $ git add -A[--all] 全てが追加される。

    $ git add .$ git add -Aの違い 前者はカレントディレクトリ下のファイルをすべて追加するが、後者はプロジェクトのどこからでも該当ファイルをステージングにあげるという違いが存在する。


☆commit の削除方法

  • commit を削除したことを残す $ git revert <commit>で削除可能。コンフリクトがおきたらがんばれ。 なお<commit>のところには commit ID を入力すればよい。

☆commit をまとめる方法

  1. $ git log --onelineでまとめたいコミットを確認。
$ git log --oneline

# 1a2b3c <commit msg3>
# 4d5e6f <commit msg2>
# 7g8h9i <commit msg1>
  1. $ git rebase -i HEAD~<n>により、HEAD から n 個の commit をまとめる。
  2. vim が展開するので、以下のように合成元をfixupに書き換える。:wqで保存。
pick 7g8h9i <commit msg1>
- pick 4d5e6f <commit msg2>
- pick 1a2b3c <commit msg3>
+ fixup 4d5e6f <commit msg2>
+ fixup 1a2b3c <commit msg3>

※やばいときは$ git rebase --abortで rebase を取りやめる。

参考


☆diff により差分を見る方法

git diffで差分を見れる。アレコレ書こうと思ったけど普通にこの Qiita が優秀だったので読みましょう。

参考 ⇒忘れやすい人のための git diff チートシート


☆rebase まとめ(含: stash, fetch)

git rebaseとは?

歴史のを改変するコマンド。以下の図が分かりやすい。

rebasemerge(則ちpull)のコミットログ比較(⇒参考)

rebaseとmergeの比較

分かりやすい。

やり方

ブランチ名は上記画像に則る。

  1. git fetch git fetch はリポジトリと結びついているorigin/masterが最新になる。(正確にはリモートのmasterブランチをもとに、ローカルにFETCH_HEADというブランチが構築され、それが最新状態になる。) 他人からの変更があった場合、こまめにやるのがよさそう。 ちなみにgit pullは、この fetch の作業が終わったのち作業ディレクトリに結び付く master を origin/master をもとに更新する。
  2. git rebase origin/develop featureブランチの"元"を上記図 D から引っ張るようにする。うまくいけば 3 へ。 以下うまくいかない場合
    • featureに commit が残っているとき この場合は、rebase をする前にgit stash saveを行う。この stash コマンドにより作業を一時退避させることができる。退避させたのち rebase を行い、git stash applyで適用する。以下便利なコマンド。
      • git stash list: stash の一覧を確認したい場合。
      • git stash apply <stash_name>: <stash_name>を適用する。コンフリクト起こしたときはがんばれ。
      • git stash drop <stash_name>: stash を削除するコマンド。適宜消すべし。
      • git stash pop <stash_name>: 適用と削除を一気に行うコマンド。自信がないうちは apply でいいよ。
    • conflict を起こしたとき Aborted! やらなにかしら吐いて rebase が一時中断される。 git statusで確認すると、コンフリクトを起こしているファイルはboth modifiedとなっているハズ。 適当なエディタ等でコンフリクトを solve したら、該当ファイルを add するのを忘れないように。 さらにそののちcommit はせずgit rebase --continueで rebase 処理を再開させる。この手順じゃないとうまくいかなかった。
    • よく分からなくなってしまったとき git rebase --aborted一択。rebase を実行する前の状態まで戻してくれるので、ヤバい!と思ったらこれを行う。
  3. git push --force origin feature あまりやりたくはないが push --force をやる。

一連の流れ

$ git fetch
$ git stash # プッシュしていないcommitがあったとき
$ git rebase origin/master
$ git status # コンフリクトがありそうなとき
$ git diff -- FILE_NAME # 差分を確認
$ git add FILE_NAME
$ git rebase --continue
$ git stash pop
$ git push --force origin mybranch

☆branch削除方法

ローカル

# コミット済み(HEADにmerge済?)の場合
git branch --delete my-branch
# 上記を問わない場合
git branch -D foo

リモート

# 一覧表示
git branch --remote
# 消し方は一通り
git push --delete origin my-branch

【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)になってしまった。進捗産めたのはいいけど、翌日に響くからみんなは早く寝ようね。睡眠大切。