こんにちは!kossyです!
さて、今回はActiveSupport::HashWithIndifferentAccessというクラスを発見してしまったので、
何をしているものなのかを検証してみようかと思います。
params.to_unsafe_hでActiveSupport::HashWithIndifferentAccessが返る
controllerの中のparams変数は、
$ params.class => ActionController::Parameters
ということでActionController::Parametersクラスのインスタンスなのですが、to_unsafe_hというメソッドが実装されており、実行すると、
$ params.to_unsafe_h.class => ActiveSupport::HashWithIndifferentAccess
掲題のActiveSupport::HashWithIndifferentAccessクラスのインスタンスが返ります。
まずはto_unsafe_hメソッドの定義位置を調べてみます。
$ params.method(:to_unsafe_h).source_location => ["/usr/local/bundle/gems/actionpack-6.0.3.6/lib/action_controller/metal/strong_parameters.rb", 336]
strong_parameters.rbの336行目に定義されているらしいので、Railsのソースコードを見に行ってみます。
# Returns an unsafe, unfiltered # <tt>ActiveSupport::HashWithIndifferentAccess</tt> representation of the # parameters. # # params = ActionController::Parameters.new({ # name: "Senjougahara Hitagi", # oddity: "Heavy stone crab" # }) # params.to_unsafe_h # # => {"name"=>"Senjougahara Hitagi", "oddity" => "Heavy stone crab"} def to_unsafe_h convert_parameters_to_hashes(@parameters, :to_unsafe_h) end
安全でない、フィルターされていないものを返します、とのことですね。
convert_parameters_to_hashesも見に行きます。
def convert_parameters_to_hashes(value, using) case value when Array value.map { |v| convert_parameters_to_hashes(v, using) } when Hash value.transform_values do |v| convert_parameters_to_hashes(v, using) end.with_indifferent_access when Parameters value.send(using) else value end end
end.with_indifferent_accessの部分が実行されると、ActiveSupport::HashWithIndifferentAccessクラスのインスタンスが返りそうですね。
def with_indifferent_access ActiveSupport::HashWithIndifferentAccess.new(self) end
ActiveSupport::HashWithIndifferentAccessのinitialize処理を見に行きます。
def initialize(constructor = nil) if constructor.respond_to?(:to_hash) super() update(constructor) hash = constructor.is_a?(Hash) ? constructor : constructor.to_hash self.default = hash.default if hash.default self.default_proc = hash.default_proc if hash.default_proc elsif constructor.nil? super() else super(constructor) end end
constructorがto_hashメソッドを実行できるなら、superクラスのinitializeを呼び出しています。
updateは以下のような定義です。
def update(*other_hashes, &block) if other_hashes.size == 1 update_with_single_argument(other_hashes.first, block) else other_hashes.each do |other_hash| update_with_single_argument(other_hash, block) end end self end
update_with_single_argumentを見る。
def update_with_single_argument(other_hash, block) if other_hash.is_a? HashWithIndifferentAccess regular_update(other_hash, &block) else other_hash.to_hash.each_pair do |key, value| if block && key?(key) value = block.call(convert_key(key), self[key], value) end regular_writer(convert_key(key), convert_value(value)) end end end
regular_updateを見る。と思ったら、updateのエイリアスメソッドでした。
処理の肝はconvert_keyですね。
if Symbol.method_defined?(:name) def convert_key(key) key.kind_of?(Symbol) ? key.name : key end else def convert_key(key) key.kind_of?(Symbol) ? key.to_s : key end end
nameというメソッドがSymbolクラスに生えていれば、
引数のkeyがSymbolクラスのインスタンスならkey.nameを返し、Symbolクラスでないならkeyをそのまま返すconvert_keyメソッドを定義し、
nameというメソッドがSymbolクラスに生えていなければ、
引数のkeyがSymbolクラスのインスタンスならkey.to_sを返し、Symbolクラスでないならkeyをそのまま返すconvert_keyメソッドを定義してました。
まとめ
unsafeという名前だったのは、特に値をフィルタリングするような処理を入れていないからですね。
通常、controllerでは値をフィルタリングするためにpermitメソッドを用いると思うのですが、to_unsafe_hをするとpermitされてないparamsも返り値として得られます。
paramsはActionController::Parametersなので、普通のHashとして処理したい場合にto_unsafe_hメソッドを用いるとよさそうです。
勉強になりました。