Ruby勉強会資料の訂正
にウソがあったので訂正します。
- RoRのコード例から引用 class MyController < ApplicationController def index if params[:name] == 'unknown' render :action => 'x' else render :action => 'y' end end end - params[]というメソッドに引数 :name を渡している
と書いていましたが、MyControllerにparams[] というメソッドはありません。あるのはparamsというメソッドです(と、params=)。このふたつはインスタンス変数(@_params)のアクセサメソッドです。このインスタンス変数がハッシュテーブルのオブジェクトなので、上の [:name] はハッシュテーブルの [] メソッドの呼び出しです。
この params[:name] は params['name'] とも書けるのですが、この実装がどうなっているのか気になって調べたのが、気づいた発端でした。
以下、Ruby on Railsのソースのトップディレクトリを$RAILSと表記します。
$RAILS/actionpack/lib/action_controller/base.rb で次のようにあります(抜粋)。ActionController::Base は ApplicationController の親クラスです。
module ActionController #:nodoc: ... class Base ... attr_internal :params
attr_internal は次のようなメソッドです(一部略)。
class Module def attr_internal_reader(*attrs) attrs.each do |attr| module_eval "def #{attr}() #{attr_internal_ivar_name(attr)} end" end end def attr_internal_writer(*attrs) attrs.each do |attr| module_eval "def #{attr}=(v) #{attr_internal_ivar_name(attr)} = v end" end end # Declare attributes backed by 'internal' instance variables names. def attr_internal_accessor(*attrs) attr_internal_reader(*attrs) attr_internal_writer(*attrs) end alias_method :attr_internal, :attr_internal_accessor private mattr_accessor :attr_internal_naming_format self.attr_internal_naming_format = '@_%s' def attr_internal_ivar_name(attr) attr_internal_naming_format % attr end end
気分は完全にマクロですが、要は attr_internal :params の呼び出しで、params と params= のふたつのメソッドを生成しています。ちなみに '@_%s' % :foo で '@_foo' の文字列を返します。
静的型の言語であれば、@_params (インスタンス変数)の定義を見れば、最初の疑問は一発で分かりそうですが、そうならないところが動的型言語の悲しいところです。結論から言えば、次のコードで Hash から HashWithIndifferentAccess に変換して返しています($RAILS/actionpack/lib/action_controller/request.rbから抜粋)。
module ActionController # CgiRequest and TestRequest provide concrete implementations. class AbstractRequest ... def parameters @parameters ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access end
with_indifferent_access は次のように定義されています。
def with_indifferent_access hash = HashWithIndifferentAccess.new(self) hash.default = self.default hash end
シンボルと文字列のどちらでも牽けるハッシュテーブルとしては、理屈上、次の4つの可能性があります。最後の候補は効率的に論外ですが。
- キーをシンボルに統一
- キーを文字列に統一
- シンボルと文字列の両方のキーを登録
- シンボルを使うキーと文字列を使うキーが混在
HashWithIndifferentAccess.new の実装は「キーを文字列に統一」です。勉強会で、シンボルの方が文字列より効率的なのでハッシュテーブルのキーにはシンボルを使うべし、と主張しておいてなんですが、paramsへのアクセスで使うキーは文字列の方が少しだけ(シンボルから文字列生成する分だけ)速いです。
と言うわけで、サンプルコードも次のように書き直しました。
if params['name'] == 'unknown'
- Category(s)
- カテゴリなし
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/inoue/ruby-article2/tbping