【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 型を踏襲。API は Java 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
if
や while
といった式は()
を返す。これは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 はこれなんだろうか…
なお上記で「ほぼ一緒」と言ったが、通常のクラスと「同値性」が異なる。
簡単に言えば、ケースクラスではインスタンスが異なる場合であっても持っているフィールドが同じであれば同値と見なされる。値っぽい挙動。
ちなみに hashCode
とequals
というメソッドをオーバーライドすれば通常のクラスにおいてもこの同値性を変更することが出来る。
オブジェクト
数あるオブジェクトの中でも、object
キーワードを使って作成したオブジェクトは、他と異なりたったひとつのインスタンス(これを「シングルトンオブジェクト」という)しか持たない。Scala では「オブジェクト」という単語が、一般的なオブジェクト全般を指す場合と、シングルトンオブジェクトを指す場合とがあるので注意。
ユースケースとしては
- グローバルな状態やユーティリティメソッドを表現するため
- オブジェクトを作り出すためのメソッドを提供するため
- アプリケーションなどのシングルトンオブジェクトをつくるため
があげられる。
コンパニオンオブジェクト
コンパニオンオブジェクトとは、クラス名と同じ名前を持つオブジェクトのこと。
apply
メソッドを定義することで、new
演算子を利用せずにインスタンスを作ること(これを「ファクトリメソッド」という)ができる。
なんでコンパニオンオブジェクトがあるの?(めっちゃ重要)
なんでclass
だけじゃなくてobject
でコンパニオンオブジェクトを書かなきゃいけないのか全く分からなかったが、以下の記事を読んで納得できた。
https://qiita.com/geshi/items/0a789724b2419bd56204
意識しなければいけないのは、Scala は静的型付け言語であること。
先のブログの文章を引用すると、
しかし、静的型付き言語ではクラスに型を定義する必要があるので(Ruby と違うところ)、 型を定義されたクラスと、クラスを生成するものが必要となります。
ということになる。なので Scala で書くクラスには大概 object が必要となる(多分)。そしてその機能としては initialize メソッドとほぼ同じと考えていてよさそう。
ちなみにクラスに対して定義するものはコンパニオンオブジェクト、インスタンスに対して定義するものはクラスに書く。
【WSL】Windowsファイルの権限変更(+VSCode Remote Developmentの実行)
TL;DR
- WSLからマウントしたWindowsファイルシステム群のデフォルトパーミッションを変更した備忘録
- 脳死でファイルのパーミッションを 644 にすると、VSCode Remote Development が実行できなくて死ぬ
- 誰か適切な権限教えて乁(˙꒳˙乁)クレヨ......
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-x
、fmask=133
は777 - 133 = 644 すなわち -rw-r--r--
となります。
補足1: 権限記号読み方
権限記号は、
- 先頭1文字目がディレクトリorファイルの識別(
d
or-
) - 以降3文字区切りで「所有者」「所有グループ」「その他」ごとの権限
を表現しています。
また権限は以下の表にもとづいて、数値で表されます。
記号 | 権限 | 数字 | |
---|---|---|---|
r | 読み込み | 4 | |
w | 書き込み | 2 | |
x | 実行 | 1 | |
- | 権限なし | 0 |
これより、-rwxrwxrwx
は777
(= 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
を用いました。
ちなみに、bashやzshの場合 .profile
にumask 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。
Tikzをベースにしているので、オプション等は共通した文法である。例) grow'=0
描画可能な図(一例)
書き方
上述の図のソースコードは以下の通り。
% 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のブロック定義ってインデントだっけ?
【GitHub】草、生やしていますか?
はじめに
いの です。
とある事情で2月までデスマ状態なのですが、VTuberを見ながらどうにか精神を保っています。
GitHubのContributionについて
いわゆる「草」です。
最近までインターンもやっており、研究の進捗もgit管理しているのできっと大草原になっていることだろう…と思ったものの、
砂漠地帯ですね…
さすがにもう少し進捗を産んでいるのでは?と思い、公式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に草が生い茂っていることでしょう。
まぁまぁだな…
あとがき
先日はVsingerの花譜を紹介しましたが、今回紹介するのはにじさんじ所属の「健屋花那(すこやかな)」さんです。
デビューから数週間にもかかわらず、登録者数は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.html
で index.js
を読み込む静的ページを構成。
この js ファイル内で API key を使用するため、.env
に環境変数を入れておきます。
また index.js
は他モジュールを読み込んでいるので babel で es5 へ変換しつつ webpack でバンドルさせます。
1. dotenv
ググったらいっぱい出てくるので説明は割愛。
環境変数を .env
ファイルで管理するときに用いられるモジュールとして有名なものですが、webpack でバンドルしてしまうとバンドル後の js ファイルに直接書き込まれてしまうっぽい。secure じゃないので却下。
2. dotenv-webpack
上記の問題を解決した(?)モジュールが dotenv-webpack
。
これは dotenv
と Webpack.DefinePlugin
をラップしたもので、環境変数の呼び出し方はほとんど dotenv
と相違ありません。
dotenv
は js ファイル内で require('dotenv').config()
と呼び出していましたが、dotenv-webpack
は webpack.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だけすればおっけー!なーんて思ってたら
うーん
結論
ローカルなら動く、さらに言えば手元でバンドルしてからデプロイしたら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.json
と webpack.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 に期待。
ラプラスかわいい
【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 をまとめる方法
$ git log --oneline
でまとめたいコミットを確認。
$ git log --oneline # 1a2b3c <commit msg3> # 4d5e6f <commit msg2> # 7g8h9i <commit msg1>
$ git rebase -i HEAD~<n>
により、HEAD から n 個の commit をまとめる。- 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
とは?
歴史の元を改変するコマンド。以下の図が分かりやすい。
rebase
とmerge
(則ちpull
)のコミットログ比較(⇒参考)
分かりやすい。
やり方
ブランチ名は上記画像に則る。
git fetch
git fetch はリポジトリと結びついているorigin/masterが最新になる。(正確にはリモートのmasterブランチをもとに、ローカルにFETCH_HEADというブランチが構築され、それが最新状態になる。) 他人からの変更があった場合、こまめにやるのがよさそう。 ちなみにgit pull
は、この fetch の作業が終わったのち作業ディレクトリに結び付く master を origin/master をもとに更新する。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 を実行する前の状態まで戻してくれるので、ヤバい!と思ったらこれを行う。
- featureに commit が残っているとき
この場合は、rebase をする前に
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大先生に聞いてみると、こちらの記事が見つかりました。以下その記事より引用。
さっき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)になってしまった。進捗産めたのはいいけど、翌日に響くからみんなは早く寝ようね。睡眠大切。