Skip to content

Commit 410d098

Browse files
authored
Merge pull request rails#47990 from ghiculescu/verify-fk-reason
Show reason for foreign key error when loading fixtures
2 parents 9f8f558 + 1f32944 commit 410d098

File tree

6 files changed

+47
-19
lines changed

6 files changed

+47
-19
lines changed

activerecord/lib/active_record/connection_adapters/abstract_adapter.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,17 @@ def disable_referential_integrity
636636

637637
# Override to check all foreign key constraints in a database.
638638
def all_foreign_keys_valid?
639+
check_all_foreign_keys_valid!
639640
true
641+
rescue ActiveRecord::StatementInvalid
642+
false
643+
end
644+
deprecate :all_foreign_keys_valid?, deprecator: ActiveRecord.deprecator
645+
646+
# Override to check all foreign key constraints in a database.
647+
# The adapter should raise a +ActiveRecord::StatementInvalid+ if foreign key
648+
# constraints are not met.
649+
def check_all_foreign_keys_valid!
640650
end
641651

642652
# CONNECTION MANAGEMENT ====================================

activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def disable_referential_integrity # :nodoc:
3838
end
3939
end
4040

41-
def all_foreign_keys_valid? # :nodoc:
41+
def check_all_foreign_keys_valid! # :nodoc:
4242
sql = <<~SQL
4343
do $$
4444
declare r record;
@@ -61,14 +61,8 @@ def all_foreign_keys_valid? # :nodoc:
6161
$$;
6262
SQL
6363

64-
begin
65-
transaction(requires_new: true) do
66-
execute(sql)
67-
end
68-
69-
true
70-
rescue ActiveRecord::StatementInvalid
71-
false
64+
transaction(requires_new: true) do
65+
execute(sql)
7266
end
7367
end
7468
end

activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,8 +241,14 @@ def disable_referential_integrity # :nodoc:
241241
end
242242
end
243243

244-
def all_foreign_keys_valid? # :nodoc:
245-
execute("PRAGMA foreign_key_check").blank?
244+
def check_all_foreign_keys_valid! # :nodoc:
245+
sql = "PRAGMA foreign_key_check"
246+
result = execute(sql)
247+
248+
unless result.blank?
249+
tables = result.map { |row| row["table"] }
250+
raise ActiveRecord::StatementInvalid.new("Foreign key violations found: #{tables.join(", ")}", sql: sql)
251+
end
246252
end
247253

248254
# SCHEMA STATEMENTS ========================================

activerecord/lib/active_record/fixtures.rb

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -646,9 +646,7 @@ def insert(fixture_sets, connection) # :nodoc:
646646

647647
conn.insert_fixtures_set(table_rows_for_connection, table_rows_for_connection.keys)
648648

649-
if ActiveRecord.verify_foreign_keys_for_fixtures && !conn.all_foreign_keys_valid?
650-
raise "Foreign key violations found in your fixture data. Ensure you aren't referring to labels that don't exist on associations."
651-
end
649+
check_all_foreign_keys_valid!(conn)
652650

653651
# Cap primary key sequences to max(pk).
654652
if conn.respond_to?(:reset_pk_sequence!)
@@ -657,6 +655,16 @@ def insert(fixture_sets, connection) # :nodoc:
657655
end
658656
end
659657

658+
def check_all_foreign_keys_valid!(conn)
659+
return unless ActiveRecord.verify_foreign_keys_for_fixtures
660+
661+
begin
662+
conn.check_all_foreign_keys_valid!
663+
rescue ActiveRecord::StatementInvalid => e
664+
raise "Foreign key violations found in your fixture data. Ensure you aren't referring to labels that don't exist on associations. Error from database:\n\n#{e.message}"
665+
end
666+
end
667+
660668
def update_all_loaded_fixtures(fixtures_map) # :nodoc:
661669
all_loaded_fixtures.update(fixtures_map)
662670
end

activerecord/test/cases/adapter_test.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,12 @@ def test_truncate_tables_with_query_cache
445445
@connection.disable_query_cache!
446446
end
447447

448+
def test_all_foreign_keys_valid_is_deprecated
449+
assert_deprecated(ActiveRecord.deprecator) do
450+
@connection.all_foreign_keys_valid?
451+
end
452+
end
453+
448454
# test resetting sequences in odd tables in PostgreSQL
449455
if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!)
450456
require "models/movie"

activerecord/test/cases/fixtures_test.rb

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -850,20 +850,24 @@ def setup
850850
# those violations can cause false positives in these tests. since they aren't related to these tests we
851851
# delete the irrelevant records here (this test is transactional so it's fine).
852852
Parrot.all.each(&:destroy)
853+
854+
@path = "/fk_pointing_to_non_existent_object.yml"
853855
end
854856

855857
def test_raises_fk_violations
856858
fk_pointing_to_non_existent_object = <<~FIXTURE
857859
first:
858860
fk_object_to_point_to: one
859861
FIXTURE
860-
File.write(FIXTURES_ROOT + "/fk_pointing_to_non_existent_object.yml", fk_pointing_to_non_existent_object)
862+
File.write(FIXTURES_ROOT + @path, fk_pointing_to_non_existent_object)
861863

862864
with_verify_foreign_keys_for_fixtures do
863865
if current_adapter?(:SQLite3Adapter, :PostgreSQLAdapter)
864-
assert_raise RuntimeError do
866+
error = assert_raise RuntimeError do
865867
ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT, ["fk_pointing_to_non_existent_object"])
866868
end
869+
assert_includes error.message, "Foreign key violations found in your fixture data. Ensure you aren't referring to labels that don't exist on associations."
870+
assert_includes error.message, "fk_pointing_to_non_existent_objects"
867871
else
868872
assert_nothing_raised do
869873
ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT, ["fk_pointing_to_non_existent_object"])
@@ -872,7 +876,7 @@ def test_raises_fk_violations
872876
end
873877

874878
ensure
875-
File.delete(FIXTURES_ROOT + "/fk_pointing_to_non_existent_object.yml")
879+
File.delete(FIXTURES_ROOT + @path)
876880
ActiveRecord::FixtureSet.reset_cache
877881
end
878882

@@ -881,7 +885,7 @@ def test_does_not_raise_if_no_fk_violations
881885
first:
882886
fk_object_to_point_to_id: 1
883887
FIXTURE
884-
File.write(FIXTURES_ROOT + "/fk_pointing_to_non_existent_object.yml", fk_pointing_to_valid_object)
888+
File.write(FIXTURES_ROOT + @path, fk_pointing_to_valid_object)
885889

886890
with_verify_foreign_keys_for_fixtures do
887891
assert_nothing_raised do
@@ -890,7 +894,7 @@ def test_does_not_raise_if_no_fk_violations
890894
end
891895

892896
ensure
893-
File.delete(FIXTURES_ROOT + "/fk_pointing_to_non_existent_object.yml")
897+
File.delete(FIXTURES_ROOT + @path)
894898
ActiveRecord::FixtureSet.reset_cache
895899
end
896900

0 commit comments

Comments
 (0)