Module: ThoughtBot::Shoulda::ActiveRecord
- Includes:
- Private
- Included in:
- Test::Unit::TestCase
- Defined in:
- lib/shoulda/active_record_helpers.rb
Overview
Macro test helpers for your active record models
These helpers will test most of the validations and associations for your ActiveRecord models.
class UserTest < Test::Unit::TestCase
should_require_attributes :name, :phone_number
should_not_allow_values_for :phone_number, "abcd", "1234"
should_allow_values_for :phone_number, "(123) 456-7890"
should_protect_attributes :password
should_have_one :profile
should_have_many :dogs
should_have_many :messes, :through => :dogs
should_belong_to :lover
end
For all of these helpers, the last parameter may be a hash of options.
Instance Method Summary collapse
-
#should_allow_values_for(attribute, *good_values) ⇒ Object
Ensures that the attribute can be set to the given values.
-
#should_belong_to(*associations) ⇒ Object
Ensure that the belongs_to relationship exists.
-
#should_ensure_length_at_least(attribute, min_length, opts = {}) ⇒ Object
Ensures that the length of the attribute is at least a certain length Requires an existing record.
-
#should_ensure_length_in_range(attribute, range, opts = {}) ⇒ Object
Ensures that the length of the attribute is in the given range Requires an existing record.
-
#should_ensure_length_is(attribute, length, opts = {}) ⇒ Object
Ensures that the length of the attribute is exactly a certain length Requires an existing record.
-
#should_ensure_value_in_range(attribute, range, opts = {}) ⇒ Object
Ensure that the attribute is in the range specified Requires an existing record.
-
#should_have_and_belong_to_many(*associations) ⇒ Object
Ensures that the has_and_belongs_to_many relationship exists, and that the join table is in place.
-
#should_have_class_methods(*methods) ⇒ Object
Ensure that the given class methods are defined on the model.
-
#should_have_db_column(name, opts = {}) ⇒ Object
Ensure that the given column is defined on the models backing SQL table.
-
#should_have_db_columns(*columns) ⇒ Object
Ensure that the given columns are defined on the models backing SQL table.
-
#should_have_indices(*columns) ⇒ Object
(also: #should_have_index)
Ensures that there are DB indices on the given columns or tuples of columns.
-
#should_have_instance_methods(*methods) ⇒ Object
Ensure that the given instance methods are defined on the model.
-
#should_have_many(*associations) ⇒ Object
Ensures that the has_many relationship exists.
-
#should_have_one(*associations) ⇒ Object
Ensure that the has_one relationship exists.
-
#should_have_readonly_attributes(*attributes) ⇒ Object
Ensures that the attribute cannot be changed once the record has been created.
-
#should_not_allow_values_for(attribute, *bad_values) ⇒ Object
Ensures that the attribute cannot be set to the given values Requires an existing record.
-
#should_only_allow_numeric_values_for(*attributes) ⇒ Object
Ensure that the attribute is numeric Requires an existing record.
-
#should_protect_attributes(*attributes) ⇒ Object
Ensures that the attribute cannot be set on mass update.
-
#should_require_acceptance_of(*attributes) ⇒ Object
Ensures that the model cannot be saved if one of the attributes listed is not accepted.
-
#should_require_attributes(*attributes) ⇒ Object
Ensures that the model cannot be saved if one of the attributes listed is not present.
-
#should_require_unique_attributes(*attributes) ⇒ Object
Ensures that the model cannot be saved if one of the attributes listed is not unique.
Methods included from Private
Instance Method Details
#should_allow_values_for(attribute, *good_values) ⇒ Object
Ensures that the attribute can be set to the given values. Requires an existing record
Example:
should_allow_values_for :isbn, "isbn 1 2345 6789 0", "ISBN 1-2345-6789-0"
185 186 187 188 189 190 191 192 193 194 195 196 |
# File 'lib/shoulda/active_record_helpers.rb', line 185 def should_allow_values_for(attribute, *good_values) (good_values) klass = model_class good_values.each do |v| should "allow #{attribute} to be set to #{v.inspect}" do assert object = klass.find(:first), "Can't find first #{klass}" object.send("#{attribute}=", v) object.save assert_nil object.errors.on(attribute) end end end |
#should_belong_to(*associations) ⇒ Object
Ensure that the belongs_to relationship exists.
should_belong_to :parent
536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 |
# File 'lib/shoulda/active_record_helpers.rb', line 536 def should_belong_to(*associations) (associations) klass = model_class associations.each do |association| should "belong_to #{association}" do reflection = klass.reflect_on_association(association) assert reflection, "#{klass.name} does not have any relationship to #{association}" assert_equal :belongs_to, reflection.macro unless reflection.[:polymorphic] associated_klass = (reflection.[:class_name] || association.to_s.classify).constantize fk = reflection.[:foreign_key] || reflection.primary_key_name assert klass.column_names.include?(fk.to_s), "#{klass.name} does not have a #{fk} foreign key." end end end end |
#should_ensure_length_at_least(attribute, min_length, opts = {}) ⇒ Object
Ensures that the length of the attribute is at least a certain length Requires an existing record
Options:
-
:short_message- value the test expects to find inerrors.on(:attribute).Regexp or string. Default =
/short/
Example:
should_ensure_length_at_least :name, 3
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 |
# File 'lib/shoulda/active_record_helpers.rb', line 273 def should_ensure_length_at_least(attribute, min_length, opts = {}) = ([opts], :short_message) ||= /short/ klass = model_class if min_length > 0 min_value = "x" * (min_length - 1) should "not allow #{attribute} to be less than #{min_length} chars long" do assert object = klass.find(:first), "Can't find first #{klass}" object.send("#{attribute}=", min_value) assert !object.save, "Saved #{klass} with #{attribute} set to \"#{min_value}\"" assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{min_value}\"" assert_contains(object.errors.on(attribute), , "when set to \"#{min_value}\"") end end should "allow #{attribute} to be at least #{min_length} chars long" do valid_value = "x" * (min_length) assert object = klass.find(:first), "Can't find first #{klass}" object.send("#{attribute}=", valid_value) assert object.save, "Could not save #{klass} with #{attribute} set to \"#{valid_value}\"" end end |
#should_ensure_length_in_range(attribute, range, opts = {}) ⇒ Object
Ensures that the length of the attribute is in the given range Requires an existing record
Options:
-
:short_message- value the test expects to find inerrors.on(:attribute).Regexp or string. Default =
/short/ -
:long_message- value the test expects to find inerrors.on(:attribute).Regexp or string. Default =
/long/
Example:
should_ensure_length_in_range :password, (6..20)
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 |
# File 'lib/shoulda/active_record_helpers.rb', line 210 def should_ensure_length_in_range(attribute, range, opts = {}) , = ([opts], :short_message, :long_message) ||= /short/ ||= /long/ klass = model_class min_length = range.first max_length = range.last same_length = (min_length == max_length) if min_length > 0 should "not allow #{attribute} to be less than #{min_length} chars long" do min_value = "x" * (min_length - 1) assert object = klass.find(:first), "Can't find first #{klass}" object.send("#{attribute}=", min_value) assert !object.save, "Saved #{klass} with #{attribute} set to \"#{min_value}\"" assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{min_value}\"" assert_contains(object.errors.on(attribute), , "when set to \"#{min_value}\"") end end if min_length > 0 should "allow #{attribute} to be exactly #{min_length} chars long" do min_value = "x" * min_length assert object = klass.find(:first), "Can't find first #{klass}" object.send("#{attribute}=", min_value) object.save assert_does_not_contain(object.errors.on(attribute), , "when set to \"#{min_value}\"") end end should "not allow #{attribute} to be more than #{max_length} chars long" do max_value = "x" * (max_length + 1) assert object = klass.find(:first), "Can't find first #{klass}" object.send("#{attribute}=", max_value) assert !object.save, "Saved #{klass} with #{attribute} set to \"#{max_value}\"" assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{max_value}\"" assert_contains(object.errors.on(attribute), , "when set to \"#{max_value}\"") end unless same_length should "allow #{attribute} to be exactly #{max_length} chars long" do max_value = "x" * max_length assert object = klass.find(:first), "Can't find first #{klass}" object.send("#{attribute}=", max_value) object.save assert_does_not_contain(object.errors.on(attribute), , "when set to \"#{max_value}\"") end end end |
#should_ensure_length_is(attribute, length, opts = {}) ⇒ Object
Ensures that the length of the attribute is exactly a certain length Requires an existing record
Options:
-
:message- value the test expects to find inerrors.on(:attribute).Regexp or string. Default =
/short/
Example:
should_ensure_length_is :ssn, 9
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 |
# File 'lib/shoulda/active_record_helpers.rb', line 307 def should_ensure_length_is(attribute, length, opts = {}) = ([opts], :message) ||= /wrong length/ klass = model_class should "not allow #{attribute} to be less than #{length} chars long" do min_value = "x" * (length - 1) assert object = klass.find(:first), "Can't find first #{klass}" object.send("#{attribute}=", min_value) assert !object.save, "Saved #{klass} with #{attribute} set to \"#{min_value}\"" assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{min_value}\"" assert_contains(object.errors.on(attribute), , "when set to \"#{min_value}\"") end should "not allow #{attribute} to be greater than #{length} chars long" do max_value = "x" * (length + 1) assert object = klass.find(:first), "Can't find first #{klass}" object.send("#{attribute}=", max_value) assert !object.save, "Saved #{klass} with #{attribute} set to \"#{max_value}\"" assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{max_value}\"" assert_contains(object.errors.on(attribute), , "when set to \"#{max_value}\"") end should "allow #{attribute} to be #{length} chars long" do valid_value = "x" * (length) assert object = klass.find(:first), "Can't find first #{klass}" object.send("#{attribute}=", valid_value) object.save assert_does_not_contain(object.errors.on(attribute), , "when set to \"#{valid_value}\"") end end |
#should_ensure_value_in_range(attribute, range, opts = {}) ⇒ Object
Ensure that the attribute is in the range specified Requires an existing record
Options:
-
:low_message- value the test expects to find inerrors.on(:attribute).Regexp or string. Default =
/included/ -
:high_message- value the test expects to find inerrors.on(:attribute).Regexp or string. Default =
/included/
Example:
should_ensure_value_in_range :age, (0..100)
353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 |
# File 'lib/shoulda/active_record_helpers.rb', line 353 def should_ensure_value_in_range(attribute, range, opts = {}) , = ([opts], :low_message, :high_message) ||= /included/ ||= /included/ klass = model_class min = range.first max = range.last should "not allow #{attribute} to be less than #{min}" do v = min - 1 assert object = klass.find(:first), "Can't find first #{klass}" object.send("#{attribute}=", v) assert !object.save, "Saved #{klass} with #{attribute} set to \"#{v}\"" assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{v}\"" assert_contains(object.errors.on(attribute), , "when set to \"#{v}\"") end should "allow #{attribute} to be #{min}" do v = min assert object = klass.find(:first), "Can't find first #{klass}" object.send("#{attribute}=", v) object.save assert_does_not_contain(object.errors.on(attribute), , "when set to \"#{v}\"") end should "not allow #{attribute} to be more than #{max}" do v = max + 1 assert object = klass.find(:first), "Can't find first #{klass}" object.send("#{attribute}=", v) assert !object.save, "Saved #{klass} with #{attribute} set to \"#{v}\"" assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{v}\"" assert_contains(object.errors.on(attribute), , "when set to \"#{v}\"") end should "allow #{attribute} to be #{max}" do v = max assert object = klass.find(:first), "Can't find first #{klass}" object.send("#{attribute}=", v) object.save assert_does_not_contain(object.errors.on(attribute), , "when set to \"#{v}\"") end end |
#should_have_and_belong_to_many(*associations) ⇒ Object
Ensures that the has_and_belongs_to_many relationship exists, and that the join table is in place.
should_have_and_belong_to_many :posts, :cars
517 518 519 520 521 522 523 524 525 526 527 528 529 530 |
# File 'lib/shoulda/active_record_helpers.rb', line 517 def should_have_and_belong_to_many(*associations) (associations) klass = model_class associations.each do |association| should "should have and belong to many #{association}" do reflection = klass.reflect_on_association(association) assert reflection, "#{klass.name} does not have any relationship to #{association}" assert_equal :has_and_belongs_to_many, reflection.macro table = reflection.[:join_table] assert ::ActiveRecord::Base.connection.tables.include?(table), "table #{table} doesn't exist" end end end |
#should_have_class_methods(*methods) ⇒ Object
Ensure that the given class methods are defined on the model.
should_have_class_methods :find, :destroy
558 559 560 561 562 563 564 565 566 |
# File 'lib/shoulda/active_record_helpers.rb', line 558 def should_have_class_methods(*methods) (methods) klass = model_class methods.each do |method| should "respond to class method ##{method}" do assert_respond_to klass, method, "#{klass.name} does not have class method #{method}" end end end |
#should_have_db_column(name, opts = {}) ⇒ Object
Ensure that the given column is defined on the models backing SQL table. The options are the same as the instance variables defined on the column definition: :precision, :limit, :default, :null, :primary, :type, :scale, and :sql_type.
should_have_db_column :email, :type => "string", :default => nil, :precision => nil, :limit => 255,
:null => true, :primary => false, :scale => nil, :sql_type => 'varchar(255)'
606 607 608 609 610 611 612 613 614 615 616 617 |
# File 'lib/shoulda/active_record_helpers.rb', line 606 def should_have_db_column(name, opts = {}) klass = model_class test_name = "have column named :#{name}" test_name += " with options " + opts.inspect unless opts.empty? should test_name do column = klass.columns.detect {|c| c.name == name.to_s } assert column, "#{klass.name} does not have column #{name}" opts.each do |k, v| assert_equal column.instance_variable_get("@#{k}").to_s, v.to_s, ":#{name} column on table for #{klass} does not match option :#{k}" end end end |
#should_have_db_columns(*columns) ⇒ Object
Ensure that the given columns are defined on the models backing SQL table.
should_have_db_columns :id, :email, :name, :created_at
586 587 588 589 590 591 592 593 594 595 596 597 |
# File 'lib/shoulda/active_record_helpers.rb', line 586 def should_have_db_columns(*columns) column_type = (columns, :type) klass = model_class columns.each do |name| test_name = "have column #{name}" test_name += " of type #{column_type}" if column_type should test_name do column = klass.columns.detect {|c| c.name == name.to_s } assert column, "#{klass.name} does not have column #{name}" end end end |
#should_have_indices(*columns) ⇒ Object Also known as: should_have_index
Ensures that there are DB indices on the given columns or tuples of columns. Also aliased to should_have_index for readability
should_have_indices :email, :name, [:commentable_type, :commentable_id]
should_have_index :age
625 626 627 628 629 630 631 632 633 634 635 |
# File 'lib/shoulda/active_record_helpers.rb', line 625 def should_have_indices(*columns) table = model_class.name.tableize indices = ::ActiveRecord::Base.connection.indexes(table).map(&:columns) columns.each do |column| should "have index on #{table} for #{column.inspect}" do columns = [column].flatten.map(&:to_s) assert_contains(indices, columns) end end end |
#should_have_instance_methods(*methods) ⇒ Object
Ensure that the given instance methods are defined on the model.
should_have_instance_methods :email, :name, :name=
572 573 574 575 576 577 578 579 580 |
# File 'lib/shoulda/active_record_helpers.rb', line 572 def should_have_instance_methods(*methods) (methods) klass = model_class methods.each do |method| should "respond to instance method ##{method}" do assert_respond_to klass.new, method, "#{klass.name} does not have instance method #{method}" end end end |
#should_have_many(*associations) ⇒ Object
Ensures that the has_many relationship exists. Will also test that the associated table has the required columns. Works with polymorphic associations.
Options:
-
:through- association name forhas_many :through -
:dependent- tests that the association makes use of the dependent option.
Example:
should_have_many :friends
should_have_many :enemies, :through => :friends
should_have_many :enemies, :dependent => :destroy
435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 |
# File 'lib/shoulda/active_record_helpers.rb', line 435 def should_have_many(*associations) through, dependent = (associations, :through, :dependent) klass = model_class associations.each do |association| name = "have many #{association}" name += " through #{through}" if through name += " dependent => #{dependent}" if dependent should name do reflection = klass.reflect_on_association(association) assert reflection, "#{klass.name} does not have any relationship to #{association}" assert_equal :has_many, reflection.macro associated_klass = (reflection.[:class_name] || association.to_s.classify).constantize if through through_reflection = klass.reflect_on_association(through) assert through_reflection, "#{klass.name} does not have any relationship to #{through}" assert_equal(through, reflection.[:through]) end if dependent assert_equal dependent.to_s, reflection.[:dependent].to_s, "#{associated_klass.name} should have #{dependent} dependency" end # Check for the existence of the foreign key on the other table unless reflection.[:through] if reflection.[:foreign_key] fk = reflection.[:foreign_key] elsif reflection.[:as] fk = reflection.[:as].to_s.foreign_key else fk = reflection.primary_key_name end assert associated_klass.column_names.include?(fk.to_s), "#{associated_klass.name} does not have a #{fk} foreign key." end end end end |
#should_have_one(*associations) ⇒ Object
Ensure that the has_one relationship exists. Will also test that the associated table has the required columns. Works with polymorphic associations.
Example:
should_have_one :god # unless hindu
485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 |
# File 'lib/shoulda/active_record_helpers.rb', line 485 def should_have_one(*associations) (associations) klass = model_class associations.each do |association| should "have one #{association}" do reflection = klass.reflect_on_association(association) assert reflection, "#{klass.name} does not have any relationship to #{association}" assert_equal :has_one, reflection.macro associated_klass = (reflection.[:class_name] || association.to_s.camelize).constantize if reflection.[:foreign_key] fk = reflection.[:foreign_key] elsif reflection.[:as] fk = reflection.[:as].to_s.foreign_key fk_type = fk.gsub(/_id$/, '_type') assert associated_klass.column_names.include?(fk_type), "#{associated_klass.name} does not have a #{fk_type} column." else fk = klass.name.foreign_key end assert associated_klass.column_names.include?(fk.to_s), "#{associated_klass.name} does not have a #{fk} foreign key." end end end |
#should_have_readonly_attributes(*attributes) ⇒ Object
Ensures that the attribute cannot be changed once the record has been created. Requires an existing record.
should_have_readonly_attributes :password, :admin_flag
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/shoulda/active_record_helpers.rb', line 137 def should_have_readonly_attributes(*attributes) (attributes) klass = model_class attributes.each do |attribute| attribute = attribute.to_sym should "make #{attribute} read-only" do readonly = klass.readonly_attributes || [] assert readonly.include?(attribute.to_s), (readonly.empty? ? "#{klass} attribute #{attribute} is not read-only" : "#{klass} is making #{readonly.to_a.to_sentence} read-only, but not #{attribute}.") end end end |
#should_not_allow_values_for(attribute, *bad_values) ⇒ Object
Ensures that the attribute cannot be set to the given values Requires an existing record
Options:
-
:message- value the test expects to find inerrors.on(:attribute).Regexp or string. Default =
/invalid/
Example:
should_not_allow_values_for :isbn, "bad 1", "bad 2"
164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/shoulda/active_record_helpers.rb', line 164 def should_not_allow_values_for(attribute, *bad_values) = (bad_values, :message) ||= /invalid/ klass = model_class bad_values.each do |v| should "not allow #{attribute} to be set to #{v.inspect}" do assert object = klass.find(:first), "Can't find first #{klass}" object.send("#{attribute}=", v) assert !object.save, "Saved #{klass} with #{attribute} set to \"#{v}\"" assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{v}\"" assert_contains(object.errors.on(attribute), , "when set to \"#{v}\"") end end end |
#should_only_allow_numeric_values_for(*attributes) ⇒ Object
Ensure that the attribute is numeric Requires an existing record
Options:
-
:message- value the test expects to find inerrors.on(:attribute).Regexp or string. Default =
/number/
Example:
should_only_allow_numeric_values_for :age
407 408 409 410 411 412 413 414 415 416 417 418 419 420 |
# File 'lib/shoulda/active_record_helpers.rb', line 407 def should_only_allow_numeric_values_for(*attributes) = (attributes, :message) ||= /number/ klass = model_class attributes.each do |attribute| attribute = attribute.to_sym should "only allow numeric values for #{attribute}" do assert object = klass.find(:first), "Can't find first #{klass}" object.send(:"#{attribute}=", "abcd") assert !object.valid?, "Instance is still valid" assert_contains(object.errors.on(attribute), ) end end end |
#should_protect_attributes(*attributes) ⇒ Object
Ensures that the attribute cannot be set on mass update. Requires an existing record.
should_protect_attributes :password, :admin_flag
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/shoulda/active_record_helpers.rb', line 114 def should_protect_attributes(*attributes) (attributes) klass = model_class attributes.each do |attribute| attribute = attribute.to_sym should "protect #{attribute} from mass updates" do protected = klass.protected_attributes || [] accessible = klass.accessible_attributes || [] assert protected.include?(attribute.to_s) || !accessible.include?(attribute.to_s), (accessible.empty? ? "#{klass} is protecting #{protected.to_a.to_sentence}, but not #{attribute}." : "#{klass} has made #{attribute} accessible") end end end |
#should_require_acceptance_of(*attributes) ⇒ Object
Ensures that the model cannot be saved if one of the attributes listed is not accepted.
Options:
-
:message- value the test expects to find inerrors.on(:attribute).Regexp or string. Default =
/must be accepted/
Example:
should_require_acceptance_of :eula
648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 |
# File 'lib/shoulda/active_record_helpers.rb', line 648 def should_require_acceptance_of(*attributes) = (attributes, :message) ||= /must be accepted/ klass = model_class attributes.each do |attribute| should "require #{attribute} to be accepted" do object = klass.new object.send("#{attribute}=", false) assert !object.valid?, "#{klass.name} does not require acceptance of #{attribute}." assert object.errors.on(attribute), "#{klass.name} does not require acceptance of #{attribute}." assert_contains(object.errors.on(attribute), ) end end end |
#should_require_attributes(*attributes) ⇒ Object
Ensures that the model cannot be saved if one of the attributes listed is not present.
Options:
-
:message- value the test expects to find inerrors.on(:attribute).Regexp or string. Default =
/blank/
Example:
should_require_attributes :name, :phone_number
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/shoulda/active_record_helpers.rb', line 32 def should_require_attributes(*attributes) = (attributes, :message) ||= /blank/ klass = model_class attributes.each do |attribute| should "require #{attribute} to be set" do object = klass.new object.send("#{attribute}=", nil) assert !object.valid?, "#{klass.name} does not require #{attribute}." assert object.errors.on(attribute), "#{klass.name} does not require #{attribute}." assert_contains(object.errors.on(attribute), ) end end end |
#should_require_unique_attributes(*attributes) ⇒ Object
Ensures that the model cannot be saved if one of the attributes listed is not unique. Requires an existing record
Options:
-
:message- value the test expects to find inerrors.on(:attribute).Regexp or string. Default =
/taken/ -
:scoped_to- field(s) to scope the uniqueness to.
Examples:
should_require_unique_attributes :keyword, :username
should_require_unique_attributes :name, :message => "O NOES! SOMEONE STOELED YER NAME!"
should_require_unique_attributes :email, :scoped_to => :name
should_require_unique_attributes :address, :scoped_to => [:first_name, :last_name]
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/shoulda/active_record_helpers.rb', line 62 def should_require_unique_attributes(*attributes) , scope = (attributes, :message, :scoped_to) scope = [*scope].compact ||= /taken/ klass = model_class attributes.each do |attribute| attribute = attribute.to_sym should "require unique value for #{attribute}#{" scoped to #{scope.join(', ')}" unless scope.blank?}" do assert existing = klass.find(:first), "Can't find first #{klass}" object = klass.new object.send(:"#{attribute}=", existing.send(attribute)) if !scope.blank? scope.each do |s| assert_respond_to object, :"#{s}=", "#{klass.name} doesn't seem to have a #{s} attribute." object.send(:"#{s}=", existing.send(s)) end end assert !object.valid?, "#{klass.name} does not require a unique value for #{attribute}." assert object.errors.on(attribute), "#{klass.name} does not require a unique value for #{attribute}." assert_contains(object.errors.on(attribute), ) # Now test that the object is valid when changing the scoped attribute # TODO: There is a chance that we could change the scoped field # to a value that's already taken. An alternative implementation # could actually find all values for scope and create a unique # one. if !scope.blank? scope.each do |s| # Assume the scope is a foreign key if the field is nil object.send(:"#{s}=", existing.send(s).nil? ? 1 : existing.send(s).next) end object.errors.clear object.valid? scope.each do |s| assert_does_not_contain(object.errors.on(attribute), , "after :#{s} set to #{object.send(s.to_sym)}") end end end end end |