Wie erweitere ich Objekte in Ruby?
Eines der interessanteren Ruby-Konzepte nennt sich “Open Classes”. Jedes Objekt kann nach Belieben erweitert werden. Natürlich kann dies auch zu Problemen führen. Gerade wenn Code wie der folgende nicht getestet wird:
class Object def blank? [1] nil? || (respond_to?(:empty?) && empty?) end unless method_defined?(:blank?) end class Numeric def blank? false end unless method_defined?(:blank?) end class NilClass def blank? true end unless method_defined?(:blank?) end class TrueClass def blank? false end unless method_defined?(:blank?) end class FalseClass def blank? true end unless method_defined?(:blank?) [2] end class String def blank? strip.empty? end unless method_defined?(:blank?) end
Abgesehen davon, dass es völlig unnötig ist, all diese Objekte mit einer blank? Methode zu erweitern
und die Implementierung vom De-facto-Standard abweicht, ist sie auch noch fehlerhaft:
false.blank? # => false
Ich hätte hier einen anderen Rückgabewert erwartet. Die Methode
Method#owner
verrät, dass die blank? Methode tatsächlich nicht an FalseClass, sondern an Object aufgerufen wird:
false.method(:blank?).owner # => Object
Die Dokumentation zu
method_defined? erklärt das Problem:
mod.method_defined?(symbol) => true or false
Returns true if the named method is defined by mod (or its included modules and, if mod is a class, its ancestors).
Public and protected methods are matched.
Da Object#blank? vor allen anderen Methoden definiert wird, gibt es FalseClass#blank? gar nicht.
Eine mögliche Lösung wäre, auf die De-facto-Standard Implementierung zurückzugreifen:
module MyGem module CoreExtensions module Object def blank? respond_to?(:empty?) ? empty? : !self end end end end Object.send :include, MyGem::CoreExtensions::Object
Erstens stimmt jetzt das Ergebnis:
false.blank? # => true
zweitens ist die Änderung transparent:
Object.ancestors # => [Object, MyGem::CoreExtensions::Object, Kernel] false.method(:blank?).owner # => MyGem::CoreExtensions::Object
und drittens könnte die Methode durch ein anderes Modul “überschrieben” werden.
Ps. Ruby 1.9 und Method#source_location
sind sehr hilfreich um herauszufinden, wo genau eine Methode definiert wird:
require "rspec" String.method(:should).source_location # => ["/Users/rubiii/.rvm/gems/ruby-1.9.2-p0@rails3/gems/rspec-expectations-2.0.0.beta.19/lib/rspec/expectations/extensions/kernel.rb", 26]
Mehr? Aktuelle Artikel oder alle Artikel im Archiv.