Skip to content

Commit

Permalink
Views for unique validations created on loading, not execution
Browse files Browse the repository at this point in the history
  • Loading branch information
samlown committed May 20, 2011
1 parent 938bf2c commit d56179a
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 15 deletions.
1 change: 1 addition & 0 deletions history.md
Expand Up @@ -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
Expand Down
43 changes: 29 additions & 14 deletions lib/couchrest/model/validations/uniqueness.rb
Expand Up @@ -3,37 +3,41 @@
module CouchRest
module Model
module Validations

# Validates if a field is unique
class UniquenessValidator < ActiveModel::EachValidator

# Ensure we have a class available so we can check for a usable view
# 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?
Expand All @@ -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
Expand Down
19 changes: 18 additions & 1 deletion spec/couchrest/validations_spec.rb
Expand Up @@ -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
Expand All @@ -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')
Expand All @@ -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')
Expand All @@ -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)
Expand Down

0 comments on commit d56179a

Please sign in to comment.