Help us understand the problem. What is going on with this article?

Padrinoのparamsがindifferent_paramsされていた件

More than 3 years have passed since last update.

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

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 インスタンスなのかと思って以下のようなコードを書いた。 1

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) 時点の値を確認してみると

only_id #=> "0"

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

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

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

ほう!

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

ほう・・・2

あーそーゆーことね(わかってない)

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

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

https://github.com/sinatra/sinatra/blob/v1.4.5/lib/sinatra/base.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

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

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 に変換して値を取ってきてくれるのである。便利!

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

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

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

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

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

ちなみに ActiveSupport::HashWithIndifferentAccess だと

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

h["one"]
# => 1

h[:one]
# => 1

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

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

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



  1. よく考えたら Padrino で Rails のプラグイン使わないですよね(((( 

  2. symbolize_keys でも罠にハマりました・・・。 利用していた activesupport が v3.2.2 と古かったため、同名の String キーと Symbol キーがあった場合、Stringキーの値で上書きされるようになっていました。 

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away