GithubでうっかりDelete forkボタンを押さないよう非表示にするChromeエクステンションを作った
Githubでfork元からPRを出してマージされると、Delete fork
ボタンが表示されます。
勢いよくマージしているとうっかり押しそうになるし、最近「マージしたら元ブランチを自動で消す」設定ができるようになったので尚更です。
Automatically delete head branches of pull requests
今までDelete branch
ボタンを押していた習慣が染み付いているので、その流れで間違えてDelete fork
を押しそうになります。
そもそもDelete fork
したいケースがないので、このボタンを非表示にするChromeエクステンションを書きました。
guard-delete-fork-chrome-extension
インストール手順
Chromeウェブストアには登録していないので自分で追加する必要があります。
1. 上記のリポジトリをCloneまたはダウンロードする
2. Chromeでchrome://settings/
から設定
> 拡張機能
を開く
3. 右上のデベロッパーモード
をオンにする
4. パッケージ化されていない拡張機能を読み込む
を選択
落としたguard-delete-fork-chrome-extension
ディレクトリを選択する。
5. Githubを開いていた場合はリロードする
おわり。
おまけ: refined-github入れれば解決するかもしれない
これを作るときに調べていて知りましたが、refined-github
というGithubを便利にいじるためのブラウザ拡張があるようで、そっちでは既に対応されているようです。(未確認)
https://github.com/sindresorhus/refined-github/issues/1786
refined-github
はFirfoxやOperaにも対応していて便利そう。
Laravel - StripeのWebhookで署名チェックする
stripe + webhookを実装しようと日本語記事を読んだら、どれも署名とか認証チェックみたいなことが考慮されていないので不安になって調べたメモ。
CSRFトークンのチェックを無効にしておく
本題とは関係ないけど、CSRFを無効にしておかないと302エラーになる。
なのでVerifyCsrfToken
の$except
にwebhookのエンドポイントを入れておく。
<?php # app/Http/Middleare/VerifyCsrfToken.php # ... protected $except = [ # こんな感じで無効にするエンドポイントを追加 'stripe/wh/*', ]; # ...
署名チェックの実装
Stripeは公式ドキュメントが本当によく出来ていて、そこにそのまま書いてある。
Checking Webhook Signatures | Stripe
サンプルコードも載っているので特に補足もないけど、Laravelで書くならこんな感じ。
<?php # ... try { $content = $req->getContent(); $signature = $req->header('Stripe-Signature'); // stripeのwebhook管理画面から取得した署名(configには自分で追加する必要あり) $wh_secret = config('services.stripe.webhook_secret'); $event = \Stripe\Webhook::constructEvent($content, $signature, $wh_secret); // $eventを使った何かしらの処理 // レスポンスは何でもいいけど、管理画面上の表示が見やすいのでjsonにしている return response()->json('ok', 200); } catch(\UnexpectedValueException $e) { return response()->json('Invalid payload', 400); } catch(\Stripe\Error\SignatureVerification $e) { return response()->json('Invalid signature', 400); } catch(\Throwable $e) { return response()->json($e->getMessage(), 500); } # ...
IPホワイトリストでもチェックできそう
実際に動作確認はしていないけどwebhookはこれらのIPから来るとか。
Domains and IP Addresses | Stripe
PCのディスプレイは複数あるよりも1つの方が作業に集中できる
結論
人それぞれ。
背景
むかしWindowsのデスクトップPCで仕事をしていたとき、デュアルディスプレイは当然だと思っていた。
当時の僕にとっては間違いなくデュアルディスプレイの方が生産性は高かった。ディスプレイが1つだとストレスがあった。現場によっては小さなディスプレイだったので尚更だった。
WindowsのノートPCを使っていた時も外付けモニタを用意して作業していたと思う。もう10年近く前なのでうろ覚えだが、この時は開発よりも設計寄りの仕事だった。
それからMacBook Proで仕事するようになると、開発の有無に関わらずデュアルディスプレイを使わなくなっていった。
会議や外出含めて持ち歩くことが多く、その度にケーブルを付け外ししたり、ウィンドウを配置し直すのが面倒に感じ始めた。
MacBookは11インチ(Air)、13インチ(Pro)、15インチ(Pro)を経験してきた。11インチのころは外付けモニタを使っていたかもしれないが、13インチ以上では使わないことが多かった。
外付けモニタを使わない開発スタイルが染み付いたものの、どこかでモヤモヤは消えなかった。
デュアルディスプレイを使った方が開発効率は上がるんじゃないだろうかと。あと単純にカッコいい。
一方でデュアルディスプレイなら必ずしも生産性が上げるわけではない、とも思い始めていた。
デスクトップにしろノートPCにしろ、シングルディスプレイだけでめちゃくちゃ生産性高い人を何人も見てきたからだ。
もちろん支給されていないとかではない。
シングルディスプレイの方が集中できる理由
たまに外付けモニタを試しては「やっぱ違う」と何となく感じていたんだけど、今日ハッキリと理由が分かった。
僕は基本的に各アプリを画面いっぱいに広げて作業する。
ブラウザも、Vimも、IDEも、ターミナルも、Slackも。
Finderとか一時的に使うアプリは例外で、適当な小さいサイズで使う。使ったらすぐに閉じる。
画面いっぱいにしているのは単純な表示領域の問題だけじゃなくて、「今はこの作業をする」と脳のスイッチを切り替えるためだ。
開発に限らず僕は自分を単細胞でマルチタスクが苦手だと思っているので、1つのことに集中するというスタイルが合っている。
むかしデュアルディスプレイを使っていたころはまだ、自分のそういう特性を理解していなかった。
デュアルディスプレイにすると目に飛び込んでくる情報量が増えるわけで、それだけ集中力が低下する。
別にリアルタイムで見る必要もないのにSlackやTwitterの画面を常に表示している必要はない。用事があれば通知は飛んでくるので反応が遅れるわけでもない。
開いているだけでいじらなくても、表示が更新されないようなアプリであっても、そこにあるというだけで意識を持っていかれる。
僕にデュアルディスプレイが向かない大きな理由はこれだと思う。
その上で、単純な操作性にしてもコストが高いと感じる。
シングルディスプレイならキーボード操作だけで切り替えが可能だが、デュアルディスプレイだとキーボード操作に加えて目線の移動が必要になる。これがどうにも落ち着かない。慣れかもしれないが。
あと僕は仮想デスクトップを活用していて、Macなら3本指のスワイプだけで簡単に切り替えられる。
デュアルディスプレイの場合はわざわざカーソルを行き来しないといけない。これも非常に面倒くさい。
仮想デスクトップの利用は、シングルディスプレイに馴染んだ理由の一つとして大きいと思う。
デュアルディスプレイの方が向いていそうな状況
恐らく僕にはしばらく縁がないけど、作業より監視や閲覧がメインになるとデュアル(マルチ)ディスプレイの方がいいと思う。
これらの時は情報量が必要になるし、アプリや画面の切り替えが頻繁に発生しそうなので。
最初に書いたとおり人それぞれだと思うが、自分にとってどうかを言語化できてスッキリした。
Laravel - DB::transaction中でDB::rollbackを呼んでいるとRefreshDatabaseを使っているテストでロールバックされない
環境
- Laravel 5.5
- PostgreSQL
- でも他のDBでも同じはず
現象
use RefreshDatabase
を使ってテスト毎にロールバックしているにも関わらず、データが残ってしまい後続のテストが落ちる。
原因
業務コードの方でDB::transaction()
を使い、その中でDB::rollback()
を呼んでいたため。
(例外時のテストを実行した時にテストデータが残ってしまった)
こういうコード。
<?php // ... try { \DB::transaction(function() { $isError = $this->updateHoge(); if ($isError) { \DB::rollback(); return; } }); } catch (\Throwable $e) { // ごにょごにょ }
解決方法
DB::rollback()
じゃなくて例外を発生させる。
<?php // ... try { \DB::transaction(function() { $isError = $this->updateHoge(); if ($isError) { throw new \Exception('hogehoge'); } }); } catch (\Throwable $e) { // ごにょごにょ }
詳細
まずLaravelのトランザクションの仕組みについて。
これは内部的に「トランザクションの階層」を保持していて、commitやrollbackが呼ばれた時にはこのプロパティを見て「実際に処理するかどうか」を判断している。
トランザクションを開始すると階層が+1されて、rollbackやcommitをすると階層が-1される。
例えば階層が1の時にcommitが呼ばれればそのままcommitするが、2以上なら「現在の階層」をいじるだけで何もしない。
<?php // laravel/framework/src/Illuminate/Database/Concerns/ManagesTransactions.php:153 public function commit() { if ($this->transactions == 1) { $this->getPdo()->commit(); } $this->transactions = max(0, $this->transactions - 1); $this->fireConnectionEvent('committed'); }
次にDB::transaction()
について。
これは引数で受け取った関数を実行したらcommitするようになっている。
<?php // laravel/framework/src/Illuminate/Database/Concerns/ManagesTransactions.php:20 try { return tap($callback($this), function ($result) { $this->commit(); }); } catch (Exception $e) { $this->handleTransactionException( $e, $currentAttempt, $attempts ); } catch (Throwable $e) { $this->rollBack(); throw $e; }
つまりDB::transaction()
内でDB::rollback()
を呼んでreturnしても、最終的にcommit
は呼ばれてしまう。
通常は問題ない。rollback()しているならこの時点で$this->transactions
は0になっているので、実際の$this->getPdo()->commit();
は呼ばれないからだ。
問題が起きるのはタイトルにある通りテストでuse RefreshDatabase
を使っている時に起きる。
(あとたぶん業務コードでもネストしたトランザクションで書いていると問題が起きそうだけど、自分のケースではなかったので考えてない)
RefreshDatabase
をuseすると、TestCaseクラスのsetUpによってbeginDatabaseTransaction
が呼ばれる。
<?php // laravel/framework/src/Illuminate/Foundation/Testing/RefreshDatabase.php:68 public function beginDatabaseTransaction() { $database = $this->app->make('db'); foreach ($this->connectionsToTransact() as $name) { $database->connection($name)->beginTransaction(); } $this->beforeApplicationDestroyed(function () use ($database) { foreach ($this->connectionsToTransact() as $name) { $database->connection($name)->rollBack(); } }); }
テストケースの開始時にトランザクションを開始して、テストが終了する時にロールバックする。
ここまでの情報をまとめると、次のように動く。
RefreshDatabase::beginTransaction()
によりtransactions=1
になる- テストデータを入れる -- Aとする
- 業務コードを呼び出す
DB::transaction()
によりtransactions=2
になる- 適当にデータ入れる -- Bとする
DB::rollback()
を実行するDB::transaction()
を抜ける時にコミットが走るtransactions=1
なので、commitが走る => AとBがcommitされる- commit後に-1して
transactions=0
になる
RefreshDatabase
のrollbackが走るがtransactions=0
だし、未コミットのデータもないので何も起きない- 結果としてAやBが残る
おまけ
そもそもPHPだとクロージャを使いづらくてコードが汚く見えるので(useを使って引き継がないといけないせい)、素直にDB::transaction()
じゃなくてDB::beginTransaction()
によるtry-catchの方が良い気がしてきた。
fish - rangerの多重起動を避ける設定
rangerというCLIのファイラがあるのを知って最近使っている。
参考: CLI で Linux ファイルマネージャ ranger を使うことのメモ
Macならbrew install ranger
で入る。homebrewほんと便利。
そんなrangerの設定というかカスタマイズで「多重起動を避ける」というのがある。
上記の参考サイトではrangerの多重起動を避けるという項目で説明されている。
ここで書かれている設定はbash/zsh向けなので、fishの場合の書き方をメモ。
rangerの多重起動を避ける
ranger() { [ -n "$RANGER_LEVEL" ] && exit || command ranger "$@"; }
fishでは次の定義を~/.config/fish/config.fish
に定義する。
(~/.config/fish/functions/
配下にファイルを作っても良いんだろうけど)
# ranger->サブシェル->rangerの多重起動を避ける function ranger if set -q -x RANGER_LEVEL exit else command ranger $argv end end
参考サイトにある通りRANGER_LEVEL
変数が定義されていればexitして、それ以外(初回)なら普通にrangerを起動する。
注意としてはサブシェルからranger /tmp
のように引数有りで起動してもexit
して元のrangerに戻るだけなので、引数で渡したディレクトリで起動しない。
rangerのサブシェルの場合はプロンプトに表示
[ -n "$RANGER_LEVEL" ] && PS1="(RANGER) $PS1"
fishでは次の定義を~/.config/fish/config.fish
に定義する。
(~/.config/fish/functions/
配下にファイルを作っても良いんだろうけど)
# rangerのサブシェルから起動された場合はpromptを変更する if set -q -x RANGER_LEVEL set -g _origin_prompt (fish_prompt) function fish_prompt echo "(RANGER)$_origin_prompt" end end
- 上記と同じように
RANGER_LEVEL
が設定されているかどうかで、通常のシェルかrangerサブシェルかを判断 (fish_prompt)
の実行結果、つまり既存のプロンプト表示を変数に保持する-g
でグローバル定義にしないとfish_prompt
関数から見れなかった。ちょっと気持ち悪い
fish_prompt
を再定義する
これで↓のように(RANGER)
が付いた状態でプロンプトが表示される。
(RANGER) ~/.config/fish ⟩