続・マチマチとOSSの関わりをGitHubで振り返る2019/03/06に公開・約11,800字ツイートRailsRubyAppleScript#OSS#postgistech!これは2019年3月6日にマチマチ技術ブログに掲載した記事のアーカイブです。ご近所SNS「マチマチ」を作っている武者(@knu)です。前回に引き続き、今回もマチマチとOSSとの関わりについて紹介したいと思います。不具合のフィードバックそれなりの規模のプロダクトを開発していれば、当然さまざまなライブラリやツールを使うことになりますが、自分達では特に変わった使い方をしているつもりはないのに、ふとバグに当たることがたびたびあります。感じている気がします。そこで思い至るのは、「バグをバグであると認知して指摘する」こと自体が、スキルや経験の裏打ちを必要とする所業なのかもしれないということです。自分に自信がない、ちゃんと調べる気に至らない、などの引っ込み思案に陥り、「きっと自分の使い方が悪いのだろう」「回避したら動いたからよしとしよう」と撤退してしまうエンジニアが思った以上にたくさんいます。明らかに想定外の使い方をしていながらバグだと思い込んで騒ぎ立てるような人もいるにはいますが、もっと恐れずにフィードバックをしていいのにと思います。「こういう使い方ができると思った」「そこでこう書いた」「しかし結果は期待と違ってこうだった」という筋道立った報告は、それがバグでなく仕様だったとしても、開発者にとって貴重でありがたいフィードバックになっていることも多いです。直接的にはドキュメントの充実、中長期的には勘違いの起こりにくいAPIを設計するための参考になります。いうわけで張り切ってバグ報告していきましょう!PDFのサムネール取得マチマチでは、自治体の配布するチラシや広報のサムネイル画像を作るためにgrimというgemを使っています。特定のファイルをアップロードするとエラーになる事象があり、調べたところ、ファイル名に半角括弧「(」が含まれるとエラーが出ると分かりました。gemのソースを確認すると、見事なエスケープ漏れでした。私が実装したメソッドだけに、始めからちゃんと使ってほしかった!と思いました。結合する形にしたいところです。{gem名}する、という手順になります。日本の電話番号マチマチでは、ユーザのSMS認証やローカルプレイスデータの整備で数多くの電話番号を取り扱います。電話番号のバリデーションや整形表示(適切な桁数で区切りを入れる)など便利な機能を提供してくれるのが、phonyというgemです。このライブラリが0800で始まるフリーダイヤルの番号を正しく扱えなかったため、調べたところ、データに誤りがあると分かりました。0800の後には6桁ではなく、7桁の番号が続きます。(0120より1桁多い)https://github.com/floere/phony/pull/424PRを出し、正規の出典(現総務省サイトにある当時の郵政省のプレスリリースのURL)も示して解説を添えました。素早くマージしてもらうためには大事なことです。メソッドを通じて提供されています。例外が発生しますが、それ以外はコマンド自体がエラー終了した場合でも必ず出力文字列を返すはずです。落ちているのが発見されました。オーバーライドしており、その中の処理に問題があることが分かりました。握り潰して、標準エラー出力にその旨を表示しています。返り値であるnilが返却されてしまうというわけです。するようになっており、コマンドが存在しない場合の挙動がシェルを介するかどうかで変わってしまうことから、与えた文字列やプラットフォームの事情に依らず同じ動きに揃えたかったということのようです。私の最初の提案は、握り潰すのであれば空文字列("")を返すようにしよう、というものでしたが、最終的には歴史的経緯はともあれRuby本来の挙動のままでいいだろう、と削除されることになりました。これは合意が取れてからもかなりマージに時間が掛かりました...。別の意味に使いたい、というアイデアがあります。挙動がいまいちである、などの理由付けもありますが、Rubyの文法を拡張したい何かが念頭にあるようです。(「ネームスペース」というワードは出ていますが実体はまだ謎に包まれています)昨今はRubyの用法の多くがWebプログラミングの文脈となり、昔ほどシェルコマンド展開を使わなくなったということもあるでしょうか。いう代替記法の方がどちらかといえばメジャーですね。ECMAScriptなどでそれぞれバッククオートが有効活用されている様子は、ASCII記号の枯渇と戦っているRubyからは魅力的に思えます。付ける)で従来のコードを動かすことはできそうです。指定してループしたいことがあります。使おうとすると中身は空、という事象が発生していました。併用した際には実体と食い違う結果を招いていることが原因でした。下では取れないのに「取れる」と誤判定してしまうわけです。https://github.com/rails/rails/issues/35191ここの修正は職人芸が要るな、と思ったので、PRでなく再現ケースを書いて単にissueを上げたところ、すぐにkamipoさんが現れて直してくれました。付けてRelationを実体化(配列化)してしまうことでworkaroundとしています。CloudflareのキャッシュパージマチマチではCDNとして主にCloudflareを使っています。メディアサイトの本格始動以降、コンテンツの拡充とともにUUもPVもまさに桁違いに激増し、今も急成長しているため、CDNのエッジキャッシュで多大な恩恵を受けています。キャッシュが難しいのはみなさんご承知の通りです。データの変化と連動して適宜関連ページのキャッシュをパージすることで、コンテンツの更新がいち早くユーザに届くようにする必要があります。キャッシュのパージにはAPIを用いるのが通例です。CloudflareのAPIはシンプルなので、ふつうのHTTPクライアントライブラリを使っても十分ですが、せっかく専用ライブラリがあるのなら、使いやすさや保守性を求めて乗っかりたいところです。いうわけで、いくつか比較検討した結果、まずはcloudflairというgemを使い始めました。最初はキャッシュパージではなくDNSレコードの操作のために使っていました。ところが、キャッシュパージAPIを同ライブラリから呼び出そうとしたところ、エラーが返ってきます...。キャッシュを消す、というイメージに引っぱられてしまったのでしょうか...。https://github.com/ninech/cloudflair/issues/10よく見ると採用後にコミットがないし、アクティブではなさそう。キャッシュパージをするユーザがいなかったので発覚せず、結局そのまま静かに眠りに就いてしまうのでしょうか...。gemへの移行を進めました。敗北感を味わうことになりました。無事、DNS部分の移行はすんなり行ったので、「ああよかった、ではキャッシュパージを...」と進んだところで、再び愕然とすることになります。なんと、このgemもキャッシュパージAPIの実装に誤りがあったのです。化けてしまっている...。修正、テストしてPRを出しました。https://github.com/socketry/cloudflare/pull/46こちらは執筆時点で取り込み待ちですが、いずれマージされると思います。結論ということでよろしいかと思います。機能拡張のフィードバック差分データの展開2つのデータの並びを比較して、差分を計算するdiff-lcsというgemがあります。subsequenceアルゴリズムをライブラリ化したものです。マチマチでは、一部コンテンツの差分更新を行う処理でこれを用いています。(irb/pry)でDiff::LCSの挙動を軽く確認した上で、実際のコードに落としていきました。REPLでの表示の様子から、てっきり差分リストの各要素は配列だと思い、以下のようなコードでブロック引数への多重代入で受けようとしたのですがなぜかうまく動きません。endenddiff-lcsの実装を見たら、このオブジェクトは配列ではなく、配列っぽい文字列表現を採用していただけでした。REPLでの見た目にだまされた...。実装するだけのPRを出しました。https://github.com/halostatue/diff-lcs/pull/47CIがこけたら直そう、と気軽に出したのですが、あっさり通ってしまい、ほどなくマージされます...。ところが、改めて手元でテストを動かしてみると大量にこける...。あるために、今まで保存されていた各要素の中身もすべてflattenされてしまったというわけです。[1]作者の方もこれに気づき、revertした上で改めてflattenしていたところを書き直してくれたので一件落着です。CIは設定が壊れていてテストが走らずに通ってしまっていたようです。メソッドが一つ増えただけなのに壊れてしまう、というやや珍しい例で、次回からは気を付けようと思います。InsertをPostGISでも動かすマチマチでは、各地域情報の充実のため、大規模なデータ投入を定常的に行っています。gemを使おうと思いました。ちなみにactiverecord-importというgemもあり、実際これを使ったこともあります。こちらはActiveRecord親和的なAPIで、オプショナルですがvalidationを行わせることもできます。モデルのデータ構造を介してしまう分オーバーヘッドがあります。bulk_insertの方は、生のINSERT文を発行することに特化しており軽量です。自分でループを回してbulk_insertに食わせていくと、適当な数(デフォルトでは500)ごとに自動でINSERTを発行してくれるのが便利です。今回は、データの正しさはあらかじめ確認できているためこちらを使うことにしました。insert機能が入るかどうか、ちょっと注目ですね。モード(重複は無視=スキップ)がactiverecord-postgis-adapterに対応していないことが分かりました。アダプターや、私が前職でも使っていたactiverecord-mysql2spatial-adapterにも対応していないようだったので、ついでに対応させるPRを出しました。https://github.com/jamis/bulk_insert/pull/27昨日の自分は明日の誰か、今日の誰かは明日の自分。開発に使う道具も、みんなで共有するメリットはたくさんあります。いいものを作って気に入っていても、それにばかり手をかけるわけにはいかないことも多いです。なれば、公開してユーザが集めるのがリスクと手間を減らすことにつながります。周辺の状況が変化して有用性が失われそうになったときに、自分の代わりに修繕・改善してくれる人が現れてくれたらうれしいですね。RSpecのマッチャーWebのE2Eテストでは、UIを中心に非同期処理が多いため、そうした面でRSpecのマッチャーの支援を期待したいところです。マッチャーは所定のタイムアウトまでに条件が成立するかをポーリングしてチェックしてくれます。サーバサイドのデータの状態が非同期に更新される場合の検査についてはどうか。マッチャーを作って使っています。https://github.com/fujimura/rspec-become-matcherマチマチにおけるテストの方針についてはこちらの記事もどうぞ。表現することはとうにあきらめています。:sql)そこで問題になるのが、「スキーマ変更が交錯すると毎回必ずconflictが起きる」ことです。テーブルへのINSERT文になっています。付くため、この後に何か追加されると-('20190220030558');+('20190220030558'),+('20190221012345');のように行の追加だけでなく変更が発生してしまい、必然的に複数の差分が重なると自動マージが働きません。専用のGitのマージドライバーを書きました。https://github.com/knu/git-merge-structure-sql例によって、ついでにMySQLやSQLite3にも対応させてあります。ドキュメントの通り、インストールは簡単にしてあるので、同じ境遇の方はぜひご利用ください。やめてRidgepoleを使えないか検討している、といった向きにはこのツールは朗報かもしれません。任せる実装になっています。表示/UI系のissueやPRを出すときは、スクリーンショットを付けないと何のこっちゃとなりがちです。ところが、GitHubには動画をインラインで貼ることができません。唯一貼れるアニメーションGIFも、ファイルフォーマットの制約で256色しか使えません。思ってしまうのですが、流麗なフォントスムージング、丸みを得た枠線、アルファチャンネルによる半透過効果などなど、今やごくシンプルに見えるページでさえレンダリングには数十のオーダーで色が使われるようになっています。パレットの最適化を行わずに素朴にffmpegなどで変換すると、ディザリングの嵐となってまだら模様が目立ってしまい、とても見られた動画にはなりません。何かいいツールはないか、と思ったら、こんな記事を見つけました。https://cassidy.codes/blog/2017/04/25/ffmpeg-frames-to-gif-optimization/ffmpegのパレット生成機能を使い、2パスで変換すると、きれいにエンコードできるというお役立ち情報です。これは良いことを知りました。いうことで、シェルスクリプトを書くだけでも良かったのですが、MacのUIから楽に呼び出せるようにAppleScriptでくるんでアプリにしてみました。https://github.com/knu/mov2gif-for-macところで、AppleScriptってどうやって体系的に学ぶんでしょうね。私は毎回ググっては切り貼りして、試行錯誤で七転八倒しています。チェックボックスを入れる機能を追加しました。Firefox)を使えば好きな外部エディタで編集できます。https://github.com/jrblevin/markdown-mode/pull/229巨大YAMLの編集で迷子にならないWebの開発をしていると、なぜか巨大なYAMLファイルと格闘するはめになることが多いですね。RailsでもメッセージカタログはYAML形式だし、CIの設定ファイルもYAMLで、最初はシンプルでもいつのまにかビルド手順やマトリックスが複雑になって肥大化していきます。JSONと違ってコメントも書けるけれど、そもそも今自分が見ている行はどこなんだろう...という問いから逃れるのは至難です。いう仕組みを使って今いる行のYAMLパスを常時表示できるパッケージを書きました。階層を選んでジャンプもできます。https://github.com/knu/yaml-imenu.elおわりに今回は少し長くなってしまいました。事の経緯をトラブル発生から描くと、一つ一つが物語になってしまいますね。日々直接携わっているプロダクト・サービス自体以外にも、この事業をしていなければこの世にこのタイミングで起きなかったかもしれないことがいろいろあるなあ、と振り返ることができました。自分達が困ったから直す、あるいは作る、が基本ではありますが、きっと誰かの役にも立つはず、という思いで一つ一つの問題に取り組むことは心を豊かにしてくれます。引き続きマチマチでは、OSSで最高にハイになりたいエンジニアを募集しています。冒頭の話と絡めると、返り値がnilだと、「コマンドが存在しないときはそういうこともあるか」と妙に納得してしまい、自分のコードを修正してしまうユーザもそれなりに多そうです。技術顧問バッジを贈って著者を応援しようバッジを受け取った著者にはZennから現金やAmazonギフト券が還元されます。自動マージスクリーンビデオキャプチャのGIF化Markdownでチェックボックス付きリストを使う巨大YAMLの編集で迷子にならないおわりにZennエンジニアのための情報共有コミュニティAboutZennについて運営会社お知らせ・リリースGuides使い方Publicationよくある質問LinksTwitterGitHubメディアキットLegal利用規約プライバシーポリシ。