Sie überprüfen bei jedem Speichern sämtliche "uniqueness"-Constraints des jeweiligen Modells.
Ein kleines Beispiel. Gegeben sei ein Modell der folgenden Form:
class Foo < ActiveRecord::Base validates_uniqueness_of :bar endDann zeigt uns die Konsole folgendes:
irb> o = Foo.first => #<Foo id: 1, bar: "some_value"> irb> ActiveRecord::Base.logger = Logger.new(STDOUT) && nil => nil irb> o.save Foo Exists (20.8ms) SELECT 1 FROM `foos` WHERE (`foos`.`bar` = BINARY 'some_value' AND `foo`.`id` != 1) LIMIT 1 => trueDas Feld `bar` wird also auf Eindeutigkeit überprüft, obwohl es überhaupt nicht geändert wurde. Das mag in einer Welt ohne Datenbank-Constraints wie bei ActiveRecord sogar irgendwie noch sinnvoll sein, bei DataMapper (was Datenbank-Constraints unterstützt), erscheint mir dies vollständig unsinnig.
Abhilfe
ActiveRecord
Jedes ActiveRecord-Objekt stellt Methoden bereit, um zu überprüfen, ob ein Attribut geändert wurde oder nicht. Eine solche Methode hieße für ein Feld mit dem Namen `foo` beispielsweise `foo_changed?`. Verwendet man dies, so sähe unser obiges Beispiel wie folgt aus:class Foo < ActiveRecord::Base validates_uniqueness_of :bar, :if => :bar_changed? end
DataMapper
Bei DataMapper gibt es ebenfalls die Möglichkeit eine Validierung mit einer `:if`-Option nur unter bestimmten Voraussetzungen auszuführen. Allerdings gibt es hier keine `_changed?`-Methoden für jedes Feld, sondern "nur" eine Methode, die Auskunft über alle geänderten Methoden gibt. Diese Methode heißt `dirty_attributes` und gibt einen Hash der Form `<#DataMapper::Property...> => dirty_value` zurück. Nutzt man dies aus, so könnte man folgendes schreiben:class Foo
include DataMapper::Resource
property :bar, String
validates_uniqueness_of(:bar, :if => lambda{|r| r.dirty_attributes.has_key?(Foo.properties[:bar])})
end
Dies ist natürlich vergleichsweise umständlich und ermöglicht nicht den Einsatz der praktischeren `:unique`-Option in der `property` selbst. Ich habe mir deshalb für diese Zwecke die DataMapper::Resource.property-Methode wie folgt überschrieben:
module MyModelExtension
def property(name, type, options = {})
options = options.dup
unique = options.delete(:unique) # Don't pass the :unique option to DM directly
property = super(name, type, options)
if unique
opts = {:if => lambda{|res| res.dirty_attributes.has_key?(property)}}
opts.merge!(:scope => unique) if unique.is_a?(Array)
self.validates_uniqueness_of(name, opts)
end
return property
end
DataMapper::Model.append_extensions self
end
Damit sollte dann auch wieder die `:unqiue`-Option funktionieren:
class Foo include DataMapper::Resource property :bar, String, :unqiue => true end
