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