Islands in the byte stream

Technical notes by a software engineer

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がコケるレベルでメンテされてないので一旦諦め。