3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

LIFULLその2Advent Calendar 2018

Day 17

sinatraのバージョンあげたらIndifferentHashで逝った話

Last updated at Posted at 2018-12-17

この記事はLIFULL Advent Calendarその2の記事です。
空いてたのでこないだみつけたsinatra関係の小ネタ投下しときます。

概要

最近とあるプロジェクトで使ってたとっても古いsinatraのバージョンを上げたら思わぬところでおもしろバグが生まれてたので紹介
検証したsinatraのバージョンは2.0.4

再現コード

なんかパラメータを受け取って、そのキーに対応する変換処理を値にかけるみたいな処理があったとします。

app.rb
require 'sinatra/base'
require 'haml'
require 'active_support/all'

require_relative 'models/init'

class Server < Sinatra::Base
  get '/' do
    converter = {
      hoge: proc{|v| v.to_i + 1}
    }

    params.symbolize_keys!

    converted = params.keys.each_with_object({}) do |key, dt|
      dt[key] = converter[key].call(params[key])
    end

    haml :index, locals: {converted: converted}
  end
end

そしてサーバー起動して http://{host}/?hoge=123にアクセスします。

コードの意図はおいといてconvertedには{hoge: 124}がはいりそうなものですが、このコードは__converterにそんなキーないよ__ って旨のエラーになります。

paramsには{"hoge"=>123}が渡ってきてるので、それをsymbolizeして{hoge: 123}にしてると流れ的によめるので問題なさそうに見えますね

実際、params[:hoge]とアクセスすると123を得ることができます

原因

どうみても正常な振る舞いにしか見えません。
となると考えられる可能性は___こいつは実はHashではない___説です。

恐る恐るparams.classしてみると

puts params.class
# Sinatra::IndifferentHash

Sinatra::IndifferenceHash ???

どうやらsinatraはどこかのバージョンアップのタイミングでparamsを普通のHashじゃなくてSinatra::IndifferentHashというものにかえてるみたいです。

で、これは実装みてみると...


module Sinatra

  class IndifferentHash < Hash
    ...

    def []=(key, value)
      super(convert_key(key), convert_value(value))
    end

    ...

    def convert_key(key)
      key.is_a?(Symbol) ? key.to_s : key
    end
  end
end

IndifferentHash

といった感じでHashを継承しているものの、[]=を上書きして代入時にキーがsymbolだったら文字列に戻してからセットするようになっています。

今回のsymbolize_keys!は破壊的メソッドなのでactive support内部でself(=params)に対してself[key.to_sym] = delete(val)みたいなことをしています。
activesupport symbolize_keys!

keys.each do |key|
  self[yield(key)] = delete(key)
end

しかしこのself[key.to_sym] = xxxがIndifferentHashの[]=の中で無情にも元の文字列に戻されてるのです。

実に無意味...

見つけにくい

IndifferentHashはアクセサーもキーがsymbolだったらstringになおすようになってるので外からの観測でみつけるのは難しいですが、今回のようにいったんparams.keysと取り出してしまえばsymbolize_keys!したはずなのに文字列型のキーが羅列されるのでそれをもとにした処理を書くと死につながるので油断できないですね。

rspec側もparamsをHashとして外から与えるテスト書きがちなので結構見落とされやすくて怖いなと思う次第。

以上、深夜にみつけた少し笑える現象でした。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?