diff --git a/NEWS.md b/NEWS.md index 719d992d..8b061e43 100644 --- a/NEWS.md +++ b/NEWS.md @@ -22,6 +22,9 @@ * You cannot test that your attribute allows nil (`.in_array([nil])`) if the column does not allow null values. +* Change `validate_uniqueness_of(...)` so that it provides default values for + non-nullable attributes. + # v 2.5.0 * Fix Rails/Test::Unit integration to ensure that the test case classes we are diff --git a/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb b/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb index ee4d121e..1d9d5bc9 100644 --- a/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb +++ b/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb @@ -115,6 +115,11 @@ module Shoulda # :nodoc: @subject.class.new.tap do |instance| instance.__send__("#{@attribute}=", value) + + other_non_nullable_columns.each do |non_nullable_column| + instance.__send__("#{non_nullable_column.name}=", correct_type_for_column(non_nullable_column)) + end + if has_secure_password? instance.password = 'password' instance.password_confirmation = 'password' @@ -193,7 +198,7 @@ module Shoulda # :nodoc: end def correct_type_for_column(column) - if column.type == :string + if column.type == :string || column.type == :binary '0' elsif column.type == :datetime DateTime.now @@ -215,6 +220,12 @@ module Shoulda # :nodoc: end value end + + def other_non_nullable_columns + @subject.class.columns.select do |column| + column.name != @attribute && !column.null && !column.primary + end + end end end end diff --git a/spec/shoulda/matchers/active_model/validate_uniqueness_of_matcher_spec.rb b/spec/shoulda/matchers/active_model/validate_uniqueness_of_matcher_spec.rb index 7c28f880..45842fad 100644 --- a/spec/shoulda/matchers/active_model/validate_uniqueness_of_matcher_spec.rb +++ b/spec/shoulda/matchers/active_model/validate_uniqueness_of_matcher_spec.rb @@ -341,6 +341,42 @@ describe Shoulda::Matchers::ActiveModel::ValidateUniquenessOfMatcher do end end + context "a model with non-nullable attribute" do + context "of type" do + [:string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |type| + context type do + it "does not raise an error" do + model = define_model_with_non_nullable(type) + expect { expect(model).to matcher }.not_to raise_error + end + end + end + end + + context "that is a primary key" do + it "does not cause duplicate entry errors by re-using default values for primary keys" do + create_table :examples, id: false do |t| + t.string :attr + t.integer :non_nullable, primary: true + end + model_class = define_model(:example, attr: :string) do + validates_uniqueness_of :attr + end + model_1 = model_class.new + model_2 = model_class.new + expect(model_1).to matcher + expect { expect(model_2).to matcher }.not_to raise_error + end + end + + def define_model_with_non_nullable(type) + define_model(:example, attr: :string, non_nullable: { type: type, options: { null: false } }) do + attr_accessible :attr, :non_nullable + validates_uniqueness_of :attr + end.new + end + end + def case_sensitive_validation_with_existing_value(attr_type) model = define_model(:example, attr: attr_type) do attr_accessible :attr