Skip to content

Commit

Permalink
Ensure uniquness validation matcher works with STI
Browse files Browse the repository at this point in the history
The uniqueness validation matcher generates a fake class when the
scope name ends on _type. This fake class is a duplicate of the
original class. In most cases this is not a problem, however when
using single table inheritance (STI), this new fake class is not
a subclass of the original class and therefore ActiveRecord will
throw an error. See thoughtbot#1245

A similar thing happens when the original class is using an AASM
state machine. The state machine settings are stored in a global
registry. So when the fake class is initialized, AASM cannot find
the settings of the set state machines (due to duplication) in the
registry. See thoughtbot#854

These problems are fixed by making the fake class inherrit from the
original class. Therefore it closes thoughtbot#1245 and closes thoughtbot#854
  • Loading branch information
StefSchenkelaars committed Jul 2, 2021
1 parent d448912 commit fcccac7
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 269 deletions.
267 changes: 0 additions & 267 deletions gemfiles/rails_5_1.gemfile.lock

This file was deleted.

1 change: 1 addition & 0 deletions gemfiles/rails_5_2.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ gem "listen", "~> 3.0.5"
gem "spring-watcher-listen", "~> 2.0.0"
gem "pg", "~> 0.18"
gem "sqlite3", "~> 1.3.6"
gem "aasm"
1 change: 1 addition & 0 deletions gemfiles/rails_6_0.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ gem "webdrivers"
gem "actiontext", "~> 6.0.4"
gem "pg", ">= 0.18", "< 2.0"
gem "sqlite3", "~> 1.4"
gem "aasm"
3 changes: 3 additions & 0 deletions gemfiles/rails_6_0.gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
GEM
remote: https://rubygems.org/
specs:
aasm (5.2.0)
concurrent-ruby (~> 1.0)
actioncable (6.0.4)
actionpack (= 6.0.4)
nio4r (~> 2.0)
Expand Down Expand Up @@ -260,6 +262,7 @@ PLATFORMS
ruby

DEPENDENCIES
aasm
actiontext (~> 6.0.4)
appraisal (= 2.2.0)
bcrypt (~> 3.1.7)
Expand Down
2 changes: 1 addition & 1 deletion lib/shoulda/matchers/active_record/uniqueness/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def next
end

def symlink_to(parent)
namespace.set(name, parent.dup)
namespace.set(name, Class.new(parent))
end

def to_s
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def class_name
end

def table_name
class_name.tableize.gsub('/', '_')
options.fetch(:table_name, class_name.tableize.gsub('/', '_'))
end

def parent_class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1292,6 +1292,66 @@ def configure_validation_matcher(matcher)
scoped_to(:favoriteable_type)
end
end

context 'if the model the *_type column refers to is using AASM state machines' do
it 'still works' do
require 'aasm'
user_columns = {
aasm_state: { type: :string },
}
user_model = define_model 'User', user_columns do
include AASM
aasm do
state :foo, :bar
end
end
favorite_columns = {
favoriteable_id: { type: :integer, options: { null: false } },
favoriteable_type: { type: :string, options: { null: false } },
}
favorite_model = define_model 'Favorite', favorite_columns do
belongs_to :favoriteable, polymorphic: true
validates :favoriteable, presence: true
validates :favoriteable_id, uniqueness: { scope: :favoriteable_type }
end

user = user_model.create!
favorite_model.create!(favoriteable: user)
new_favorite = favorite_model.new

expect(new_favorite).
to validate_uniqueness_of(:favoriteable_id).
scoped_to(:favoriteable_type)
end
end

context 'if the model the *_type column refers to an STI class' do
it 'still works' do
animal_columns = {
type: { type: :string, options: { null: false } },
}
animal_model = define_model 'Animal', animal_columns do
has_many :classifications, as: :classifiable
end
bird_model = define_model 'Bird', animal_columns, parent_class: animal_model, table_name: 'animals'

classification_columns = {
classifiable_id: { type: :integer },
classifiable_type: { type: :string },
}
classification_model = define_model 'Classification', classification_columns do
belongs_to :classifiable, polymorphic: true
validates :classifiable_id, uniqueness: { scope: :classifiable_type }
end

bird = bird_model.create!
classification = classification_model.new(classifiable: bird)

expect(classification).
to validate_uniqueness_of(:classifiable_id).
scoped_to(:classifiable_type)
end
end
end

context 'when the model does not have the attribute being tested' do
Expand Down

0 comments on commit fcccac7

Please sign in to comment.