From b8bfdcfd60be39da15d94cadd3312f12c137fcdf Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Tue, 20 May 2008 11:10:07 +0100 Subject: [PATCH] Support for multiple scoped_to attributes in should_require_unique_attributes. Created 3 new models (user_groups, memberships, meetings) in the tests to demonstrate this. --- lib/shoulda/active_record_helpers.rb | 50 ++++++++++++++++---- test/fixtures/meetings.yml | 5 ++ test/fixtures/memberships.yml | 4 ++ test/fixtures/user_groups.yml | 3 + test/rails_root/app/models/meeting.rb | 6 ++ test/rails_root/app/models/membership.rb | 7 +++ test/rails_root/app/models/user.rb | 3 + test/rails_root/app/models/user_group.rb | 8 +++ ...ate_user_groups_and_memberships_and_meetings.rb | 22 +++++++++ test/unit/meeting_test.rb | 13 +++++ test/unit/membership_test.rb | 14 ++++++ test/unit/user_group_test.rb | 13 +++++ test/unit/user_test.rb | 3 + 13 files changed, 142 insertions(+), 9 deletions(-) create mode 100644 test/fixtures/meetings.yml create mode 100644 test/fixtures/memberships.yml create mode 100644 test/fixtures/user_groups.yml create mode 100644 test/rails_root/app/models/meeting.rb create mode 100644 test/rails_root/app/models/membership.rb create mode 100644 test/rails_root/app/models/user_group.rb create mode 100644 test/rails_root/db/migrate/009_create_user_groups_and_memberships_and_meetings.rb create mode 100644 test/unit/meeting_test.rb create mode 100644 test/unit/membership_test.rb create mode 100644 test/unit/user_group_test.rb diff --git a/lib/shoulda/active_record_helpers.rb b/lib/shoulda/active_record_helpers.rb index 1ad8a7f..b9c4e36 100644 --- a/lib/shoulda/active_record_helpers.rb +++ b/lib/shoulda/active_record_helpers.rb @@ -51,6 +51,8 @@ module ThoughtBot # :nodoc: # Options: # * :message - value the test expects to find in errors.on(:attribute). # Regexp or string. Default = /taken/ + # * :scoped_to - shoulda analogue to :scope in + # ActiveRecord::Base#validates_uniqueness_of. Can be an array of values or just one. # # Example: # should_require_unique_attributes :keyword, :username @@ -62,14 +64,33 @@ module ThoughtBot # :nodoc: klass = model_class attributes.each do |attribute| attribute = attribute.to_sym - should "require unique value for #{attribute}#{" scoped to #{scope}" if scope}" do + test_description = %Q{require unique value for #{attribute}#{ + if scope + " scoped to #{ + if scope.is_a? Array + scope.to_sentence + else + scope.to_s + end + }" + end + }} + + should test_description do assert existing = klass.find(:first), "Can't find first #{klass}" object = klass.new object.send(:"#{attribute}=", existing.send(attribute)) if scope - assert_respond_to object, :"#{scope}=", "#{klass.name} doesn't seem to have a #{scope} attribute." - object.send(:"#{scope}=", existing.send(scope)) + if scope.is_a? Array + scope.each do |scoped_attribute| + assert_respond_to object, :"#{scoped_attribute}=", "#{klass.name} doesn't seem to have a #{scoped_attribute} attribute." + object.send(:"#{scoped_attribute}=", existing.send(scoped_attribute)) + end + else + assert_respond_to object, :"#{scope}=", "#{klass.name} doesn't seem to have a #{scope} attribute." + object.send(:"#{scope}=", existing.send(scope)) + end end assert !object.valid?, "#{klass.name} does not require a unique value for #{attribute}." @@ -83,12 +104,23 @@ module ThoughtBot # :nodoc: # could actually find all values for scope and create a unique # one. if scope - # Assume the scope is a foreign key if the field is nil - object.send(:"#{scope}=", existing.send(scope).nil? ? 1 : existing.send(scope).next) - object.errors.clear - object.valid? - assert_does_not_contain(object.errors.on(attribute), message, - "after :#{scope} set to #{object.send(scope.to_sym)}") + if scope.is_a? Array + scope.each do |scoped_attribute| + # Assume the scope is a foreign key if the field is nil + object.send(:"#{scoped_attribute}=", existing.send(scoped_attribute).nil? ? 1 : existing.send(scoped_attribute).next) + object.errors.clear + object.valid? + assert_does_not_contain(object.errors.on(attribute), message, + "after :#{scoped_attribute} set to #{object.send(scoped_attribute.to_sym)}") + end + else + # Assume the scope is a foreign key if the field is nil + object.send(:"#{scope}=", existing.send(scope).nil? ? 1 : existing.send(scope).next) + object.errors.clear + object.valid? + assert_does_not_contain(object.errors.on(attribute), message, + "after :#{scope} set to #{object.send(scope.to_sym)}") + end end end end diff --git a/test/fixtures/meetings.yml b/test/fixtures/meetings.yml new file mode 100644 index 0000000..f042149 --- /dev/null +++ b/test/fixtures/meetings.yml @@ -0,0 +1,5 @@ +first: + id: 1 + user_group_id: 1 + location: London + happens_on: 2008-01-01 diff --git a/test/fixtures/memberships.yml b/test/fixtures/memberships.yml new file mode 100644 index 0000000..cc816dc --- /dev/null +++ b/test/fixtures/memberships.yml @@ -0,0 +1,4 @@ +first: + id: 1 + user_id: 1 + user_group_id: 1 diff --git a/test/fixtures/user_groups.yml b/test/fixtures/user_groups.yml new file mode 100644 index 0000000..2614ff7 --- /dev/null +++ b/test/fixtures/user_groups.yml @@ -0,0 +1,3 @@ +first: + id: 1 + name: LRUG diff --git a/test/rails_root/app/models/meeting.rb b/test/rails_root/app/models/meeting.rb new file mode 100644 index 0000000..26cfe12 --- /dev/null +++ b/test/rails_root/app/models/meeting.rb @@ -0,0 +1,6 @@ +class Meeting < ActiveRecord::Base + belongs_to :user_group + + validates_presence_of :user_group_id, :location, :happens_on + validates_uniqueness_of :user_group_id, :scope => [:location, :happens_on] +end \ No newline at end of file diff --git a/test/rails_root/app/models/membership.rb b/test/rails_root/app/models/membership.rb new file mode 100644 index 0000000..c5bba21 --- /dev/null +++ b/test/rails_root/app/models/membership.rb @@ -0,0 +1,7 @@ +class Membership < ActiveRecord::Base + belongs_to :user + belongs_to :user_group + + validates_presence_of :user_id, :user_group_id + validates_uniqueness_of :user_id, :scope => :user_group_id +end \ No newline at end of file diff --git a/test/rails_root/app/models/user.rb b/test/rails_root/app/models/user.rb index 5044182..403faf4 100644 --- a/test/rails_root/app/models/user.rb +++ b/test/rails_root/app/models/user.rb @@ -4,6 +4,9 @@ class User < ActiveRecord::Base has_one :address, :as => :addressable + has_many :memberships + has_many :user_groups, :through => :memberships + attr_protected :password validates_format_of :email, :with => /\w*@\w*.com/ diff --git a/test/rails_root/app/models/user_group.rb b/test/rails_root/app/models/user_group.rb new file mode 100644 index 0000000..6eee92d --- /dev/null +++ b/test/rails_root/app/models/user_group.rb @@ -0,0 +1,8 @@ +class UserGroup < ActiveRecord::Base + has_many :memberships + has_many :users, :through => :memberhips + has_many :meetings + + validates_presence_of :name + validates_uniqueness_of :name +end diff --git a/test/rails_root/db/migrate/009_create_user_groups_and_memberships_and_meetings.rb b/test/rails_root/db/migrate/009_create_user_groups_and_memberships_and_meetings.rb new file mode 100644 index 0000000..715ee2c --- /dev/null +++ b/test/rails_root/db/migrate/009_create_user_groups_and_memberships_and_meetings.rb @@ -0,0 +1,22 @@ +class CreateUserGroupsAndMembershipsAndMeetings < ActiveRecord::Migration + def self.up + create_table :user_groups do |t| + t.string :name + end + create_table :memberships do |t| + t.integer :user_group_id + t.integer :user_id + end + create_table :meetings do |t| + t.integer :user_group_id + t.string :location + t.date :happens_on + end + end + + def self.down + drop_table :meetings + drop_table :memberships + drop_table :user_groups + end +end diff --git a/test/unit/meeting_test.rb b/test/unit/meeting_test.rb new file mode 100644 index 0000000..946a0ef --- /dev/null +++ b/test/unit/meeting_test.rb @@ -0,0 +1,13 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class MeetingTest < Test::Unit::TestCase + load_all_fixtures + + should_belong_to :user_group + + should_have_db_columns :user_group_id, :location, :happens_on + + should_require_attributes :user_group_id, :location, :happens_on + + should_require_unique_attributes :user_group_id, :scoped_to => [:location, :happens_on] +end diff --git a/test/unit/membership_test.rb b/test/unit/membership_test.rb new file mode 100644 index 0000000..60a280f --- /dev/null +++ b/test/unit/membership_test.rb @@ -0,0 +1,14 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class MembershipTest < Test::Unit::TestCase + load_all_fixtures + + should_belong_to :user + should_belong_to :user_group + + should_have_db_columns :user_id, :user_group_id + + should_require_attributes :user_id, :user_group_id + + should_require_unique_attributes :user_id, :scoped_to => :user_group_id +end diff --git a/test/unit/user_group_test.rb b/test/unit/user_group_test.rb new file mode 100644 index 0000000..978cb49 --- /dev/null +++ b/test/unit/user_group_test.rb @@ -0,0 +1,13 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class UserGroupTest < Test::Unit::TestCase + load_all_fixtures + + should_have_many :memberships + should_have_many :users, :through => :memberships + + should_have_db_columns :name + + should_require_attriubutes :name + should_require_unique_attributes :name +end diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb index ff1a57e..f0ae342 100644 --- a/test/unit/user_test.rb +++ b/test/unit/user_test.rb @@ -6,6 +6,9 @@ class UserTest < Test::Unit::TestCase should_have_many :posts should_have_many :dogs + should_have_many :memberships + should_have_many :user_groups, :through => :memberships + should_have_one :address should_have_indices :email, :name, [:email, :name] -- 1.5.4.5