meishi キットで自作キーボードに入門した
自作キーボードが作りたくて遊舎工房さんに行った。 yushakobo.jp
Ergo42 Towel と meishi キット を購入。 買うときにいろいろと教えてくれたり、こちらのふわっとした相談に乗ってくれたりしてありがたかった 🙏
meishi キットは練習用に買ってみた。 ハンダ付けとか10年くらいやってないし、いきなり作り始めるのも危険そうだったので…。
小さなキットでもキーボードに必要な最低限の要素が詰め込まれていて、入門用として最適だった。 案の定つまづくこともあって、作っといてよかったなーと思った。
キースイッチは、左から
- Orange Healios
- Blue Zilent v2 67g
- Kailh Box White
- Kailh Choc (ロープロファイル) Brown
色々試せて楽しい。
Pro Micro の MicroUSB 端子はもげやすいという噂を聞いたので、マグネット式のケーブルも買った。
キーマッピングは今のところ何が便利か思い浮かばず、とりあえず Slate で割り当てていたウィンドウ切り替えに対応させてみた。
便利…か?
https://docs.qmk.fm/#/keycodes あたりを見て、 qmk_firmware の keyboards/meishi/keymaps/default/keymap.c
を変更してファームウェアをビルド&書き込み。
お手軽。
さあ次は Ergo 42 Towel を作っていくぞー。
Rails Developer Meetup 2019 に参加してCSSの技術的負債について発表した
発表内容
https://railsdm.github.io/#session-upinetree
「CSSの技術的負債との向き合い方」というタイトルで、普段のチーム開発で目をつぶりがちなCSSをテーマに話しました。
プログラマからの視点で、どういう流れでCSSが負債になるのか、その負債に対してどう取り組んで行けるか、などを色々考察してみたという内容です。
質疑応答: https://railsdm.herokuapp.com/issues/118
スライド
日本語版
英語版
動画
(怖くてまだ自分自身で見られていません…)
感想
以下、感想を思いつくままに並べてみます。
全体
- とても楽しくて濃い2日間だった
- この規模のイベントを運営されたカルパスさんすごい
- 動いているDHH氏を見られてよかった。準備もなしに、饒舌に自分の意見をスラスラ言っていたのが印象的だった
- 発表が終わった後は他のセッションに気が向くまま参加。疲れていたのもあって休憩していた時間も多かった
- 2日間共おいしいコーヒーが提供されてとてもQOL高かった
- これからアップされた資料や動画をゆっくり見て行きたい。RubyKaigiもあるのでそれまでには…
登壇
- ランチでの現場Rails本のトークセッションから続けての登壇。1時間以上スクリーンの前にいることになってしまったので、結構気疲れした。発表終わってからしばらく放心してた
- B会場は110人入るとのことだったけど、だいたい席が埋まっている感じで、まさかこんなに多くの人の前で話すことになるとはという感じ
良かったところ
- 緊張はしていたが、話し始めてからはリラックスしていられたと思う
- ペースを意識しながらやれた
- スライドの途中にここまで何分でできたら急ぐ、みたいな目安を決めていた。30分ぴったりで終わって我ながらよくやった
- 良かったという感想が多くてホッとした
- CSSは専門領域じゃないので、何適当なこと言ってんだみたいな反応ないか怖かった
- 関心の谷っていうメタファーいいよねといろんな方に言われてうれしい
- 全体の構成を紙に書いて考えた
- 30分ともなると箇条書きで考えるのつらそうだったので
- マインドマップ的なやつとか、色々絵を書いた
- 英訳をがんばった
- 必須ではないし、会場に英語話者の方がいらっしゃっていたかは不明だけど、チャレンジしてみた
- 海外でのチーム開発や職責の事情は違うのかもしれないけど、リーチできるってのは良いことだ
改善できそうなところ
- 詰め込みこみすぎた
- ペースを早めにしないと時間が足りなくなってしまった
- 息継ぎが少なめになってしまって結構苦しかった
- ブラッシュアップ期間が取れなかった
- もう少し余裕を持てたら良かった
- ブラッシュアップしたらシンプルにできて発表時間に余裕を持てたかもしれない
- 負債自体は悪くないんだよという話も入れたかったなー
- スライドが文章だらけになってしまった
- 何を話すか思い出すためでもあって、これもブラッシュアップできなかった影響
- 英訳にかかる時間を甘く見ていた
- 結果として途中まで英語という中途半端な形で発表することに…
今後やってみたいこと
- Stylelint で橋の使われ方のチェック
- 発表では「橋が巨大になるに連れて使われ方のチェックが大変になる」という話をしたが、そのあたりも自動化できたら良さそう
- 谷を埋める方法の掘り下げ
- みんなでがんばっていくぞ的な抽象的な内容で終わってしまった
- じゃあその分のコストはどうするのとか、やりたくない、やらないほうが価値を発揮できる人にもやらせるのかとか、色々議論したい
- 他の領域での関心の谷
- CSSに限らずあると思うので、色々ディスカッションしたい
- まずは認識することが大事で、そこからチームで踏み出せていけたらなと思う
自分の結婚式用にサーバレスな招待状Webアプリを作った
結婚しました
昨日無事に結婚式できました。天候に恵まれて最高でした! pic.twitter.com/BZTyzK8ceE
— Takuya Matsumoto (@upinetree) 2018年12月2日
2018/12/1 (土) に結婚式を行いました。天候に恵まれ、穏やかな晴れの日で式を挙げられて本当に良かったです。私のことなので直前で体調を崩すか怪我をするか天気が崩れるかのどれかはあるんじゃないかと思っていたのですが、何もなく無事に終えることができて安心しました。
我々夫婦のために多くの方々が集まってくれてとても嬉しかったです。遠くからはるばる来てくださった方もいて、本当にありがたい限りです。
よちよち.rb からも電報をいただいてしまってびっくりしました。 curl
から始まり | ruby
で終わる電報をいただく(そして困惑する司会の方がゲストに促されそれを読み上げる)機会はそうそうないでしょう 😂 お忙しい中、粋な計らいをありがとうございました。
披露宴の内容やアイテムにはかなりこだわって、我々らしいオリジナリティのある結婚式になったと思います。夫婦で演奏したりもしました(私がギター、妻が鍵盤ハーモニカ、ヴィブラフォン、マリンバ)し、謝辞にLTしたりもしました。
現場Rails本の執筆と並行しての準備となってしまってかなりきつかったけど、妻に主導してもらってなんとかなりました。本当に感謝。そしてようやく締切に追われない日々が訪れ、ブログを書くこともできるようになりました。
これから夫婦ともどもよろしくお願いいたします。
(というわけで例のリストです)
招待状Webアプリ
結婚式のこだわりのひとつとして、招待状の回答をWebアプリでできるようにしてみました。せっかくなのでその紹介を書いてみようと思います。
なぜ招待状の回答をWebアプリでできるようにしたかというと、従来のはがきでの回答には以下の扱いづらさがあったからです。
- 「御」を打ち消したりといったマナーがたくさん。ゲストに手間をかけさせたくない
- 当日の情報を見たいときにすぐ見られない。地図の検索などの一手間が必要
- 返信用切手の購入や添付が必要
- 回答の集計が手入力で大変
一方で、はがきには紙の良さもあります。色々とデコったり、文字でメッセージを伝えたいといった需要もありますし、モノとして手元に残るのはうれしい点です。
そこで、今回は以下のような運用にしてみました。
- 招待状は紙で送付。ゲストカードと、招待状アプリの案内カードを同封
- 招待状アプリの案内カードには、アクセスするためのQRコードと、ログインパスコードを記載
- 招待状アプリで回答していただく
- こちらが集計スクリプトで回答結果をチェック(毎日のたのしみ)
QRコードの用意やログインパスコードの添付など、物理との接続のための工夫はかなり苦労しました。正直この作業中ははがきのほうが数倍楽だったのではとも思いましたが、苦労した分だけ楽しみつつも総合的に満足できるものができたかなと思っています。
アプリケーションの構成
公開用に個人情報とか抜いたリポジトリを作ってみました。
https://github.com/upinetree/wedat-demo
今回は前々から興味があったサーバーレスSPAでやってみました。AWSを中心に以下のような構成にしました。
なお、これらの構成は Terraform で管理しました(勢いでかなり雑に…)。
今回はSPAということで、フロント周りはReactで組みました。CRAでシュッと作って、その上に以下の色々を乗せた感じです。
- Redux
- Redux Form
- React Router
- React Transition Group
- Luxon
- Styled Components
- Tailwind CSS
- AWS Ampify
なお、CRAの Service worker は途中まで使ったり整備したりしたのですが、キャッシュの更新とかの懸念点の解消が公開に間に合わず、泣く泣く外しました…。
作ってからCRAもAmplifyもかなり大きなアップデートが入っていて、追従できていないのも心残りではあります。作って利用してもらうことが最優先だったので、目的は達成できたということで良しとしています。
やってみた感想
Webアプリにしてみて感じたのは
- 紙面のような限界がないので、長文でコメントくれる人も結構いてうれしかった
- 結構びっくりしてもらえた。製品かと思わせることに成功
- 集計がめっちゃ楽。毎日集計結果とメッセージを確認できるのでモチベーションになる
- ゲストに名前を入力してもらうので、名前の正確な漢字に手書きよりも確実に気付ける
のあたり。やってよかったな〜と思いました。
技術的な感想としては
- サーバレスSPAめっちゃ低コストで月に多くて100円程度しかかからない。運用もほとんど気にしなくていい(一応トラッキングはしていたが、安定してからは本当に手が離せて助かった)
- CRAやAmplifyのレールからはずれたいときに大変だった。結局ソースコードを読みに行った
- Amplifyが提供するReactコンポーネントはカスタマイズがあまりできなくて、上書きしたりしたり、同じような機能を持つコンポーネントを自作したりした
- Styled Components 最高だ。Tailwind CSS でざっくりマークアップして、まとまりがでてきたらコンポーネント化するというパターンがかっちりきた
- API Gateway でとても苦労した。Terraformでの管理が一つ一つのリソースに対して記述量が多くて大変だった。マッピングテンプレートのVTL (Velocity Template Luangage) の記述方法でもめっちゃハマる。これでサーバーレスで柔軟なエンドポイントを定義できると考えて、トレードオフとしてはありのようななしのような…?代替手段があればまずそっちを選択すると思う
- Cognito もちょっとレールからはずれた使い方をするととたんに応用が効かなくて苦労した。次は Auth0 とか Firebase auth とかも使ってみたい
- (最終的には採用しなかったけど)Service worker は、キャッシュの扱いで苦労した。キャッシュはCDNにあるのかSWにあるのかブラウザにあるのか、どこの設定が効いているのか、というところ。この規模のアプリのSWキャッシュでそんなに苦労に見合うメリットはないと判断した
- それぞれのサービスやフレームワークを組み合わせたベストプラクティスがなくて、その都度自分で判断しないといけなかったのは大変だったけど楽しかった。普段書いてるRailsは(レールに乗っていれば)だいぶ楽できるなと再確認
技術的な方はその都度アウトプットできたら良かったのですが、執筆という巨大なアウトプットの最中で断念しました。まだ使える知識がありそうだったらこれから出してみようかと思います。
Minitest の Unit style で DSL 風な around ヘルパーを定義する
Rails の ActiveSupport::TestCase
を使っているとして、サクッとミニマムにやりたい。
minitest-around を使うと、以下のような around メソッドを定義することで setup/teardown をセットで実行できる。
require "test_helper" require 'minitest/around/unit' class CatTest < ActiveSupport::TestCase def around(&block) p "konnichi nyan" block.call p "sayouna nyan" end def test_nyan # ... end end
でも、なんとなく ActiveSupport::TestCase
が用意している setup
, teardown
と揃えてDSL風に使いたい。
require "test_helper" require 'minitest/around/unit' class CatTest < ActiveSupport::TestCase # こうしたい! around do |test| p "konnichi nyan" test.call p "sayouna nyan" end # これと同じ感じ setup do @cat = Cat.new end end
なので test_helper.rb
でこうする。
class ActiveSupport::TestCase def self.around(&block) define_method(:around) do |&test| instance_exec(test, &block) end end end
Unit style でどこまで複雑なテスト設計に対応できるかは置いておいて、ちょっとした便利ネタでした。
Docker + Rails開発環境Tips
普段なんとなくで設定していたのですが、今の所こんな感じでやるのがいいんじゃない?というのをそろそろまとめたくなったので書いてみました。 もっといい方法があれば教えてください〜。
bundle install の実行のたびにDockerイメージのビルドし直しになるのを回避する
Quickstart にあるような構成では、以下のように Dockerfile で bundle install する。
FROM ruby:2.5.3
RUN mkdir /app
WORKDIR /app
RUN gem install bundler
RUN bundle install
この構成はgem一式を含めてポータブルにするという意味では合理的なのだが、開発環境においてはそうでもない。なぜなら、開発環境ではGemfileの更新やgemのアップデート、Dockerfileの修正が頻繁に発生し、そのたびに各開発者のローカルマシンでのイメージのビルドが必要になってしまうからだ。
これを回避するには、イメージの外、つまりコンテナで bundle install を行って gem を保存すればよい。ただし、コンテナを破棄した際に gem も消えてしまうと困るので、キャッシュできる必要がある。そのためには以下の方法がある。
- bundle install 用のディレクトリをホストと共有し、そこに格納する
- gem 用の volume を用意し、そこに格納する
前者のほうがお手軽だが、後者のほうがディスクアクセス的に有利そうではある(未計測)。もしホストと共有したくないとか、パフォーマンス上問題になってきたら後者に乗り換える形でどうだろうか。
bundle install をコンテナで行い、そのディレクトリをホストと共有する
BUNDLE_PATH
でインストール先をホストと共有するディレクトリ(この場合は /app
配下の /app/vendor/bundle
)に指定。
FROM ruby:2.5.3
ENV APP_PATH=/app
ENV BUNDLE_PATH=$APP_PATH/vendor/bundle
RUN bundle config path $BUNDLE_PATH
なお、 Bundler は BUNDLE_PATH
, .bundle/config
の順で優先してパスの設定を読み込む。そのためどちらかを指定していれば問題ないが、まれに .bundle/config
の設定がないとRailsが require 'bundle/setup
したときに BUNDLE_PATH
を見てくれないことがあった。そのため上記の例では両方設定している(原因特定できておらず、環境要因かもしれないので要確認)。どの設定が効いているかは docker-compose run --rm app bundle config
で確認できる。
docker-compose.yml
は以下のように /app
配下をホストにマウントする。 vendor/bundle
を delegated
フラグ付きでマウントすると高速に同期できる(ただし、コンテナ→ホストへのWriteパフォーマンスが犠牲になる。bundler配下ではほぼ発生しないので問題なし)
version: '3'
services:
app:
build: .
command: ['./bin/rails', 's']
tty: true
stdin_open: true
ports:
- '3000:3000'
volumes:
# cached/delegated options for performance tuning
- .:/app:cached
- ./vendor/bundle:/app/vendor/bundle:delegated
- ./node_modules:/app/node_modules:delegated
gem 用の volume を用意し、そこに格納する
BUNDLE_PATH
はマウントされないところにしておく。
ENV APP_PATH=/app
ENV BUNDLE_PATH=/bundle
RUN mkdir $APP_PATH
WORKDIR $APP_PATH
そして docker-compose.yml
でボリュームを用意し紐付ける。
version: '3'
services:
app:
volumes:
- bundle:/bundle
volumes:
bundle:
# use default driver
binstub も考慮したい場合は、以下のようにすると良いらしい。これがなくても手動で binstub を生成する(そしてリポジトリに含める)ことは可能な気がするが、この方法の便利なところは entry point を定義しておくと docker-compose up
のたびに最新の状態に保てることだろう。そういう意味では、 bin/setup
を entry point に登録しておくと便利なのかもしれない。
https://medium.com/@jfroom/docker-compose-3-bundler-caching-in-dev-9ca1e49ac441
デバッガによる対話コンソールを有効にする
よくある docker-compose.yml
では、以下のように docker-compose up
でRailsサーバも一緒に起動するようになっている。
services:
app:
build: .
command: ['./bin/rails', 's', "-b", "0.0.0.0"]
この設定だと標準入力が閉じられているので、 binding.pry
や binding.irb
のような対話コンソールによるデバッグが行えない。従って以下の設定を追加する。
services:
app:
build: .
command: ['./bin/rails', 's', "-b", "0.0.0.0"]
tty: true
stdin_open: true
ただし、 docker-compose up
でコンテナを起動した状態では、デバッガが起動した内容のログを確認できても、対話コンソールにアクセスできない。Railsサーバのコンテナは docker-compose up
のプロセスの後ろにいるためのようだ。従って一工夫する必要がある。2通りの方法を紹介する。
Railsサーバのコンテナに attach する
docker attach
コマンドを使うと、起動中のコンテナの標準入出力に接続することができる。これをRailsサーバのコンテナに利用する。具体的には次の操作を行う。
- デバッガのブレークポイントを仕込んで、そこを通る処理を実行する(処理を通って対話コンソールが待機状態になっているかどうかは
docker-compose up
のログ、もしくはdocker-compose logs
でも確認できる) - 別のターミナルで
docker attach $(docker-compose ps -q app)
を行って接続。接続直後は何も出力されないので、エンターキーを押して対話コンソールが起動していることを確認すると良い - デバッグを行う
- 対話コンソールを終了する(
ctrl-d
やquit
などで) - コンテナへの接続を解除する。
ctrl-p ctrl-q
を連続して入力(デフォルトの設定。docker attach --detach-keys="<sequence>"
で変更可能)。うっかりctrl-c
を押してしまうとコンテナごと終了してしまうので注意
このように、Dockerを利用せずにローカルでサーバを起動する場合とは全く感覚が異なる。特にチームで作業する場合などでは、効率を上げるためにも bin/attach
のようなスクリプトを作っておくと良さそうだ。
#!/usr/bin/env bash
echo attach to $1 ...
docker attach --detach-keys="ctrl-c" $(docker-compose ps -q $1)
次のような起動スクリプトでさらにラップしても良い。
docker-compose up -d
bin/attach app
Railsサーバのコンテナだけ docker-compose up とは別に起動する
attach を利用する方法で以下のような懸念がある場合には、Railsサーバを独立したコンテナで起動すると便利だ。
bin/attach
のようなスクリプトを作っても、意識のアップデートが必要。Dockerに慣れていない場合にハードルがある- Railsサーバのコンテナに変更や試行錯誤が多く、再起動する機会が多い
up -d
で起動してstop app
⇒start app
で個別に再起動することはできるが、フォアグラウンドで起動、終了を制御したい
このためには、まず docker-compose.yml
で次のようにRailsサーバを起動しないようにする。ここではかわりに bin/update
を実行して環境の最新化を行うようにしている。
services:
app:
build: .
command: ["./bin/update"]
そして、Railsサーバは次のようなスクリプトで起動する(Makefileに入れるのが楽だ)。
docker-compose up -d
./bin/sleep_until_app_ready
docker-compose run --rm --service-ports app bin/rails s
注意点は —service-ports
オプション。 docker-compose.yml
で設定した ports が、 run
のときにはこのオプションがないと反映されない。
https://docs.docker.com/compose/reference/run/
The second difference is that the docker-compose run command does not create any of the ports specified in the service configuration. This prevents port collisions with already-open ports. If you do want the service’s ports to be created and mapped to the host, specify the --service-ports flag
途中で使っている ./bin/sleep_until_app_ready
は以下のような簡単なスクリプトで、 bin/update
が終了するのを待っている。
#!/usr/bin/env bash
while :; do
sleep 1
docker-compose exec db echo 'alive?' &>/dev/null
DB_UP=$?
docker-compose exec app echo 'alive?' &>/dev/null
APP_UP=$?
if [ $DB_UP -eq 0 ] && [ $APP_UP -eq 1 ]; then
break
else
echo -n "."
fi
done
echo "Ready for running app"
対話コンソールで利用されるページャ
ruby
イメージはデフォルトでは less を備えていないので、デバッガは ruby 実装の自前ページャを利用する。ローカルで動かしたときのように less で見たい場合は、インストールするように Dockerfile を修正すると良い。
Springサーバーを有効にする
spring-docker-example が参考になる。
基本的には以下のようにSpringサーバ用のコンテナを立てて、
services:
app:
build: .
volumes:
- .:/app:cached
depends_on:
- db
- spring
spring:
build: .
volumes:
- .:/app:cached
command: ["./bin/spring", "server"]
spring.sock
をコンテナ間で共有するだけ。そのために、 sock ファイルを tmp ディレクトリ配下(ホストにマウントして共有)に置くように、 config/spring_client.rb
(Springが起動時に読み込む)でその場所を教えることにする。環境変数なので、もちろん他の方法で指定しても良い。
ENV["SPRING_SOCKET"] = "tmp/spring.sock"
コンテナをまたいだ spring の自動立ち上げはできない(方法あったら教えてほしい)ので、適宜 depends_on: spring
を設定しておくと良い。
注意したい点として、 Springサーバのコンテナの環境変数を、Railsサーバのコンテナの環境変数と揃えておくこと。Springサーバのコンテナが異なる環境変数を利用し、それを元に各種定数を初期化してキャッシュすると、Railsサーバは異なる環境変数を元にした定数を参照することになる。例えば database.yml
にERBで環境変数を埋め込んでいる場合に、意図通りに設定されない可能性がある。
default: &default
host: <%= ENV.fetch("DATABASE_HOST") { 'localhost' } %>
同様の理由で、マウントするディレクトリやボリュームも同じ条件にしておくのが安全。Spring経由での Dir.glob
の結果が異なることがあるため。
なお、 spring-docker-example の例では、ホストとコンテナの両方で同じ spring.sock
を共有し、さらに SPRING_SERVER_COMMAND='docker-compose up spring'
を設定したりPIDのネームスペースをホストにしたりして、ホストからSpringを利用して bin/rails c
を行うことができるようにしている(というか、そもそもホストにRubyとSpringを入れて起動するのを前提にしているようにみえる)。ホストとの共有はまだ試したことがなくて、きっと spring コンテナの環境での Rails コンソールが立ち上がると思うのだが、本当に macOS 上の環境に依存しない( native extension の違いとか、 webpacker が実行する yarn check --integrity
の結果とかの影響を受けない)のか気になるところ。今のところはホストからの利用はせずにコンテナ上で完結するように設定しているが、そのうち試したい。
「現場で使える Ruby on Rails 5速習実践ガイド」の執筆に参加して現場の知見を書きました
この度、2018/10/19にマイナビ出版から発売される書籍「現場で使える Ruby on Rails 5速習実践ガイド」の執筆に参加しました。
書籍情報
今ご購入いただくと、購入特典として未収録PDFがもらえます!
https://book.mynavi.jp/pcbook/blog/detail/id=97187
特典は紙・電子本両方に付きます。配布は2018年10月24日(水)ごろになる予定です。
どんな本なの?
Ruby on Rails の基本的な使い方の解説から、複数人での開発やメンテナンスしやすい状態の維持などの実践的なトピックにまで広くカバーしています。
想定読者の中心を「他言語でのWebアプリケーション開発経験のある方」として設定していますが、初心者の方でも理解しやすいよう段階的に学べる構成に仕上げました。その結果480ページと盛り沢山なボリュームとなっており、索引から知りたい箇所を調べるといった辞書的な用途にも十分使えるかと思います。
本書の特徴
タイトルにもある通り、現場で得た知識や経験をところどころに散りばめています。例えば、チームでアプリケーションを開発するときにどうコードを共有してコミュニケーションを取るのか、長く生きることになりそうなアプリケーションの保守性をどう持続して行くのかという話題について、多くのページを割いて丁寧に説明しています。Railsを学習する最初のステップから発展して、実際に仕事で使うような次のステップでも本書が役に立つと思います。
読者が本書を超えて次のステップに進めるように、というのは特に重視した点です。そのための仕掛けとして、本書で解説はしないけれども大事だという概念はキーワードだけ出してみたり、脚注で補足したりと、興味があれば自力で調べて学習していけるようなきっかけを作っています。またRailsGuidesやAPIリファレンスなどを都度参照するようにして、Railsコミュニティの信頼できる情報ソースを見る習慣を付けられるようにもしています。
Rails 5.2 かつ Ruby 2.5.1 と、今出ているバージョンの最新に追従している点もアピールしたいところです。ActiveStrage や Credentials, Content Security Policy, Webpacker といった最近のRailsの機能にも触れています。「数年間Railsからちょっと離れちゃったけど、最近のRailsについて包括的に勉強し直したい」といった用途にも有用です。
逆に本書で扱っていない話題は、Railsアプリケーションの運用です。本当は触れたかったところではあるのですが、あくまでRailsの機能やRailsアプリケーションの作り方にフォーカスする形で限られた紙面を使いました(それでもなかなかのボリュームになってしまったのですが)。運用のために利用できるRailsの機能は紹介していますが、どこにどうやってデプロイするかといった話題は他の良書で学んでもらえたらと思います。
いくつかサンプルを貼るので参考にしてください。2色刷りで図もコードも見やすくなっています!
個人的な担当範囲と感想
2章、6章、8章、9章、10章を中心に関わりました。特に10章は本書の特色を仕上げる章にしたくて中心的に力を入れました。
執筆する上で意識したのは、Railsらしさとはなんだろうというところから、Railsらしく書くことのメリット、それを外れるときの判断基準やリスク、その対処方法、Railsの外の設計パターンとRailsの取った道…などなど、いろいろ考えました。なんとなく理解していてもここまで突き詰めて考えたことはなかったので、執筆する中で自分としてもとても勉強になりました。Railsのコードも結構読んだので、この処理はあの辺ね〜という脳内地図ができたのも収穫のひとつです。
あとは、なるべくコンテキスト依存の単語を使わないようにしようとか、平易な言葉で説明しようということを意識しました。オブジェクト指向やソフトウェア工学をやった人には「関心事」「結合度」「オブジェクトの責務」などの表現がすごく便利で話が早いのですが、今回はRailsの学習に集中するために極力使わないようにしてみました。意識してみて、自分がいかに普段特定のコンテキストに生きているのかを感じました…。
苦労した点
たくさん苦労しました。
Railsが(というかウェブアプリケーションが)対象とする範囲が広く、この概念を説明するにはこれがいる、というのがとても多いので、「あー、あれの説明がないからこれを紹介できない!」となることが多かったです。互いに関連しあっている話題もあり、そのうえ複数人で執筆していることから調整が難しいという点も要因の1つでした。
そして、この話題はこの章に移動したほうが良いなど、読者の理解の順や話題の類似性などを考慮して組み替えることも多々ありました。しかしただ組み替えただけでは、それぞれ独立した話題が寄せ集まっただけで流れが悪くなってしまいます。そのためそこにストーリーを付ける必要があるのですが、それがなかなかに苦労しました。特に10章は難しかったのですが、(共著者の)大場さんの力をかなり借りてとても良い形にまとまったと思います。
また、説明したいことはいろいろと思い浮かんでも、それを端的に分かりやすく伝えるためのサンプルコードを考えるのに苦労しました。悩んだ末「これだ!」と思いついた例が、帯に短し襷に長しであったり、抽象的すぎたり具体的すぎたりファンタジーすぎたりと、ちょうどよいのが思い浮かばない。これはある種のトレードオフスライダーみたいなもので、結局ある程度の落とし所を見つけるのですが、ちゃんと伝わりやすくなっているかなと今でも心配です。これが一番大変だったかもしれません。
おわりに
こういった苦難を乗り越えてようやく出版されることになり、とっても清々しい気持ちです。まだまだブラッシュアップできるんじゃないとか、やりきれていないんじゃないかといった自問もありますが、そうやってると一生終わらないので、今はとにかく終わってよかったという気持ちです。といいつつ、手元に届いた見本誌を見ると、良い本に仕上がったんじゃないかなと思っています!
以上のように、現場の知識や経験をこねくり回して濃縮したのが「現場で使える Ruby on Rails 5速習実践ガイド」です。ぜひ手にとってみてください。
合わせて読みたい
ppworks.hatenablog.jp (早速のレビューありがとうございます!)
BackstopJS を利用した Visual Regression Testing の感想と工夫ポイント
Visual Regression Testing とは
CSSやコードの変更によって、デザインに意図せぬ影響・リグレッションがないかを視覚的に確認するためのテスト。 変更前後のスクリーンショットを撮影して、差分を出力する方法が一般的。
似たテストに Snapshot Testing があるが、これはHTMLやASTを比較するテストであるため、CSSを含めたデザインの確認はできない。
やってみた感想
CSSのリファクタリングでは、見た目が変わっていないことや、見た目の変更が意図通りであることを確認する工程が多くを占める。 変更が大量にある場合はすべてをチェックすることは現実的ではないため、ある程度サンプリングして確認することになる。従って確認漏れの可能性は残る。 さらに、目視での確認は不確実でもあるし、何度も行っていると疲れて精度が落ちてくる(あと、とにかくつらい… :sob: )。
テストがあると、これらの作業を効率化したうえで、安心してリファクタリングできるようになる。 具体的には、次のシチュエーションで効果を実感できた。
- 全体のレイアウトに広く影響があるような修正を行うとき
- 新しく実装する機能のマークアップを行うとき
- 副次的に、レビュー依頼など変更を説明するとき
- レポート画面をスクショして提示するだけで、before/after+差分画像で変更を説明できて楽かつわかりやすい
プログラミングの自動テストと同じ感覚で、効率化と安心感をもたらしてくれた。
逆に微妙な点は、
- 実行時間が長い
- アプリケーションや環境にもよるが、100画面超えてくると10分超かかる
- 並列実行数を増やすとテスト結果が不安定になるので高速化にも限界がある
- 流して放置しつつ休憩 or 作業することができるので、目視時代よりは格段に楽
- テスト結果が不安定
- 今のところ開発環境で立ち上げたサーバでテストしているので、データの状態や実行時間などの影響が大きい
- スクリーンショットを撮影するタイミングで結果が変わることがある
- 上記は工夫次第で解決できそうだが、そのための労力が必要
BackstopJS
今回は、BackstopJSを使って実施した。 BackstopJSはスクリーンショット撮影、比較、比較結果のビジュアライズ、レポートUIといった、テストに必要な機能を十分満たしていて、かつメンテナンスも活発であることから採用した。 最近の開発シーンだとStorybookなどと組み合わせて行うのがモダンなのだろうけど、今回はそこまでJSを多用するプロジェクトではないのと、コンポーネントを組み合わせたときのレイアウトも確認したいため、どんな画面でもテストできることを重視した。
BackstopJSでは、例えばテストに失敗すると、以下のように差分が確認できる。
before/after の目視での比較も可能。
詳細はBackstopJSのREADMEを確認されたし。
利用にあたっては、標準の状態では使い勝手の良くない部分があったので色々工夫した。
BackstopJSはブラウザの操作に使うスクリプトを ./backstop_data
下に出力するので、そこをカスタマイズすることで大体解決できる。
そのカスタマイズ結果を他のプロジェクトでも使えるように汎用化して、雛形として公開してみた。
https://github.com/upinetree/backstop_boilerplate
大体コードを眺めればわかるのだが、せっかくなので以下では工夫したところやハマったところを解説してみる。
工夫ポイント
複数クリックイベント
オプション clickSelector
はクリックするセレクタを1つしか指定できないが、あるセレクタをクリックして、次にこのセレクタをクリック、ということをしたい場面は結構ある。
そのため複数のセレクタを連続でクリックする clickSelectors
を指定可能にした。
// backstop_data/engine_scripts/chromy/clickAndHoverHelper.js const clickSelectors = (scenario.clickSelectors || [scenario.clickSelector]).filter(x => x); clickSelectors.forEach((clickSelector) => { console.log(`INFO: click ${clickSelector}`); chromy .wait(clickSelector) .click(clickSelector); });
(Chromyを利用しているが、現時点でBackstopJSはPuppeteerにも対応しているので、そっちでも利用できるようにしたい…)
UserAgent を設定する
viewport
の設定による画面サイズごとのテストはサポートしているが、標準ではそこにUAを付けてくれない。
なので、 viewport
の内容を見てUAを切り替える処理を自分で書き加える。
backstop.json
の onBeforeScript
に指定されているスクリプトで、以下を記述。
// backstop_data/engine_scripts/chromy/onBefore.js if (vp.label === "phone") { chromy.userAgent("Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1"); } else { chromy.userAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36"); }
Cookieの読み込み動作周り
初期状態では、Cookieは毎回 cookiePath
オプションでテストシナリオごとに指定しなければいけないが、ログインが前提のアプリケーションでは使いづらい。
デフォルトのCookieを読み込むように変更した。
// backstop_data/engine_scripts/chromy/loadCookies.js const defaultCookiePath = "backstop_data/engine_scripts/cookies.json"; const cookiePath = scenario.cookiePath === "" ? "" : scenario.cookiePath || defaultCookiePath;
また、BackstopJSが生成したクッキー読み込みスクリプト loadCookies.js
ではテストシナリオごとにCookieの中身を出力するので、ログが流れてしまい追いづらい。
必要ないときは出力を抑制するようにした。
// backstop_data/engine_scripts/chromy/loadCookies.js if (process.env.VERBOSE) { console.log("Cookie state restored with:", JSON.stringify(cookies, null, 2)); } else { console.log("Cookie state restored."); }
テストシナリオの複数JSONへの分割機能
BackstopJSは標準ではテストシナリオを backstop.json
に以下のように他の設定と同列に書く。
scenarios: [ { "label": "xxx", "url": "http://localhost:3000/xxx", "selectors": [ ".aListOfStuff li" ] } ]
これだと、テストシナリオが増えたときに管理しづらくなる。
そのため backstop_data/scenarios
内の複数のJSONファイルに分割して管理できるようにした。
実行時にはこれらのJSONをそれぞれ読み込み、以下のようにひとまとめにする( backstop_data/scenarios/index.json )。
const fs = require("fs"); const jsonFiles = fs.readdirSync(__dirname).filter(filename => filename.match(/\.json$/)); const scenarios = jsonFiles.reduce((acc, filename) => ( acc.concat(require(`./${filename}`)) ), []); module.exports = scenarios;
こうしてひとまとめになったJSONをBackstopJSに渡す、という処理を backstop_data/runner.js
に記述し、yarnスクリプトから呼び出すようにした。
(runner.js
については後述)
backstop_data/runner.js
const backstop = require("backstopjs"); const config = require("../backstop.json"); const scenarios = require("./scenarios"); config.scenarios = scenarios; backstop(command, { filter, config });
package.json
{ "scripts": { "backstop": "node ./backstop_data/runner.js" } }
テストシナリオのJSONへの変数展開機能
例えば商品詳細画面のように、テスト対象のURLには動的なものもある。 そのようなURLを決め打ちでJSONに記述すると、以下のようにテスト環境が変化したときにURLが無効になってしまう。
- URLのパラメータにしていたレコードの ID, UUID, 名前, etc が変わる
- レコードが削除される
このような環境変化をある程度吸収するために、JSON読み込み時に変数展開を行えるようにした。
たとえば次のように、 ${}
で埋め込んだ変数を展開してテスト対象URLを動的にできる。
{ "url": "http://localhost:3000/posts/${post_id}" } post_id が 1 なら… => { "url": "http://localhost:3000/posts/1" }
具体的な実装はシンプルにこんな感じ( backstop_data/interpolate.js )。
const variables = require("./variables"); const keys = Object.keys(variables); module.exports = (json) => { const jsonStr = JSON.stringify(json); const interpolated = keys.reduce((acc, key) => ( acc.split("${" + key +"}").join(variables[key]) ), jsonStr); return JSON.parse(interpolated); };
これを runner.js
が呼び出して、BackstopJSに渡す前に適用している。
変数を展開するための値は、 backstop_data/variables.json
に定義するようにした。
{ "post_id": 1, ... }
また、このJSONを手動で変更するのはつらいので自動で生成するスクリプトを書いた。 今回はRailsプロジェクトなので、次のようなRakeタスク。
desc 'Dump a JSON file for the backstop variable interpolation' task dump_backstop_variables: :environment do post = Post.first! json = JSON.pretty_generate({ post_id: post.id.to_s }) File.open('backstop_data/variables.json', 'w') { |f| f.write(json) } end
透過的なyarnスクリプトを用意する
次のように、yarnスクリプトを利用する場合でも、BackstopJSのサブコマンドやオプションを透過的に利用できるようにしたかった。
$ yarn run backstop test $ yarn run backstop test --filter="<scenario label>" $ yarn run backstop reference $ yarn run backstop approve
JSONを結合したり、JSONに変数を展開したりといった機能を実現するために runner.js
を作成したので、yarnスクリプトから引数を受け取って、うまいことBackstopJSの関数に渡す必要がある。
パーサをrequireするのも良いのだが、シンプルな用途なのに依存を増やすのもあれだったので、自分で軽く書いた。
ハマリポイント
スクリーンショットの撮影に失敗しやすい
テストレポートにスクリーンショットが出ない場合、撮影に失敗している。
サーバーが起動していて、かつ selector などの指定が明らかに正しい場合は、
backstop.json
の asyncCaptureLimit
の値を下げてみると改善する。
この値はヘッドレスブラウザの並列起動数で、メモリが足りなかったりすると撮影に失敗することがあるようだ(詳しくは調べていない)。
他にも、サーバー立ち上げ直後だとアセットの更新(webpackだったり、Railsならアセットパイプラインだったり)で時間がかかりタイムアウトする場合がある。
事前にページを開いておく、webpack
、bin/rails assets:precompile
しておくなどでテストを安定化できる。
デザイン崩れに起因しない差分が出やすい
テストの性質上、冪等性が保ちづらい。 デザインは崩れていなくても、スクリーンショットに差分が出ることが多くある。 例えば、
- レコードの増減、変更
- 時間経過による動的な表示
- ランダム表示
などの影響を受けやすい。
あまりに動的で毎回差分が出るような要素はあらかじめ hideSelectors
で非表示にすると良い。
もしくは特定要素のみの確認で良ければ selector
でその他の要素の影響を避けることもできる。
差分が出て、デザイン崩れに起因しなければ、その都度 backstop approve
する。