Padrinoのparamsがindifferent_paramsされていた件

Padrino の `params` は String でも Symbol でもアクセスできる。

```rb
Foo::App.controllers :user do
  get :show1 do
    @user = User.find params["id"] # String でもとってこれる
    render "user/show"
  end

  get :show2 do
    @user = User.find params[:id] # Symbol でもとってこれる
    render "user/show"
  end
end
```

# とある日の私の実装

どっちでもアクセスできるものだから、てっきり [`ActiveSupport::HashWithIndifferentAccess`](http://api.rubyonrails.org/classes/ActiveSupport/HashWithIndifferentAccess.html) インスタンスなのかと思って以下のようなコードを書いた。 [^aho]

```rb
Hoge::App.controllers :pager do
  get :user do
    # only_id パラメータが "0" or "1" で渡される想定
    paginate User, params.merge(only_id: params[:only_id].to_i > 0) # (A)
  end

  get :hoge do
    paginate Hoge, params
  end

  get :fuga do
    paginate Fuga, params
  end

  define_method :paginate do |klass, params|
    klass.paginate(target, params.symbolize_keys) # (B)
  end
end

class User
  def self.paginate(only_id: false, max_id: nil)
    # (C)
    users = find_by_max_id max_id
    items = only_id ? users.map(&:id) : users
    { items: items, only_id: only_id ? 1 : 0, max_id: max_id } # (D)
  end
end
```
 
`only_id` パラメータが `"0"` の場合、 (D) の items には User インスタンスの配列が入ってることを期待していた。 のだが、なぜか id の配列になっている。

(C) 時点の値を確認してみると

```rb
only_id #=> "0"
```

文字列 `"0"` のままである…。 (A) で `params[:only_id].to_i > 0` しているから、 `false` になるはずと思い…

今度は (B) 時点の値を確認してみる。

```rb
params #=> { "is_active" => "0", :is_active => false }
```

ほう！

```rb
params.symbolize_keys #=> { :is_active => "0" } 
```

ほう・・・[^wana]

![あーそーゆーことね(わかってない)](http://image.itmedia.co.jp/nl/articles/1511/12/l_kk_151112popu04.jpg)

# `Sinatra::Base` のコードを見てみる

Padrino は Sinatra を利用している。 Sinatra::Base のコードを見てみるとこんな記述があった。(私が使っていたものはv1.4.5)

https://github.com/sinatra/sinatra/blob/v1.4.5/lib/sinatra/base.rb

```rb
module Sinatra
  # ...
  class Base
    # ...

    def call!(env) # :nodoc:
      # ...
      @params   = indifferent_params(@request.params)
      # ...
    end

    # Enable string or symbol key access to the nested params hash.
    def indifferent_params(object)
      case object
      when Hash
        new_hash = indifferent_hash
        object.each { |key, value| new_hash[key] = indifferent_params(value) }
        new_hash
      when Array
        object.map { |item| indifferent_params(item) }
      else
        object
      end
    end


    # Creates a Hash with indifferent access.
    def indifferent_hash
      Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
    end

    # ...
  end
  # ...
end
```


## `Sinatra::Base#indifferent_hash` 

このメソッドがポイントである

```rb
h = Hash.new { |hash,key| hash[key.to_s] if Symbol === key }
h["one"] = 1
h["two"] = 2
h
# => {"one"=>1, "two"=>2}

# もちろん String キーで取ってこれる
h["one"]
# => 1

# そしてSymbol キーでも取ってこれる
h[:one]
# => 1
```

Symbol でアクセスした場合は String に変換して値を取ってきてくれるのである。便利！

ただし、値を書き換えるときは注意が必要である。

```rb
h[:one] = 1.0
h
# => {"one"=>1, "two"=>2, :one=>1.0}
```

実体は String キーなので、 Symbol でもうまいことやってくれるやじゃろと思っても、そこはそのままSymbolキーで突っ込まれる。

皆さんお気づきだろうか。そう、とある日の私はこのミスを犯したのである。

**書き換えのときは必ず String キーで行いましょう。**

# ちなみに [`ActiveSupport::HashWithIndifferentAccess`](http://api.rubyonrails.org/classes/ActiveSupport/HashWithIndifferentAccess.html) だと

```rb
h = { "one" => 1, "two" => 2 }.with_indifferent_access
# => {"one"=>1, "two"=>2}

h["one"]
# => 1

h[:one]
# => 1
```

呼び出しは基本indifferent_paramsと同じ。

```rb
h[:one] = 1.0
h
# => {"one"=>1.0, "two"=>2}
```

ただし、書き換えの場合はSymbolキーでも同名のStringキーの値が書き換えられる。

---

[^aho]: よく考えたら Padrino で Rails のプラグイン使わないですよね（（（（
[^wana]: [`symbolize_keys`](https://github.com/rails/rails/blob/3-2-stable/activesupport/lib/active_support/core_ext/hash/keys.rb#L24) でも罠にハマりました・・・。 利用していた activesupport が v3.2.2 と古かったため、同名の String キーと Symbol キーがあった場合、Stringキーの値で上書きされるようになっていました。