LoginSignup
2
0

More than 5 years have passed since last update.

Padrinoのparamsがindifferent_paramsされていた件

Posted at

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)

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キーの値で上書きされるようになっていました。 

2
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
2
0