Permalink
Browse files

Most of specs pass. Removed :case_sensitive and :allow_blank options …

…because there is no equivalent in validates_is_unique
  • Loading branch information...
1 parent f2b79b5 commit 39c44f02d2b5028d6c65db3b5b2f18c163f1be8d Blake Gentry committed Sep 2, 2009
View
13 remarkable_datamapper/lib/remarkable_datamapper/base.rb
@@ -180,12 +180,11 @@ def assert_bad_value(model, attribute, value, error_message_to_expect=:invalid)
#
def error_message_from_model(model, attribute, message) #:nodoc:
if message.is_a? Symbol
- message = if I18N # Rails >= 2.2
- model.errors.generate_message(attribute, message, :count => '12345')
- else # Rails <= 2.1
- ::DataMapper::Errors.default_error_messages[message] % '12345'
- end
-
+ # TODO: No Internationalization yet.
+ # TODO: remove debug line
+ pp attribute
+ message = ::DataMapper::Validate::ValidationErrors.default_error_message(message, attribute, '12345')
+
if message =~ /12345/
message = Regexp.escape(message)
message.gsub!('12345', '\d+')
@@ -240,7 +239,7 @@ def collection_interpolation #:nodoc:
# Returns true if the given collection should be translated.
#
def i18n_collection? #:nodoc:
- I18N && I18N_COLLECTION.include?(self.class.matcher_arguments[:collection])
+ RAILS_I18N && I18N_COLLECTION.include?(self.class.matcher_arguments[:collection])
end
end
View
1 remarkable_datamapper/lib/remarkable_datamapper/describe.rb
@@ -130,7 +130,6 @@ def describe(*args, &block)
:key => translated_key.downcase, :value => value.inspect)
end
- pp pieces
description << pieces.join(connector)
args.unshift(description)
View
65 ...atamapper/matchers/validates_is_unique.rb → ...er/matchers/validate_is_unique_matcher.rb
@@ -1,15 +1,15 @@
module Remarkable
module DataMapper
module Matchers
- class ValidatesIsUniqueMatcher < Remarkable::DataMapper::Base #:nodoc:
+ class ValidateIsUniqueMatcher < Remarkable::DataMapper::Base #:nodoc:
arguments :collection => :attributes, :as => :attribute
optional :message
optional :scope, :splat => true
- optional :case_sensitive, :allow_nil, :allow_blank, :default => true
+ optional :nullable, :default => true
- collection_assertions :find_first_object?, :responds_to_scope?, :is_unique?, :case_sensitive?,
- :valid_with_new_scope?, :allow_nil?, :allow_blank?
+ collection_assertions :find_first_object?, :responds_to_scope?, :is_unique?,
+ :valid_with_new_scope?, :nullable?
default_options :message => :taken
@@ -28,24 +28,24 @@ class ValidatesIsUniqueMatcher < Remarkable::DataMapper::Base #:nodoc:
# If any of these attempts fail, an error is raised.
#
def find_first_object?
- conditions, message = if @options[:allow_nil]
- [ ["#{@attribute} IS NOT NULL"], " with #{@attribute} not nil" ]
- elsif @options[:allow_blank]
- [ ["#{@attribute} != ''"], " with #{@attribute} not blank" ]
- else
- [ [], "" ]
+ conditions, message = [[], ""]
+ if @options[:nullable]
+ conditions << {::DataMapper::Query::Operator.new(@attribute, :not) => nil}
+ message << " with #{@attribute} not nil"
end
-
- unless @subject.new_record?
- primary_key = subject_class.primary_key
-
- message << " which is different from the subject record (the object being validated is the same as the one in the database)"
- conditions << "#{subject_class.primary_key} != '#{@subject.send(primary_key)}'"
+
+ unless @subject.new?
+ key = subject_class.key
+
+ message << " which is different from the subject record (the object being validated is the same as the one in the database)"
+ conditions << {::DataMapper::Query::Operator.new(subject_class.key.first.name, :not) => @subject.send(key)}
+ pp conditions
end
-
- options = conditions.empty? ? {} : { :conditions => conditions.join(' AND ') }
-
- return true if @existing = subject_class.find(:first, options)
+
+ require 'pp'
+ #pp conditions
+
+ return true if @existing = subject_class.first(conditions)
raise ScriptError, "could not find a #{subject_class} record in the database" + message
end
@@ -70,19 +70,6 @@ def is_unique?
return bad?(@value)
end
- # If :case_sensitive is given and it's false, we swap the case of the
- # value used in :is_unique? and see if the test object remains valid.
- #
- # If :case_sensitive is given and it's true, we swap the case of the
- # value used in is_unique? and see if the test object is not valid.
- #
- # This validation will only occur if the test object is a String.
- #
- def case_sensitive?
- return true unless @value.is_a?(String)
- assert_good_or_bad_if_key(:case_sensitive, @value.swapcase)
- end
-
# Now test that the object is valid when changing the scoped attribute.
#
def valid_with_new_scope?
@@ -173,7 +160,7 @@ def new_value_for_stringfiable_scope(scope)
conditions = { scope => values, @attribute => @value }
# Get values from the database, get the scope attribute and map them to string.
- db_values = subject_class.find(:all, :conditions => conditions, :select => scope)
+ db_values = subject_class.all(:conditions => conditions, :fields => [scope])
db_values.map!{ |r| r.send(scope).to_s }
if value_to_return = (values - db_values).first
@@ -204,7 +191,6 @@ def new_value_for_stringfiable_scope(scope)
# == Options
#
# * <tt>:scope</tt> - field(s) to scope the uniqueness to.
- # * <tt>:case_sensitive</tt> - the matcher look for an exact match.
# * <tt>:allow_nil</tt> - when supplied, validates if it allows nil or not.
# * <tt>:allow_blank</tt> - when supplied, validates if it allows blank or not.
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
@@ -213,20 +199,19 @@ def new_value_for_stringfiable_scope(scope)
# == Examples
#
# it { should validate_uniqueness_of(:keyword, :username) }
- # it { should validate_uniqueness_of(:email, :scope => :name, :case_sensitive => false) }
+ # it { should validate_uniqueness_of(:email, :scope => :name) }
# it { should validate_uniqueness_of(:address, :scope => [:first_name, :last_name]) }
#
# should_validate_uniqueness_of :keyword, :username
- # should_validate_uniqueness_of :email, :scope => :name, :case_sensitive => false
+ # should_validate_uniqueness_of :email, :scope => :name
# should_validate_uniqueness_of :address, :scope => [:first_name, :last_name]
#
# should_validate_uniqueness_of :email do |m|
# m.scope = name
- # m.case_sensitive = false
# end
#
- def validates_is_unique(*attributes, &block)
- ValidateUniquenessOfMatcher.new(*attributes, &block).spec(self)
+ def validate_is_unique(*attributes, &block)
+ ValidateIsUniqueMatcher.new(*attributes, &block).spec(self)
end
end
end
View
17 remarkable_datamapper/locale/en.yml
@@ -248,25 +248,12 @@ en:
expectations:
allow_nil: "{{subject_name}} to require {{attribute}} to be set"
- validate_uniqueness_of:
- description: "require unique values for {{attributes}}"
- expectations:
- responds_to_scope: "{{subject_name}} instance responds to {{method}}"
- is_unique: "{{subject_name}} to require unique values for {{attribute}}"
- case_sensitive: "{{subject_name}} to {{not}}be case sensitive on {{attribute}} validation"
- valid_with_new_scope: "{{subject_name}} to be valid when {{attribute}} scope ({{method}}) change"
- optionals:
- scope:
- positive: "scoped to {{sentence}}"
- case_sensitive:
- positive: "case sensitive"
- negative: "case insensitive"
-
- validates_is_unique:
+ validate_is_unique:
description: "require unique values for {{attributes}}"
expectations:
responds_to_scope: "{{subject_name}} instance responds to {{method}}"
is_unique: "{{subject_name}} to require unique values for {{attribute}}"
+ nullable: "{{subject_name}} to require {{attribute}} to be set"
case_sensitive: "{{subject_name}} to {{not}}be case sensitive on {{attribute}} validation"
valid_with_new_scope: "{{subject_name}} to be valid when {{attribute}} scope ({{method}}) change"
optionals:
View
103 remarkable_datamapper/spec/describe_spec.rb
@@ -1,103 +0,0 @@
-require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
-
-I18N = true # specs won't run unless false
-
-class Post
- attr_accessor :published, :public, :deleted
-
- def attributes=(attributes={}, guard=true)
- attributes.each do |key, value|
- send(:"#{key}=", value) unless guard
- end
- end
-
- def self.human_name(*args)
- "MyPost"
- end
-end
-
-describe Post do
- it "should use human name on description" do
- self.class.description.should == "MyPost"
- end
-
- describe "default attributes as a hash" do
- subject_attributes :deleted => true
-
- it "should set the subject with deleted equals to true" do
- subject.deleted.should be_true
- end
-
- it "should not change the description" do
- self.class.description.should == "MyPost default attributes as a hash"
- end
- end
-
- describe "default attributes as a proc" do
- subject_attributes { my_attributes }
-
- it "should set the subject with deleted equals to true" do
- subject.deleted.should be_true
- end
-
- it "should not change the description" do
- self.class.description.should == "MyPost default attributes as a proc"
- end
-
- def my_attributes
- { :deleted => true }
- end
- end
-
- describe :published => true do
- it "should set the subject with published equals to true" do
- subject.published.should be_true
- end
-
- it "should generate a readable description" do
- self.class.description.should == "MyPost when published is true"
- end
-
- it "should call human name attribute on the described class" do
- Post.should_receive(:human_attribute_name).with("comments_count", :locale => :en).and_return("__COMMENTS__COUNT__")
- self.class.describe(:comments_count => 5) do
- self.description.should == 'MyPost when published is true and __comments__count__ is 5'
- end
- end
-
- describe :public => false do
- it "should nest subject attributes" do
- subject.published.should be_true
- subject.public.should be_false
- end
-
- it "should nest descriptions" do
- #self.class.describe_subject_attributes.should == "blah"
- self.class.description.should == "MyPost when published is true and public is false"
- end
-
- describe "default attributes as a hash" do
- subject_attributes :deleted => true
-
- it "should merge describe attributes with subject attributes" do
- subject.published.should be_true
- subject.public.should be_false
- subject.deleted.should be_true
- end
- end
- end
- end
-
- describe :published => true, :public => false do
- it "should set the subject with published equals to true and public equals to false" do
- subject.published.should be_true
- subject.public.should be_false
- end
-
- it "should include both published and public in descriptions" do
- self.class.description.should match(/MyPost/)
- self.class.description.should match(/public is false/)
- self.class.description.should match(/published is true/)
- end
- end
-end
View
49 remarkable_datamapper/spec/model_builder.rb
@@ -1,7 +1,7 @@
# This is based on Shoulda model builder for Test::Unit.
#
-# TODO: !!! These functions are
+# TODO: !!! These functions are not all updated yet
module ModelBuilder
def self.included(base)
return unless base.name =~ /^Spec/
@@ -14,29 +14,30 @@ def self.included(base)
end
end
- DataMapper.auto_migrate!
- #if @created_tables
- # @created_tables.each do |table_name|
- # ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS #{table_name}")
- # end
- #end
+ if @created_tables
+ @created_tables.each do |table_name|
+ DataMapper::Repository.adapters[:default].execute("DROP TABLE IF EXISTS #{table_name}")
+ end
+ end
end
end
base.extend ClassMethods
end
- def create_table(table_name, &block)
- connection = ActiveRecord::Base.connection
+ def create_table(model)
+ adapter = DataMapper::Repository.adapters[:default]
+ table_name = model.to_s.tableize
+ command = "DROP TABLE IF EXISTS #{table_name}"
begin
- connection.execute("DROP TABLE IF EXISTS #{table_name}")
- connection.create_table(table_name, &block)
+ adapter.execute(command)
+ adapter.create_model_storage(model)
@created_tables ||= []
@created_tables << table_name
- connection
+ adapter
rescue Exception => e
- connection.execute("DROP TABLE IF EXISTS #{table_name}")
+ adapter.execute(command)
raise e
end
end
@@ -45,8 +46,8 @@ def define_constant(class_name, base, &block)
class_name = class_name.to_s.camelize
klass = Class.new
- klass.include base
- Object.const_set(class_name, klass)
+ klass.send :include, base
+ Object.const_set(class_name, klass) #unless klass
klass.class_eval(&block) if block_given?
@@ -63,18 +64,18 @@ def define_model_class(class_name, &block)
def define_model(name, columns = {}, &block)
class_name = name.to_s.pluralize.classify
table_name = class_name.tableize
-
- table = columns.delete(:table) || lambda {|table|
- columns.each do |name, type|
- table.column name, *type
- end
- }
-
- create_table(table_name, &table)
-
klass = define_model_class(class_name, &block)
+ pp columns # TODO: REMOVE debug line
+ columns.each do |name, type|
+ options = {}
+ type, options = type if type.class == Array
+ klass.property(name, type, options)
+ end
+
instance = klass.new
+ create_table(klass)
+
self.class.subject { instance } if self.class.respond_to?(:subject)
instance
end
View
155 remarkable_datamapper/spec/validates_is_unique_matcher_spec.rb
@@ -0,0 +1,155 @@
+require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
+
+describe 'validate_is_unique' do
+ include ModelBuilder
+
+ # Defines a model, create a validation and returns a raw matcher
+ def define_and_validate(options={})
+ @model = define_model :user, :id => DataMapper::Types::Serial, :username => String, :email => String, :public => DataMapper::Types::Boolean, :deleted_at => DateTime do
+ validates_is_unique :username, options
+ end
+
+ # Create a model
+ User.create(:username => 'jose', :deleted_at => 1.day.ago, :public => false)
+
+ validate_is_unique(:username)
+ end
+
+ describe 'messages' do
+ before(:each){ @matcher = define_and_validate }
+
+ it 'should contain a description' do
+ @matcher.description.should == 'require unique values for username'
+
+ @matcher.nullable
+ @matcher.description.should == 'require unique values for username allowing nil values'
+
+ @matcher = validate_is_unique(:username, :scope => :email)
+ @matcher.description.should == 'require unique values for username scoped to :email'
+
+ @matcher = validate_is_unique(:username)
+ @matcher.scope(:email)
+ @matcher.scope(:public)
+ @matcher.description.should == 'require unique values for username scoped to :email and :public'
+ end
+
+ it 'should set responds_to_scope? message' do
+ @matcher.scope(:title).matches?(@model)
+ @matcher.failure_message.should == 'Expected User instance responds to title='
+ end
+
+ it 'should set is_unique? message' do
+ @matcher = validate_is_unique(:email)
+ @matcher.matches?(@model)
+ @matcher.failure_message.should == 'Expected User to require unique values for email'
+ end
+
+ it 'should valid with new scope' do
+ @matcher.scope(:email).matches?(@model)
+ @matcher.failure_message.should == 'Expected User to be valid when username scope (email) change'
+ end
+ end
+
+ describe 'matcher' do
+
+ describe 'without options' do
+ before(:each){ define_and_validate }
+
+ it { should validate_is_unique(:username) }
+ it { should_not validate_is_unique(:email) }
+ end
+
+ describe 'scoped to' do
+ it { should define_and_validate(:scope => :email).scope(:email) }
+ it { should define_and_validate(:scope => :public).scope(:public) }
+ it { should define_and_validate(:scope => :deleted_at).scope(:deleted_at) }
+ it { should define_and_validate(:scope => [:email, :public]).scope(:email, :public) }
+ it { should define_and_validate(:scope => [:email, :public, :deleted_at]).scope(:email, :public, :deleted_at) }
+ it { should_not define_and_validate(:scope => :email).scope(:title) }
+ it { should_not define_and_validate(:scope => :email).scope(:public) }
+ end
+
+ create_message_specs(self)
+
+ # Those are macros to test optionals which accept only boolean values
+ create_optional_boolean_specs(:nullable, self)
+ end
+
+ describe 'errors' do
+ it 'should raise an error if no object is found' do
+ @matcher = define_and_validate
+ User.all.destroy
+
+ proc { @matcher.matches?(@model) }.should raise_error(ScriptError)
+ end
+
+ it 'should raise an error if no object with not nil attribute is found' do
+ @matcher = define_and_validate.nullable
+ User.all.destroy
+
+ User.create(:username => nil)
+ proc { @matcher.matches?(@model) }.should raise_error(ScriptError)
+
+ User.create(:username => 'jose')
+ proc { @matcher.matches?(@model) }.should_not raise_error(ScriptError)
+ end
+
+ it 'should raise an error if @existing record is the same as @subject' do
+ @matcher = define_and_validate
+ proc { @matcher.matches?(User.first) }.should raise_error(ScriptError, /which is different from the subject record/)
+ end
+
+ it 'should raise an error if cannot find a new scope value' do
+ @matcher = define_and_validate(:scope => :email).scope(:email)
+
+ User.stub!(:find).and_return do |many, conditions|
+ if many == :all
+ 1000.upto(1100).map{|i| User.new(:email => i) }
+ else
+ User.new(:username => 'jose')
+ end
+ end
+ lambda { @matcher.matches?(@model) }.should raise_error(ScriptError)
+
+ User.stub!(:find).and_return do |many, conditions|
+ if many == :all
+ 1000.upto(1099).map{|i| User.new(:email => i) }
+ else
+ User.new(:username => 'jose')
+ end
+ end
+ lambda { @matcher.matches?(@model) }.should_not raise_error(ScriptError)
+ end
+
+ describe 'when null values are not allowed' do
+ def define_and_validate(options={})
+ @model = define_model :user, :id => DataMapper::Types::Serial, :username => [String, {:nullable => false}] do
+ validates_is_unique :username, options
+ end
+
+ User.create(:username => 'jose')
+ validate_is_unique(:username)
+ end
+
+ it { should define_and_validate }
+ it { should define_and_validate(:nullable => false).nullable(false) }
+
+ it 'should raise an error if nullable is true but we cannot save nil values in the database'do
+ lambda { should define_and_validate.nullable }.should raise_error(ScriptError, /You declared that username accepts nil values in validate_is_unique, but I cannot save nil values in the database, got/)
+ end
+ end
+ end
+
+ describe 'macros' do
+ before(:each){ define_and_validate(:scope => :email) }
+
+ should_validate_is_unique :username
+ should_validate_is_unique :username, :scope => :email
+ should_not_validate_is_unique :email
+ should_not_validate_is_unique :username, :scope => :access_code
+
+ should_validate_is_unique :username do |m|
+ m.scope :email
+ end
+ end
+end

0 comments on commit 39c44f0

Please sign in to comment.