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キーの値が書き換えられる。
-
よく考えたら Padrino で Rails のプラグイン使わないですよね(((( ↩
-
symbolize_keys
でも罠にハマりました・・・。 利用していた activesupport が v3.2.2 と古かったため、同名の String キーと Symbol キーがあった場合、Stringキーの値で上書きされるようになっていました。 ↩