2009年8月25日火曜日

[Rails][CodeReading] Rails::Initializer (17) initialize_whiny_nils

Initializer.process で17番目に呼ばれる initialize_whiny_nils メソッド。

def initialize_whiny_nils
require('active_support/whiny_nil') if configuration.whiny_nils
end

Configuration.whiny_nils が true なら active_support/whiny_nil を require します。
Configuration.whiny_nils のデフォルトはfalse。

def default_whiny_nils
false
end


development環境やtest環境の場合、trueに設定し直される。

# config/environments/development.rb, config/environments/test.rb
# Log error messages when you accidentally call methods on nil.
config.whiny_nils = true


WhinyNil とは

Rubyは動的型付け言語なので、予想外のnilに遭遇したとき、その変数に入るべき(と開発者が考えていた)型を探し当てるために、コードをさまようことになりかねません。
WhinyNilの目的は、

予想外のnil値をできるだけ早期に補足し、nilが検出されたときに開発者により意味のあるエラーメッセージを提供すること

と『実践Rails』にあります。

どういうことかというと、試してみるとすぐわかります。

Ruby添付のirbの場合。
$ irb
irb(main):001:0> nil.size
NoMethodError: undefined method `size' for nil:NilClass
from (irb):1
from /usr/lib/ruby/ruby-1.9.1/bin/irb:12:in `<main>'
irb(main):002:0> nil.save
NoMethodError: undefined method `save' for nil:NilClass
from (irb):2
from /usr/lib/ruby/ruby-1.9.1/bin/irb:12:in `<main>'


Rails console (development環境)の場合。
$ script/console 
>> nil.size
NoMethodError: You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.size
from (irb):3
from /usr/lib/ruby/ruby-1.9.1/bin/irb:12:in `<main>'
>> nil.save
NoMethodError: You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.save
from (irb):4
from /usr/lib/ruby/ruby-1.9.1/bin/irb:12:in `<main>'

Arrayじゃないの?とかActiveRecordじゃないの?とか言ってきます。

WhinyNil の実体は、次のような高々30行程度のコードです。

# activesupport-2.3.3/lib/active_support/whiny_nil.rb
class NilClass
WHINERS = [::Array]
WHINERS << ::ActiveRecord::Base if defined? ::ActiveRecord

METHOD_CLASS_MAP = Hash.new

WHINERS.each do |klass|
methods = klass.public_instance_methods - public_instance_methods
class_name = klass.name
methods.each { |method| METHOD_CLASS_MAP[method.to_sym] = class_name }
end

# Raises a RuntimeError when you attempt to call +id+ on +nil+.
def id
raise RuntimeError, "Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id", caller
end

private
def method_missing(method, *args, &block)
raise_nil_warning_for METHOD_CLASS_MAP[method], method, caller
end

# Raises a NoMethodError when you attempt to call a method on +nil+.
def raise_nil_warning_for(class_name = nil, selector = nil, with_caller = nil)
message = "You have a nil object when you didn't expect it!"
message << "\nYou might have expected an instance of #{class_name}." if class_name
message << "\nThe error occurred while evaluating nil.#{selector}" if selector

raise NoMethodError, message, with_caller || caller
end
end


# でもなぜ Array と ActiveRecord だけなんでしょうか。。。

[Rails][CodeReading] Railsの初期化コードを読む (イントロ&目次)

0 件のコメント:

コメントを投稿