[Ruby] Rails(ActiveSupport)5.2でMongoidのattributes.sliceの挙動が変わった話

Rails5.2にアップデート中に遭遇したので、メモしておく。

# 環境
- ruby: 2.5.1
- rails: 5.2.0
- mongoid: 6.4.0
- mongo: 2.5.1
- bson: 4.3.0

# attributes.sliceの結果が急に空になった

アップデート前 (5.1.4)

```ruby
model.attributes.slice(:hoge) #=> {“hoge”: ... }
```

アップデート後 (5.2.0)

```ruby
model.attributes.slice(:hoge) #=> {}
```

※ `attributes.slice` のkeyにSymbolを使っている場合に発生

# 原因
- mongoidのattributesは `Hash` を継承した `BSON::Document` のインスタンスが返ってくる
	- https://github.com/mongodb/bson-ruby/blob/v4.3.0/lib/bson/document.rb#L35
	- BSON::DocumentはHashを継承しているのでsliceはRailsアプリ内で使っているとActiveSupportの `Hash#slice` が呼ばれていた ※v4.3.0時点
	- BSON::Documentは `#[]` をorverrideして、keyがSymbolでもStringでも取り出すことができるようになっていた
		- https://github.com/mongodb/bson-ruby/blob/v4.3.0/lib/bson/document.rb#L52-L54
	- ActiveSupportの `Hash#slice` は `#[]` を使っていた為、`x.attributes.slice(:hoge)` でも `x.attributes('hoge')` でも同じ挙動になっていた
		- と思っていたけどよく見ると、`#convert_key` 定義されていたらconvert_key通してから `#[]` 呼んでた
			- https://github.com/rails/rails/blob/v5.1.4/activesupport/lib/active_support/core_ext/hash/slice.rb#L21-L24
		- どちらにせよ、最終的には `self[key]` してるので、 `#[]`が呼ばれている
- 5.2でActiveSupportの `Hash#slice` の実装が変わった
	- https://github.com/rails/rails/commit/01ae39660243bc5f0a986e20f9c9bff312b1b5f8
	- これによって、Ruby標準の `Hash#slice` が呼ばれるようになった
- **Ruby標準の `Hash#slice` は `#[]` が呼ばれないようで `BSON::Document#[]` を通らない為、keyをSymbolで呼び出すと結果が取れなくなった**


# 検証してみる
## ActiveSupport5.1.4の `Hash#slice` を使う場合
```ruby
# Gemfile
source 'https://rubygems.org'

ruby '2.5.1'

gem 'activesupport', '5.1.4'
```


```ruby
# hash_test.rb

require 'active_support/all'

class MyHash < Hash
  def [](key)
    puts "called with key: #{key.inspect}"
    super(key)
  end
end

h = MyHash[{hoge: 100 }]
puts h.slice(:hoge)
```

```
❯ bundle exec ruby hash_test.rb
called with key: :hoge
{:hoge=>100}
```

たしかに、 `#[]` を通っている

https://github.com/rails/rails/blob/v5.1.4/activesupport/lib/active_support/core_ext/hash/slice.rb#L21-L24

## ActiveSupport5.2.0で `Hash#slice` を使う場合
ruby2.5.1でactive_support5.2にすると、Hash#sliceがdefindedなので、Ruby標準の方が使われる
なので、activesupportをrequireしなければいいはずだけど、一応5.2.0のactivesupportをrequireしてやってみる

```
# Gemfile
source 'https://rubygems.org'

ruby '2.5.1'

gem 'activesupport', '5.2.0'
```

```
# hash_test.rb
require 'active_support/all'

class MyHash < Hash
  def [](key)
    puts "called with key: #{key.inspect}"
    super(key)
  end
end

h = MyHash[{hoge: 100 }]
puts h.slice(:hoge)
```

```
❯ bundle exec ruby hash_test.rb
{:hoge=>100}
```

うん、`#[]` 通らなくなりましたね。

## ActiveSupportをrequireしないでやってみる
```
# hash_test.rb

# require 'active_support/all'

class MyHash < Hash
  def [](key)
    puts "called with key: #{key.inspect}"
    super(key)
  end
end

h = MyHash[{hoge: 100 }]
puts h.slice(:hoge)
```

↑コメントアウトしてみました。

```
❯ bundle exec ruby hash_test.rb
{:hoge=>100}
```

やっぱり `#[]` は通ってないっぽいですね。

# bson-rubyのmasterでは既に修正されている
https://github.com/mongodb/bson-ruby/commit/5d9453c477cd0900696f801621f05ddb75fb8ab1#diff-cc9ff933e6c1af10b7f7314fbda5926f

↑このコミットで `slice` をorverrideする形で、元の挙動と同じになるように修正されていました。

> # @since 4.3.1

4.3.1には入るようですね。
※ 2018/04/28 17:25 現在では 4.3.0までしかリリースされていないので、まだ未リリースの様子

# まとめ
- `Hash#slice` のおかげで勉強になった
- そもそも、sliceの振る舞いとして `{"hoge” => 100 }` から `h.slice(:hoge)` で取ろうとするのが間違っている気がしてきた
	- できるとたしかに便利ではあるんだけど
	- SymbolとStringは別モンだよね、っていう方が明らかに正しい
- bsonの修正はまだリリースされていないので、一旦はattributesのsliceにSymbol渡してる所を全て文字列を使うように直して対応した