graphql-rubyの知見をscrapboxにまとめはじめた

scrapbox.io

React DOM elementsに focused プロパティがほしくなったとき

<input ref={(element) => this.input = element}/> しておいて this.input.focus() とか this.input.blur() とか呼び出すのはReact的ではないのでなんとかしたいものだなあ、と思って一旦こういう関数を作って運用してみることにしました。

// in TypeScript

// This function emulates "focus" attributes for React DOM elements
export function createFocusHandler<E extends HTMLElement>(focused: boolean) {
  return (element: E | null) => {
    if (element && element.matches(":focus") !== focused) {
      if (focused) {
        element.focus();
      } else {
        element.blur();
      }
    }
  };
}

これを次のように ref へのイベントハンドラとして渡せばOKです。

<input ref={createFocusHandler(focused)}/>

ラッパー component を作ってやっていくほうがいいような気もしますが、まあ動くのでとりあえずこれで。

yarn-deduplicate(1) で yarn.lock の重複エントリを最適化する

追記(2019/03/18): yarn-toolsからyarn-deduplicateが独立して使いやすくなり、 --strategy でdedupeの方法を選べるようになっています。タイトルも変更しました。

yarnpkg(1) を使って依存関係を管理しているとき、 yarnpkg upgrade-interactive は対話的にライブラリのアップデートができるので大変便利です。しかし、これを実行すると yarn.lock に不必要に重複エントリが作られることがよくあります。

nodejsで実行するケースでは重複があっても問題がないことが多いのですが、 クライアントサイドでは重複があると単純にファイルサイズが大きくなり。また、@types/* や react, jquery といったフレームワークはどの環境でも動作に問題が出たりするので、重複エントリは問題です*1。そこで今までは、 yarnpkg upgrade-interactive 後に 差分を眺めて重複エントリを1つにまとめるというタスクを手動でやっていました。

とはいえこの yarn.lock 最適化ロジックは機械的にできるはずなので、いいツールはないかと探していて、なければ作るのでもいいかと思っていいたところ、 yarn-deduplicate(1) というツールがその機能を提供しているようでした。

github.com

手元のかなり大きな yarn.lock でもちゃんと動いたので、 package.json の scripts セクションに次のようなdedupe scriptを登録して、 npm run dedupe で運用していこうと思います。

npx yarn-deduplicate --strategy=fewer && yarn install --force

こういうとき、 npx(1) を使うとプロジェクトの依存関係をいじらなくて済むのでいいですね。

*1:特にTypeScriptの型定義はグローバルスコープだったりするので、微妙に異なるバージョンの .d.ts が node_modules 以下に復数存在すると TS2300: Duplicate identifier が起きがちです。

nodejsをリビルドすることなくIntl APIをfull-icu相当のデータで使う

追記(2019/08/23): https://github.com/nodejs/node/issues/19214 によると、将来的にはfull-icuビルドがデフォルトになりそうです。


gfx.hatenablog.com

上記エントリの続きです。

その後調べた結果、 full-icu というNPM moduleでfull-icu相当のデータをインストールできることがわかりました。

https://github.com/unicode-org/full-icu-npm

中身については精査していませんが、 unicode-org が提供しているものなのでそれなりに信頼できるでしょう。

このモジュールをインストールすると node-icu-data-path(1) が提供されるので、それで得たパスを NODE_ICU_DATA 環境変数にいれるとnodejsの Intl APIで日本語を含めた多くのロケールデータを利用できるようになります。

HTC U11 は安心して人におすすめできるシムフリーAndroid端末だ!

www.htc.com

発売日に買って2週間ほど使ってますが、非常に快適です。

ここ1年で Android Z4 ⇢ Huawei P9 Lite ⇢ HTC U11 と変えてきましたが、一番ストレスフリーかもしれないです。シムフリーだし*1、変なビルトインアプリはあまり入ってないし*2、指紋認証がついてるし*3、Felica もついているし*4、USB debugも有効にできます*5。今のところ気になるのは、ボトムナビゲーションのホームボタンが指紋認証デバイスとかぶせているせいで独自実装なのがちょっと慣れないと使いにくいのと、ボトムナビゲーションにあるはずのIME切り替えボタンが存在しないのが不便だということくらいです。

Felicaと指紋認証のついているシムフリーのハイエンドAndroid端末というのがなくて困っていたわけですが、HTC U11は今のところ申し分のない出来です。よかったよかった。

*1:Xperia Z4はいい端末だがシムフリー版を手に入れるのがちょっと難しい。

*2:Huaweiのビルトインアプリは出来が良くなくてトラブルが起きがち。

*3:Xperia Z4は指紋認証が付いていないのが普段使いする上で唯一の欠点だった。

*4:Xperia Z4 はFelica がついている。

*5:Huawei P9 LiteはUSB Debugを有効にできない。

NodeJSのIntlサポートを調べた

  • IntlというのはECMAScript Internationalization APIのこと
  • NodeJSでも利用可能だが、ビルドオプションによって様子が変わる
  • NodeJSのICUサポートは none, system-icu, small-icu, full-icu というオプションが用意されている
    • https://nodejs.org/api/intl.html
    • 公式バイナリのオプションは small-icu で、これは英語だけをサポートしている
    • 起動時にICU dataを与える事も可能
    • この情報は node -e 'console.log(process.config.variables)' で調べられる
  • Intl によってI18N処理をするときに日本語サポートをしたい場合、3つの選択肢がある
    1. full-icu でビルドしたnodejsを使う
    2. small-icu ビルドのままで、NODE_ICU_DATA 環境変数でICU dataを別途与える
    3. intl.js でpolyfillする
  • intl.js はメンテが活発でもなくバグも放置気味
  • 日本のサービスで Intl を使うならfull-icu でビルドしたものを使うのがいいのかもしれない

発表資料: Elasticsearchによる 全文検索の実装

Rails Developers Meetup 2018 (#railsdm) で話した資料です。Railsの話はほとんどなくて、全文検索の仕組みとスコアリングについてのまとめが主です。

Q&Aシステムでの質問もここで回答します。

Q. データの同期はどうされていますか?

同期はActiveRecordのcallbackでActiveJobに更新jobを投げる形で非同期で行っています。また、データ構造などの更新がある場合にindex再構築するときのためのblue-green deployment用のバッチがあります。

Q. 何かgemを使われていますか?使われているなら、どんな選定理由ですか?

いまはelasticsearch-railsを使っていますが、このエントリの後半にあるような理由で捨てようと思っています。移行先はまだ決めていません。

Q. 辞書を作ったりしていますか?

Amazon Elasticsearch Serviceなのでカスタム辞書は使っていません。AESがカスタム辞書に対応したら考えます。

Q. 社内用語のような一般的ではない単語(かつ形態素解析で微妙な位置で区切られてしまう)で検索されるケースが多いとおもいますが、そういうものも、ngramとのハイブリッドで解決していますか?

現在のところ特に工夫はしていません。ngramとのハイブリッドである程度は解決出来ると思っています。

Q. スコアリングの評価について自動化や定量化は行えていますか? 何をもって改善したあるいはその逆であったと言えるか。

現在はスコアリングの評価について自動化や定量化はしていません。現在はユーザーからのフィードバックを元に行っている改善が主です。

ただ定量化自体はすべきだと思っていて、たとえば(私の前職でもある)クックパッド社では「検索成功率」というKPIを定義しており*1、これをもとに検索体験の改善を行っています。Kibelaでも同様にKPIを定義してそれを観測するということは必要だろうと思います。


最初は elasticsearch-rails の使い方の話を中心にしようと思ったのですが、まず全文検索そのものの話を整理しないとそこにたどり着かないなということでその話を中心にしました。

なおKibelaでは現在 elasticsearch-rails を使っていますが、次の理由から別のelasticsearch clientに移行する予定です。

  • elasticsearch-rails はあまりメンテされていない
  • elasticsearch-rails は ActiveRecordのmodel classにフォーカスしすぎていて多くの機能が不要
    • たとえば Model.search というメソッドが定義されるが、基本的に復数modelにまたがった検索しかしないのでこのメソッドを使うことがない
  • elasticsearch-rails から返るモデルが同値比較できなくてテストで困る問題 - Islands in the byte stream で示すようなモンキーパッチがいくつかあって厳しい
    • PR を送ろうにもローカルでテストを走らせるのが難しく、またそもそもあまりメンテされてないっぽいのでやる気がそがれる

じゃあ移行先はどうかというと searchkick はクエリの抽象化が高度すぎてもっと生のelasticsearch queryを使いたいんですよね。GitHubが開発している elastomer-client はわりとよさそうだなと思ってます。

elasticsearch-rails から返るモデルが同値比較できなくてテストで困る問題

elasticsearch-rails はElasticsearchとRails / ActiveRecordのインテグレーションをしてくれるgemです。

こいつを使っているとき、 特定条件下でモデル同士を比較するテストがコケるようになる という現象に悩まされてきました。pryなどで確認しても、たとえば model_a がes-railsからの返却値であるとき、 model_a == Model.find(model_a.id) がコケるのです。これはクラスのリロードがうまくいっていないからではという示唆を得てSpringを切ると、たしかに再現しなくなりました。そこで elasticsearch-rails を調べたところ、 Elasticsearch::Model::Registry がモデルクラスのインスタンスを保持しているようでした。 Springのドキュメントにも触れているとおり、これはNGです。

https://github.com/rails/spring#class-reloading

Rails前提のgemがSpringに配慮したコードになっていないというのはバグといえるかどうか微妙なところですが、とにかく動かないのは困るのでモンキーパッチを当てて凌ぐことにしました。

Monkey Patch

module ElasticsearchMultipleRecordsExtension
  if defined?(::Spring)
    # Workaround for elasticsearch-rails's bug that Elasticsearch::Model::Registry keeps class instances but they are not reloaded on Spring
    ORIGINAL_TYPE_FOR_HIT = Elasticsearch::Model::Adapter::Multiple::Records.instance_method(:__type_for_hit)

    def __type_for_hit(hit)
      type = ORIGINAL_TYPE_FOR_HIT.bind(self).call(hit)
      Object.const_get(type.name)
    end
  end
end

Elasticsearch::Model::Adapter::Multiple::Records.prepend(ElasticsearchMultipleRecordsExtension)

elasticsearch-rails にもpull-requestを送ろうと思ったのですが、bundle installがコケるレベルでメンテされてないので一旦諦め。

Elasticsearchのドキュメントの直し方

Elastic社の @johtani さんに教えてもらったんですが、たとえば Elasticsearchのドキュメント(たとえばFunction Score Query )をみて不足があるなーと思ったとき、Edit リンクからGitHubの該当ページを直接開けるので即pull-reqを作れます。

f:id:gfx:20180228150033p:plain

あとはCLAにサインして数日待つとレビューを経てマージされたりされなかったりします。

実例: [document] s/logarithm/common logarithm/ by gfx · Pull Request #28821 · elastic/elasticsearch

DroidKaigi 2018で『すばらしきGraphQLのSEKAIへようこそ』という発表をしました

DroidKaigi 2018 に参加して発表してきました。

今回はDroidKaigiということでクライアントサイドの視点から解説しています。よって、サーバーサイドの実装についてはまったく触れていません。サーバーサイドの実装については以前発表をしたことがあるのでそちらもどうぞ。

RejectKaigi 2017で「GraphQL on Rails」という発表をしました - Bit Journey's Tech Blog

今回のDroidKaigiはday 1で当日スタッフとして司会もしましたし、個人的にもDroidKaigi Prelude というイベントをするなどして思い出深いkaigiになりました。また来年もできるといいですね。