diff --git a/history.md b/history.md index 7e8d4827..a211ca74 100644 --- a/history.md +++ b/history.md @@ -10,6 +10,7 @@ * Removed railties dependency (DAddYE) * DesignDoc cache refreshed if a database is deleted. * Fixing dirty tracking on collection_of association. + * Uniqueness Validation views created on initialization, not on demand! ## 1.1.0.beta5 - 2011-04-30 diff --git a/lib/couchrest/model/validations/uniqueness.rb b/lib/couchrest/model/validations/uniqueness.rb index c2898496..6f9abddb 100644 --- a/lib/couchrest/model/validations/uniqueness.rb +++ b/lib/couchrest/model/validations/uniqueness.rb @@ -3,7 +3,7 @@ module CouchRest module Model module Validations - + # Validates if a field is unique class UniquenessValidator < ActiveModel::EachValidator @@ -11,29 +11,33 @@ class UniquenessValidator < ActiveModel::EachValidator # or add one if necessary. def setup(model) @model = model + if options[:view].blank? + attributes.each do |attribute| + opts = merge_view_options(attribute) + + if model.respond_to?(:has_view?) && !model.has_view?(opts[:view_name]) + opts[:keys] << {:allow_nil => true} + model.view_by(*opts[:keys]) + end + end + end end def validate_each(document, attribute, value) - keys = [attribute] - unless options[:scope].nil? - keys = (options[:scope].is_a?(Array) ? options[:scope] : [options[:scope]]) + keys - end - values = keys.map{|k| document.send(k)} - values = values.first if values.length == 1 + opts = merge_view_options(attribute) - view_name = options[:view].nil? ? "by_#{keys.join('_and_')}" : options[:view] + values = opts[:keys].map{|k| document.send(k)} + values = values.first if values.length == 1 model = (document.respond_to?(:model_proxy) && document.model_proxy ? document.model_proxy : @model) # Determine the base of the search - base = options[:proxy].nil? ? model : document.instance_eval(options[:proxy]) + base = opts[:proxy].nil? ? model : document.instance_eval(opts[:proxy]) - if base.respond_to?(:has_view?) && !base.has_view?(view_name) - raise "View #{document.class.name}.#{options[:view]} does not exist!" unless options[:view].nil? - keys << {:allow_nil => true} - model.view_by(*keys) + if base.respond_to?(:has_view?) && !base.has_view?(opts[:view_name]) + raise "View #{document.class.name}.#{opts[:view_name]} does not exist for validation!" end - rows = base.view(view_name, :key => values, :limit => 2, :include_docs => false)['rows'] + rows = base.view(opts[:view_name], :key => values, :limit => 2, :include_docs => false)['rows'] return if rows.empty? unless document.new? @@ -47,6 +51,17 @@ def validate_each(document, attribute, value) end end + private + + def merge_view_options(attr) + keys = [attr] + keys.unshift(*options[:scope]) unless options[:scope].nil? + + view_name = options[:view].nil? ? "by_#{keys.join('_and_')}" : options[:view] + + options.merge({:keys => keys, :view_name => view_name}) + end + end end diff --git a/spec/couchrest/validations_spec.rb b/spec/couchrest/validations_spec.rb index f8e2f4bf..b387323a 100644 --- a/spec/couchrest/validations_spec.rb +++ b/spec/couchrest/validations_spec.rb @@ -16,7 +16,11 @@ before(:all) do @objs = ['title 1', 'title 2', 'title 3'].map{|t| WithUniqueValidation.create(:title => t)} end - + + it "should create a new view if none defined before performing" do + WithUniqueValidation.has_view?(:by_title).should be_true + end + it "should validate a new unique document" do @obj = WithUniqueValidation.create(:title => 'title 4') @obj.new?.should_not be_true @@ -35,6 +39,7 @@ @obj.should be_valid end + it "should allow own view to be specified" do # validates_uniqueness_of :code, :view => 'all' WithUniqueValidationView.create(:title => 'title 1', :code => '1234') @@ -50,6 +55,13 @@ }.should raise_error end + it "should not try to create a defined view" do + WithUniqueValidationView.validates_uniqueness_of :title, :view => 'fooobar' + WithUniqueValidationView.has_view?('fooobar').should be_false + WithUniqueValidationView.has_view?('by_title').should be_false + end + + it "should not try to create new view when already defined" do @obj = @objs[1] @obj.class.should_not_receive('view_by') @@ -60,6 +72,11 @@ end context "with a proxy parameter" do + + it "should create a new view despite proxy" do + WithUniqueValidationProxy.has_view?(:by_title).should be_true + end + it "should be used" do @obj = WithUniqueValidationProxy.new(:title => 'test 6') proxy = @obj.should_receive('proxy').and_return(@obj.class)