Module: ThoughtBot::Shoulda::ActiveRecord::Macros
- Includes:
- MacroHelpers
- Included in:
- Test::Unit::TestCase
- Defined in:
- lib/shoulda/active_record/macros.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
-
#load_all_fixtures ⇒ Object
DEPRECATED: Use
fixtures :allinstead. -
#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.
-
#should_ensure_length_in_range(attribute, range, opts = {}) ⇒ Object
Ensures that the length of the attribute is in the given range.
-
#should_ensure_length_is(attribute, length, opts = {}) ⇒ Object
Ensures that the length of the attribute is exactly a certain length.
-
#should_ensure_value_in_range(attribute, range, opts = {}) ⇒ Object
Ensure that the attribute is in the range specified.
-
#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_named_scope(scope_call, *args) ⇒ Object
Ensures that the model has a method named scope_name that returns a NamedScope object with the proxy options set to the options you supply.
-
#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.
-
#should_only_allow_numeric_values_for(*attributes) ⇒ Object
Ensure that the attribute is numeric.
-
#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 MacroHelpers
Instance Method Details
#load_all_fixtures ⇒ Object
DEPRECATED: Use fixtures :all instead
Loads all fixture files (test/fixtures/*.yml)
46 47 48 49 |
# File 'lib/shoulda/active_record/macros.rb', line 46 def load_all_fixtures warn "[DEPRECATION] load_all_fixtures is deprecated. Use `fixtures :all` instead." fixtures :all end |
#should_allow_values_for(attribute, *good_values) ⇒ Object
Ensures that the attribute can be set to the given values.
If an instance variable has been created in the setup named after the model being tested, then this method will use that. Otherwise, it will create a new instance to test against.
Example:
should_allow_values_for :isbn, "isbn 1 2345 6789 0", "ISBN 1-2345-6789-0"
204 205 206 207 208 209 210 211 212 |
# File 'lib/shoulda/active_record/macros.rb', line 204 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_good_value(klass, attribute, v) end end end |
#should_belong_to(*associations) ⇒ Object
Ensure that the belongs_to relationship exists.
should_belong_to :parent
528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 |
# File 'lib/shoulda/active_record/macros.rb', line 528 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.camelize).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
If an instance variable has been created in the setup named after the model being tested, then this method will use that. Otherwise, it will create a new instance to test against.
Options:
-
:short_message- value the test expects to find inerrors.on(:attribute). Regexp or string. Default =I18n.translate('activerecord.errors.messages.too_short') % min_length
Example:
should_ensure_length_at_least :name, 3
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 |
# File 'lib/shoulda/active_record/macros.rb', line 279 def should_ensure_length_at_least(attribute, min_length, opts = {}) = ([opts], :short_message) ||= (:too_short, :count => min_length) 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_bad_value(klass, attribute, min_value, ) end end should "allow #{attribute} to be at least #{min_length} chars long" do valid_value = "x" * (min_length) assert_good_value(klass, attribute, 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
If an instance variable has been created in the setup named after the model being tested, then this method will use that. Otherwise, it will create a new instance to test against.
Options:
-
:short_message- value the test expects to find inerrors.on(:attribute). Regexp or string. Default =I18n.translate('activerecord.errors.messages.too_short') % range.first -
:long_message- value the test expects to find inerrors.on(:attribute). Regexp or string. Default =I18n.translate('activerecord.errors.messages.too_long') % range.last
Example:
should_ensure_length_in_range :password, (6..20)
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 262 263 264 |
# File 'lib/shoulda/active_record/macros.rb', line 229 def should_ensure_length_in_range(attribute, range, opts = {}) , = ([opts], :short_message, :long_message) ||= (:too_short, :count => range.first) ||= (:too_long, :count => range.last) 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_bad_value(klass, attribute, 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_good_value(klass, attribute, min_value, ) end end should "not allow #{attribute} to be more than #{max_length} chars long" do max_value = "x" * (max_length + 1) assert_bad_value(klass, attribute, max_value, ) end unless same_length should "allow #{attribute} to be exactly #{max_length} chars long" do max_value = "x" * max_length assert_good_value(klass, attribute, 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
If an instance variable has been created in the setup named after the model being tested, then this method will use that. Otherwise, it will create a new instance to test against.
Options:
-
:message- value the test expects to find inerrors.on(:attribute). Regexp or string. Default =I18n.translate('activerecord.errors.messages.wrong_length') % length
Example:
should_ensure_length_is :ssn, 9
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 |
# File 'lib/shoulda/active_record/macros.rb', line 310 def should_ensure_length_is(attribute, length, opts = {}) = ([opts], :message) ||= (:wrong_length, :count => length) klass = model_class should "not allow #{attribute} to be less than #{length} chars long" do min_value = "x" * (length - 1) assert_bad_value(klass, attribute, min_value, ) end should "not allow #{attribute} to be greater than #{length} chars long" do max_value = "x" * (length + 1) assert_bad_value(klass, attribute, max_value, ) end should "allow #{attribute} to be #{length} chars long" do valid_value = "x" * (length) assert_good_value(klass, attribute, valid_value, ) end end |
#should_ensure_value_in_range(attribute, range, opts = {}) ⇒ Object
Ensure that the attribute is in the range specified
If an instance variable has been created in the setup named after the model being tested, then this method will use that. Otherwise, it will create a new instance to test against.
Options:
-
:low_message- value the test expects to find inerrors.on(:attribute). Regexp or string. Default =I18n.translate('activerecord.errors.messages.inclusion') -
:high_message- value the test expects to find inerrors.on(:attribute). Regexp or string. Default =I18n.translate('activerecord.errors.messages.inclusion')
Example:
should_ensure_value_in_range :age, (0..100)
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 |
# File 'lib/shoulda/active_record/macros.rb', line 347 def should_ensure_value_in_range(attribute, range, opts = {}) , = ([opts], :low_message, :high_message) ||= (:inclusion) ||= (:inclusion) klass = model_class min = range.first max = range.last should "not allow #{attribute} to be less than #{min}" do v = min - 1 assert_bad_value(klass, attribute, v, ) end should "allow #{attribute} to be #{min}" do v = min assert_good_value(klass, attribute, v, ) end should "not allow #{attribute} to be more than #{max}" do v = max + 1 assert_bad_value(klass, attribute, v, ) end should "allow #{attribute} to be #{max}" do v = max assert_good_value(klass, attribute, 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
509 510 511 512 513 514 515 516 517 518 519 520 521 522 |
# File 'lib/shoulda/active_record/macros.rb', line 509 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.to_s), "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
550 551 552 553 554 555 556 557 558 |
# File 'lib/shoulda/active_record/macros.rb', line 550 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)'
598 599 600 601 602 603 604 605 606 607 608 609 |
# File 'lib/shoulda/active_record/macros.rb', line 598 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
578 579 580 581 582 583 584 585 586 587 588 589 |
# File 'lib/shoulda/active_record/macros.rb', line 578 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
617 618 619 620 621 622 623 624 625 626 627 |
# File 'lib/shoulda/active_record/macros.rb', line 617 def should_have_indices(*columns) table = model_class.table_name 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=
564 565 566 567 568 569 570 571 572 |
# File 'lib/shoulda/active_record/macros.rb', line 564 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
415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 |
# File 'lib/shoulda/active_record/macros.rb', line 415 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 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, "#{association} 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 associated_klass_name = (reflection.[:class_name] || association.to_s.classify) associated_klass = associated_klass_name.constantize 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_named_scope(scope_call, *args) ⇒ Object
Ensures that the model has a method named scope_name that returns a NamedScope object with the proxy options set to the options you supply. scope_name can be either a symbol, or a method call which will be evaled against the model. The eval’d method call has access to all the same instance variables that a should statement would.
Options: Any of the options that the named scope would pass on to find.
Example:
should_have_named_scope :visible, :conditions => {:visible => true}
Passes for
named_scope :visible, :conditions => {:visible => true}
Or for
def self.visible
scoped(:conditions => {:visible => true})
end
You can test lambdas or methods that return ActiveRecord#scoped calls:
should_have_named_scope 'recent(5)', :limit => 5
should_have_named_scope 'recent(1)', :limit => 1
Passes for
named_scope :recent, lambda {|c| {:limit => c}}
Or for
def self.recent(c)
scoped(:limit => c)
end
691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 |
# File 'lib/shoulda/active_record/macros.rb', line 691 def should_have_named_scope(scope_call, *args) klass = model_class scope_opts = args. scope_call = scope_call.to_s context scope_call do setup do @scope = eval("#{klass}.#{scope_call}") end should "return a scope object" do assert_equal ::ActiveRecord::NamedScope::Scope, @scope.class end unless scope_opts.empty? should "scope itself to #{scope_opts.inspect}" do assert_equal scope_opts, @scope. 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.
Options:
-
:dependent- tests that the association makes use of the dependent option.
Example:
should_have_one :god # unless hindu
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 |
# File 'lib/shoulda/active_record/macros.rb', line 469 def should_have_one(*associations) dependent = (associations, :dependent) klass = model_class associations.each do |association| name = "have one #{association}" 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_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." if dependent assert_equal dependent.to_s, reflection.[:dependent].to_s, "#{association} should have #{dependent} dependency" end end end end |
#should_have_readonly_attributes(*attributes) ⇒ Object
Ensures that the attribute cannot be changed once the record has been created.
should_have_readonly_attributes :password, :admin_flag
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/shoulda/active_record/macros.rb', line 154 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
If an instance variable has been created in the setup named after the model being tested, then this method will use that. Otherwise, it will create a new instance to test against.
Options:
-
:message- value the test expects to find inerrors.on(:attribute). Regexp or string. Default =I18n.translate('activerecord.errors.messages.invalid')
Example:
should_not_allow_values_for :isbn, "bad 1", "bad 2"
184 185 186 187 188 189 190 191 192 193 |
# File 'lib/shoulda/active_record/macros.rb', line 184 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_bad_value(klass, attribute, v, ) end end end |
#should_only_allow_numeric_values_for(*attributes) ⇒ Object
Ensure that the attribute is numeric
If an instance variable has been created in the setup named after the model being tested, then this method will use that. Otherwise, it will create a new instance to test against.
Options:
-
:message- value the test expects to find inerrors.on(:attribute). Regexp or string. Default =I18n.translate('activerecord.errors.messages.not_a_number')
Example:
should_only_allow_numeric_values_for :age
390 391 392 393 394 395 396 397 398 399 400 |
# File 'lib/shoulda/active_record/macros.rb', line 390 def should_only_allow_numeric_values_for(*attributes) = (attributes, :message) ||= (:not_a_number) klass = model_class attributes.each do |attribute| attribute = attribute.to_sym should "only allow numeric values for #{attribute}" do assert_bad_value(klass, attribute, "abcd", ) end end end |
#should_protect_attributes(*attributes) ⇒ Object
Ensures that the attribute cannot be set on mass update.
should_protect_attributes :password, :admin_flag
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/shoulda/active_record/macros.rb', line 131 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.empty? && !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.
If an instance variable has been created in the setup named after the model being tested, then this method will use that. Otherwise, it will create a new instance to test against.
Options:
-
:message- value the test expects to find inerrors.on(:attribute). Regexp or string. Default =I18n.translate('activerecord.errors.messages.accepted')
Example:
should_require_acceptance_of :eula
644 645 646 647 648 649 650 651 652 653 654 |
# File 'lib/shoulda/active_record/macros.rb', line 644 def should_require_acceptance_of(*attributes) = (attributes, :message) ||= (:accepted) klass = model_class attributes.each do |attribute| should "require #{attribute} to be accepted" do assert_bad_value(klass, attribute, false, ) end end end |
#should_require_attributes(*attributes) ⇒ Object
Ensures that the model cannot be saved if one of the attributes listed is not present.
If an instance variable has been created in the setup named after the model being tested, then this method will use that. Otherwise, it will create a new instance to test against.
Options:
-
:message- value the test expects to find inerrors.on(:attribute). Regexp or string. Default =I18n.translate('activerecord.errors.messages.blank')
Example:
should_require_attributes :name, :phone_number
64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/shoulda/active_record/macros.rb', line 64 def should_require_attributes(*attributes) = (attributes, :message) ||= (:blank) klass = model_class attributes.each do |attribute| should "require #{attribute} to be set" do assert_bad_value(klass, attribute, nil, ) 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 =I18n.translate('activerecord.errors.messages.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]
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/shoulda/active_record/macros.rb', line 90 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 existing_value = 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_bad_value(object, attribute, existing_value, ) # 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) assert_good_value(object, attribute, existing_value, ) end end end end end |