あんパン

こしあん以外は認めない

docker-compose run --rm app bundle exec rails cがうごかなかったやつ

これ。

masawada.hatenadiary.com

Dockerfileで

FROM ruby:2.4.2

...

ENV BUNDLE_PATH /bundle

としていたけど、ruby:2.4.2の方はこういう状態で、$BUNDLE_BINがあらぬ方向を向いている状態になってたっぽい?なぜかruby:2.4.1だとうまく行ってたんだけどナ。

正確には、bundler 1.15.4までは大丈夫で、1.16.0からは動かなくなっていた。まだdiff見ていないけどそのあたりでちゃんと各種PATHを見るようになったとか、そういう話かもしれない。

ということで、BUNDLE_PATHを設定するのをやめて、bundle用のvolumeを/usr/local/bundleにマウントするようにしたところ、正常に動かすことができた。

サポーターズさんの勉強会で、はてなブログの開発フローなどの話をします

来る12月20日、サポーターズさん@東京の勉強会にご招待いただき、『はてなブログの実例で学ぶ「はてな流」大規模Webサービス開発の勘所』と題して、はてなブログで実践している開発・運用Tipsなどの話をします。

supporterzcolab.com

完全に煽ったタイトルをつけておいて恐縮ですが、新しいプロジェクト始めるときはちゃんとキックオフしようね、とか、当たり前のことを当たり前にやりましょうね、みたいな、そういうノリの話をします。多分。大枠は決まってるけど、話す内容の詳細は詰めているところです。

プロジェクトを進めるにあたって、当たり前にやった方が良いことを見落としがちだったり、そもそもそういう意識がなくてやらずに進めてしまうことは多いと思います。ので、そういうやつの再確認をしましょうという意味合いがあります(本当か?)。あとは会社やチームによって意識ポイントが違ったりして、そういう差異が確認できて面白いという聴き方もあるかなと思います。どうかな、わからん。

既に15名の方々に参加登録をいただいています。是非みなさんも奮ってご参加ください。基本的に学生を含めた若手エンジニア向けの勉強会なんですが、あまり技術技術した話にはしないつもりでいます。なので若手ディレクタとか若手プランナみたいな人も歓迎です。

暇な人は終わったら近くの鳥貴族とかで打ち上げしましょう。

Google Homeで血圧を記録できるようにした

f:id:masawada:20171011024618p:plain

Google Homeを購入したのは日記に書いたとおり。

ぼくは高血圧ぎみで薬を飲んでいて最近は毎日血圧を測っているのだけれど、億劫で記録はしていなかった。いちいちノートにつけるのは面倒だしスマートフォンを開いてメモアプリを立ち上げるのも挫折した。

Google HomeならIFTTT連携でGoogle Spreadsheetに血圧を記録できるのではないかということに気づいたので、早速やってみた。

Google Assistantをトリガーにして出力先をGoogle Spreadsheetのadd row to spreadsheetに向ける。全体の設定としては以下の通り。

f:id:masawada:20171011022241p:plainf:id:masawada:20171011022244p:plain
IFTTTの設定

これで「ねえGoogle、血圧80の120を記録して」と発話すると、Google Homeが 80の120 の部分を抜き出してGoogle Spreadsheetに記録してくれる。上記の設定だとGoogleフォルダが自動で作られて、その中に血圧マスタというSpreadsheetが作られる。ここのB列に血圧が80 の 120といった形で記録される。

一応CreatedAtを記録するように設定しているのだけど何故かうまく記録されないので、この動画の通りにやる。

www.youtube.com

具体的には、血圧マスタのScript editorを開いて

function addDate(e) {
  var lr = SpreadsheetApp.getActiveSheet().getLastRow();
  SpreadsheetApp.getActiveSheet().getRange(lr, 1).setValue(new Date());
}

このスクリプトを保存してトリガーを追加する。トリガーの設定は以下の通り。

f:id:masawada:20171011022807p:plain
トリガーの設定

ここまでで血圧のマスタが完成する。

f:id:masawada:20171011024201p:plain
血圧マスタにGoogle Homeから入力されたデータの様子

どうも同一のファイルに別シートを追加するとIFTTTでうまく行を追加してくれなくなってしまうようだったので、このマスタとは別にデータを整形するためのスプレッドシートを作成する。

新たなシートを作成してSheet1とSheet2を作成しておいて、Sheet2の方にはA1のセルに=IMPORTRANGE("https://docs.google.com/spreadsheets/.../edit","Sheet1!A:B")を入力する。URLの部分は、血圧マスタのスプレッドシートのURLを入れる。これで血圧マスタから整形用シートのSheet2に全く同じデータを取り込むことができる。

Sheet1にはA列が時刻、B列が下の血圧、C列が上の血圧という感じで整形してデータを入れる。1行目には見出しを書いておいて、A2には=Sheet2!A1と入れる。B2には=IF( ISBLANK(Sheet2'!B1) , "", SPLIT(Sheet2!B1, " の "))と入れる。最後にA, Bそれぞれの列について下の方まで内容を複製する。セルを複数選択してcmd+dで複製することができる。A1とかB1とかの数字部分が行に応じて勝手に書き換わってくれて便利。

これでデータをうまく整形できたかと思う。

f:id:masawada:20171011024618p:plain
整形後の血圧の様子

あとはやる気の問題だけなので頑張って声を張って毎日血圧を記録していこう。


追記: Google Homeの認識がちょいちょい変わって、「80の114」というと「80 - 114」だったり「80 の 114」だったりするので、スクリプト部分を少し変えました。

function formatRow(e) {
  var sheet = SpreadsheetApp.getActive().getSheetByName('Sheet1');
  var lr = sheet.getLastRow();
  
  sheet.getRange(lr, 1).setValue(new Date());
  
  var value = sheet.getRange(lr, 2).getValue();
  var formattedValue = value.toString().replace(/\s(から|-)\s/, ' の ');
  sheet.getRange(lr, 2).setValue(formattedValue);
}

自分のサイトを作り直した

https://masawada.me

もともとGitHub Pagesで管理していたが、そろそろHTTPSで配信してもよかろうと思い立ってCloudFront+S3で配信することにした。おおよそいつもの構成。

↓このリポジトリで管理していて、masterにコミットが追加されるとCircle CIで勝手にビルドしてデプロイする。いままでHTMLとCSSをベタで書いてたけどEJSとSass使うことにした。みどころはそれくらいなはず。見た目自体は全く変えていないけど中身は丸々書き直した。

設定にあたっては、307 Temporary RedirectでS3のURLに飛ばされて困った。正確にはbucket-name.s3.amazonaws.comがデフォルトでus-east-1を向いていて、実際のS3 bucketのregionと異なる場合はリダイレクトされるという仕様にハマって困った。

CloudFrontのoriginをS3に設定するとbucket-name.s3.amazonaws.comに自動で向くのでS3 bucketのregionがus-east-1以外のときに307が発生する。そのため独自ドメインをCNAME(Route 53ならAレコードのaliasでも)でCloudFrontに向けるとS3のURLへのリダイレクトが発生して期待通りにアクセスできない。

bucket-name.s3.amazonaws.comがus-east-1に向く設定はユーザからは変更不可能(のはず?)で時が経つと勝手に適用されるとのことなのだけど、数時間たってもなぜかうまくいかなかった。

masawada.meは東京リージョンのS3 bucketを使っているので、今回はbucket-name.s3-website-ap-northeast-1.amazonaws.comというようなURLをCloudFrontのoriginとして設定したところうまく見られるようになった。

若かりしころはFetchというFTPクライアントでちまちまHTMLとかをアップロードしていたので、こういう構成の静的ウェブサイトを作るときはいつも隔世の感を感じながら作業している。

Let's Splitを組み立てた

大抵のエンジニアにとってキーボードは、触らない日はないというほど身近なインターフェースだと思う。いままでHHKBやErgoDoxなど自分に合う鞍*1を探してきたわけだけれど、自分で作ってみるというのもアリなのではないか、とふと思い至った。

しかし、電子工作の経験もそんなにない自分がいきなり設計から始めてひとつのキーボードを完成させるのは途方もなく壮大な計画に思えたので、まずはキット的なものを探して自分で組み立ててみることにした。そうして調べた結果、Let’s Splitというキーボードを組み立てることになった。

Let’s Split

普段生活しているとなかなかお目にかかることはないけれど、世の中には40%キーボードといって、キーが48個しかついていないキーボードがある。レイヤーを切り替えて1つのキーに複数の役割をアサインして使う。

Let’s Splitはredditの/r/MechanicalKeyboards/のユーザWootpatoot氏によって作られた左右分離型40%キーボードで、PCBが市販されており、ダイオードやらスイッチやらを実装して組み立てることができる。

材料を購入する

ここを参考にして揃えるとよい。

github.com

Part Cost Shop
PCB $10 switchTop
TRRS Jack *2 $3 switchTop
Switches *60 $15 switchTop
Keycaps $49.06(送料込) pimpmykeyboard
Diodes 1N4148 *500 \500 秋月
Tact Switch *2 \10 秋月
Pro Micro *2 \1,200 (送料込) Amazon
Plates $65.17 (送料込) Ponoko
M3 Standoffs & Screws $5.36 (送料込) AliExpress 1 2
  • 秋月の送料は500円
  • switchTopの送料は$8.50

かかった費用の合計としては、このときのレートで19,919円(送料込)であった。このほか、はんだごてやら何やらと機材を揃えたので、3万程度にはなっていると思う。

タクトスイッチはPro MicroのRSTとGNDに実装して、ファームウェア焼き込み時のリセットボタンとして使う。

ケースはこの部分を参考に、Ponokoで注文した。letssplitv2.svgはどこにも見当たらないのだが、QMK Firmwareのリポジトリの古いコミットに存在する(大分無理やりっぽい)。他、どこかにある感じだったら教えてください:bow:

$ git clone git@github.com:qmk/qmk_firmware.git
$ cd qmk_firmware
$ git checkout ca01d94005f67ec4fa9528353481faa622d949ae
$ ls keyboards/lets_split/imgs/
 left.stl  'lets split rev2 case.iges'   letssplitv2.svg   plate.stl   right.stl   split-keyboard-i2c-schematic.png   split-keyboard-serial-schematic.png

ネジとスペーサは良いやつを買った方が良いと思う。今回買ったやつは使えないこともないのだけど、質が悪くて、ミリネジとインチネジがあっていない時みたいな舐め方をする。また、スペーサが10mm指定だけど、高くて良いのであればもう少し長めにしてネジも長いやつにしたほうが良いと思う。ネジが短すぎて留めるのに苦労した。

キーキャップは、単体で買うよりもTKL Base Setで買う方がお得だった。無刻印で良い場合は、とにかくx1のキーが48個以上あれば良い。

これとは別に、TRRSケーブルも購入した。

また、ネジが若干出っ張っており安定感に欠けていたので、ゴム足も購入した。

3M しっかりつくクッションゴム 7.9x2.2mm 丸形 22粒 CS-01

3M しっかりつくクッションゴム 7.9x2.2mm 丸形 22粒 CS-01

組み立てる

以下の順序で実装した。

  • ダイオード
  • TRRSジャック
  • ピンヘッダ
  • キースイッチ
  • Pro Micro

インターネットにたくさん情報があるので、そっちを参考にすると良いと思う。気をつける点としては以下くらい?

  • ダイオードの導通試験できればやりたい
    • Prime Nowでテスター買った
  • ダイオードは黒線(カソード)が四角い方(GND)になるように実装する
    • Double check your work. Black lines should be facing the square pad. とのこと
  • Pro Microを実装する前にキースイッチをはんだ付けして足を切る
  • Pro Microの向き(上下)

あと、ネジ留めをする際に、Pro Micro横に設置するスペーサが基板の一部と干渉してうまく入らないことがあった。当該部分は結構ギリギリまで穴が合いていて脆い感じだったので、ニッパーで切り落としてスペーサを設置した。

ファームウェアを焼く

焼き込みにあたって、AVRの環境が必要。

$ brew tap osx-cross/avr
$ brew install avr-gcc
$ brew install avrdude

avr-gccは入れるのに30分弱くらいかかる。インストールを終えたら、QMK Firmwareを手元にcloneしてきて、自分のキーマップのディレクトリを切っておく。あとでカスタムするとして、とりあえずはdefaultからコピーする。

$ git clone git@github.com:qmk/qmk_firmware.git
$ cd qmk_firmware/keyboards/lets_split
$ cp -r keymaps/default keymaps/USER_NAME

avr-gccでhexファイルを生成してから、PCとPro MicroをmicroUSBケーブルで繋いでPro Microに焼き込む

$ make rev2-USER_NAME
$ make rev2-USER_NAME-avrdude

という具合。make rev2-USER_NAME-avrdudeを実行するときに、Pro MicroのRSTとGNDをショートさせる必要がある。今回はタクトスイッチを実装していて、押すだけでショートできるようにしていた。モノによって素早く2回ショートだったり、長押しだったりするっぽい。自分のは長押しだった。

これでうまく動けば完成。動作確認はkeycode.infoで正しいkeycodeが取れているかどうかを見た。

参考

YAPC::Fukuoka 2017 HAKATAを見にいった

ブログを書くまでがYAPCということで、もう一週間以上前のことになるのだけど、そのときのことを書く。(月火と京都出張だったり、今週はなにかとイベントがあって遅れたのじゃ…)

yapcjapan.org

YAPCはYet Another Perl Conferenceの略で、一般社団法人 JPAが主催する、Perlを軸としたITに関わる全ての人のためのカンファレンスです。

です。

トーク

オープニングからずっとA会場に居る、ランチセッション後はずっとB会場に居るという感じでトークを観覧した。いったりきたりすると席の確保が面倒なのでわりとこうなりがち。

見た中からいくつか良かったトークについて書く。

分散ユニークID採番機katsubushiとWebアプリケーションへの応用例

個人的に今回のベストトーク。Golangで書かれた分散ユニークID採番機katsubushiが生まれた経緯とその仕様紹介、単純にレコードのidとして使う以外の応用例などの紹介。

単一のリクエストにidを発行して引き回して各種ログに記録するというのが良かったなと思った。スライドにもあったけど非同期のジョブが落ちた時の調査は割と困難で、どのリクエスト起因なのかなどが分かるのは良さそう。(今ならこれだけであればkatsubushiを使う必要はなくて、Nginxの$request_idを引き回すで良さそうという話もありそう。)

Web application good error messages and bad error messages

良いエラーメッセージとは、悪いエラーメッセージとは、というトーク。アプリケーションを作る上で、例外を吐く時のメッセージとか、何らかの問題が発生したときにユーザ向けにその旨を伝えるためのメッセージを書くことは避けられないわけだけれど、きちんと意識しないとふんわりとしがちで、そのメッセージが実際にどのように役に立つのかなどを考えずにいると、中身がなかったり不十分だったりする文字列が並ぶことがある。

トークでは、エラーメッセージについて次元という分類でレベルを整理されてたのが良かったなと思った。

最近実際に、1年くらい前に書いたエラーメッセージが情報量が少なくて何を言っているのか全く意味がわからんぞということがあり、共感があってよかった。

個人的な意見なのだけれど、エラーメッセージを中途半端に高次元にする必要はないと思っている。ここでいう中途半端な高次元というのは、対処方法や解決手段が提案されているということ。

これは心理的な問題で、素人PCユーザ気質が抜けないので、対処の手段が分かってるならそうしろや、という気持ちになってしまいどうにもストレスが溜まる。例えば、gitのサブコマンドをtypoしたときに出るDid you mean this?みたいなのが出るのが苦手だ。

そこまでわかっているなら修正しますか?と聞いてきて、1クリックで修正したコマンドが発行できるとうれしい。発表中にはなかったけど、これが一番の高次元であるといえるのではないか。(もちろん、これがエラーメッセージの範疇に入るのかという話題もありそう。)

例えば、僕のシェルとかはそうなっている。

[wadamasayoshi@sibelius] $ vmi hoge.txt
vim is correct? [n,y,a,e]:

とはいいつつもちろんそこまで高次元にするコストが払える場面が少ないことも理解しており、かつ簡潔に何が起きているか分かるだけというよりかは解決手段まで提示されているほうが良いので、3次元的なエラーメッセージを記述する程度にとどまることは十分に頷ける。

新時代のテストフレームワークTest2

弊社akiymさんのトーク。Perlのテストフレームワーク、Test2の紹介とハマりどころの説明。先日、とあるツールをcpanmでいれたところ勝手にTest2がインストールされて手元のテスト環境が完膚無きまでに破壊されてしまうという現象が起こっていて、トークを聞いたところ「あぁ、これ見たことあるやつだ……」というのが2,3回くらいあって面白かった。Test2便利そうだったので使いたい。

Test::Deep的な記述ができるakiymさん謹製のモジュールTest2::DeepLikeがかなり強力そうで、これぞ顧客の求めていたものなのでは…と思った。

github.com

その他

飛行機

飛行機あまり乗ったことがなくて、沖縄に修学旅行で行ったのが最初で最後だったので、一人で乗るのは初めてだった。当日家を出る1時間くらいまでなんの準備もしておらず、準備を始めてから国内線の機内持ち込みが可能なものを調べたりとか手荷物の数に制限があることとかを知ったりして、とにかく大慌てで準備した。今度はもう大丈夫だと思う。

あと、高校生のときに気胸やったんですが、さすがにもう大丈夫でした。

宿

飛行機と2泊の宿併せてパックでとって3.2万くらいで、正直もう少し金積んでおけばよかったかなという気分。

本当は博多駅近くの宿を取りたかったのだけど、既に埋まっていて大濠公園という駅(博多から数駅)の宿になった、という話を同僚のid:papixさんにお話したところ「人権はある宿ですよ」という含みのあるコメントを頂いたので、実際に行ってみたところ、0.8人権程度であった。

微妙な香りがしたり、風呂が異常に狭かったり、操作可能な空調がなかったり、物理鍵&オートロックなしだったりというのが耐えられるのであれば良いと思う。。。まあ安いので今回はよしということで。

ご飯

ご飯の方は期待通り美味しかったです。ラーメンだけ食べ損ねた。

前夜祭チケット取れなかった組で寿司食べに行ったり、そのあと前夜祭組と合流してラーソーメン食べたり。YAPC翌日は駅でお土産かったり近くの飲み屋でいろいろ飲んだりした。

ロッカー

よし帰るぞということになって、衣服とか預けてたロッカーまで戻ったら鍵番号が書いてあるレシートを紛失した。最近、物理鍵ではなくて、預けた時に出てくるレシートの鍵番号または交通系電子マネーで解錠するタイプのロッカーがあるけど、預けたところは電子マネーが使えなくて、仕方なく番号入力するタイプで預けた。

が、見事にそのレシートを紛失した。係員の人に電話して20分ほど待ったところ、中身の確認とサイン+電話番号で荷物を回収できた。気をつけましょう。

次回

来年2018年3月3日に沖縄の恩納村ということで、楽しみですね。今度は是非ともトークしたいと思うのだけど、倍率高いんだろうなー。

yapcjapan.org

axiosでHTMLを取得してきてappendChildで埋め込む

普段axiosを使うときはレスポンスがJSONなAPIを叩くことが多いが、HTMLを取得して挿入することもあると思う。innerHTMLで直接挿入することもできるが、responseTypedocumentにすることでappendChildで要素を挿入することができる。

import axios from 'axios';

axios({
  method       : 'GET', 
  url          : '/api/response/as/html', 
  params       : { hoge: 'fuga' }, 
  responseType : 'document', 
})
.then((res) => {
    const body = document.querySelector('body');
    Array.from(res.data.body.childNodes).forEach((node) => {
      body.appendChild(node);
    });
});