foreign_key_test.rb 15.5 KB
Newer Older
1 2
# frozen_string_literal: true

3 4
require "cases/helper"
require "support/schema_dumping_helper"
5

R
Ryuta Kamizono 已提交
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
if ActiveRecord::Base.connection.supports_foreign_keys_in_create?
  module ActiveRecord
    class Migration
      class ForeignKeyInCreateTest < ActiveRecord::TestCase
        def test_foreign_keys
          foreign_keys = ActiveRecord::Base.connection.foreign_keys("fk_test_has_fk")
          assert_equal 1, foreign_keys.size

          fk = foreign_keys.first
          assert_equal "fk_test_has_fk", fk.from_table
          assert_equal "fk_test_has_pk", fk.to_table
          assert_equal "fk_id", fk.column
          assert_equal "pk_id", fk.primary_key
          assert_equal "fk_name", fk.name unless current_adapter?(:SQLite3Adapter)
        end
      end
    end
  end
end

26
if ActiveRecord::Base.connection.supports_foreign_keys?
27 28 29 30 31 32 33
  module ActiveRecord
    class Migration
      class ForeignKeyTest < ActiveRecord::TestCase
        include SchemaDumpingHelper
        include ActiveSupport::Testing::Stream

        class Rocket < ActiveRecord::Base
34 35
        end

36
        class Astronaut < ActiveRecord::Base
37 38
        end

39 40 41 42 43
        setup do
          @connection = ActiveRecord::Base.connection
          @connection.create_table "rockets", force: true do |t|
            t.string :name
          end
44

45 46 47 48 49
          @connection.create_table "astronauts", force: true do |t|
            t.string :name
            t.references :rocket
          end
        end
50

51
        teardown do
R
Ryuta Kamizono 已提交
52 53
          @connection.drop_table "astronauts", if_exists: true
          @connection.drop_table "rockets", if_exists: true
54
        end
55

56 57 58
        def test_foreign_keys
          foreign_keys = @connection.foreign_keys("fk_test_has_fk")
          assert_equal 1, foreign_keys.size
59

60 61 62 63 64 65 66
          fk = foreign_keys.first
          assert_equal "fk_test_has_fk", fk.from_table
          assert_equal "fk_test_has_pk", fk.to_table
          assert_equal "fk_id", fk.column
          assert_equal "pk_id", fk.primary_key
          assert_equal "fk_name", fk.name
        end
67

68 69
        def test_add_foreign_key_inferes_column
          @connection.add_foreign_key :astronauts, :rockets
70

71 72
          foreign_keys = @connection.foreign_keys("astronauts")
          assert_equal 1, foreign_keys.size
73

74 75 76 77 78 79 80
          fk = foreign_keys.first
          assert_equal "astronauts", fk.from_table
          assert_equal "rockets", fk.to_table
          assert_equal "rocket_id", fk.column
          assert_equal "id", fk.primary_key
          assert_equal("fk_rails_78146ddd2e", fk.name)
        end
81

82 83
        def test_add_foreign_key_with_column
          @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id"
84 85 86 87 88 89

          foreign_keys = @connection.foreign_keys("astronauts")
          assert_equal 1, foreign_keys.size

          fk = foreign_keys.first
          assert_equal "astronauts", fk.from_table
90 91 92 93
          assert_equal "rockets", fk.to_table
          assert_equal "rocket_id", fk.column
          assert_equal "id", fk.primary_key
          assert_equal("fk_rails_78146ddd2e", fk.name)
94 95
        end

96
        def test_add_foreign_key_with_non_standard_primary_key
97 98 99
          @connection.create_table :space_shuttles, id: false, force: true do |t|
            t.bigint :pk, primary_key: true
          end
100

101 102
          @connection.add_foreign_key(:astronauts, :space_shuttles,
            column: "rocket_id", primary_key: "pk", name: "custom_pk")
103

104 105
          foreign_keys = @connection.foreign_keys("astronauts")
          assert_equal 1, foreign_keys.size
106

107 108 109 110 111 112 113
          fk = foreign_keys.first
          assert_equal "astronauts", fk.from_table
          assert_equal "space_shuttles", fk.to_table
          assert_equal "pk", fk.primary_key
        ensure
          @connection.remove_foreign_key :astronauts, name: "custom_pk"
          @connection.drop_table :space_shuttles
114 115
        end

116 117
        def test_add_on_delete_restrict_foreign_key
          @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_delete: :restrict
118

119 120
          foreign_keys = @connection.foreign_keys("astronauts")
          assert_equal 1, foreign_keys.size
121

122 123 124
          fk = foreign_keys.first
          if current_adapter?(:Mysql2Adapter)
            # ON DELETE RESTRICT is the default on MySQL
125
            assert_nil fk.on_delete
126 127 128 129
          else
            assert_equal :restrict, fk.on_delete
          end
        end
130

131 132
        def test_add_on_delete_cascade_foreign_key
          @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_delete: :cascade
133

134 135
          foreign_keys = @connection.foreign_keys("astronauts")
          assert_equal 1, foreign_keys.size
136

137 138 139 140 141 142 143 144 145
          fk = foreign_keys.first
          assert_equal :cascade, fk.on_delete
        end

        def test_add_on_delete_nullify_foreign_key
          @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_delete: :nullify

          foreign_keys = @connection.foreign_keys("astronauts")
          assert_equal 1, foreign_keys.size
146

147 148
          fk = foreign_keys.first
          assert_equal :nullify, fk.on_delete
149 150
        end

151 152 153 154 155 156 157 158
        def test_on_update_and_on_delete_raises_with_invalid_values
          assert_raises ArgumentError do
            @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_delete: :invalid
          end

          assert_raises ArgumentError do
            @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_update: :invalid
          end
159 160
        end

161 162 163 164 165
        def test_add_foreign_key_with_on_update
          @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_update: :nullify

          foreign_keys = @connection.foreign_keys("astronauts")
          assert_equal 1, foreign_keys.size
Y
Yves Senn 已提交
166

167 168 169
          fk = foreign_keys.first
          assert_equal :nullify, fk.on_update
        end
Y
Yves Senn 已提交
170

171 172
        def test_foreign_key_exists
          @connection.add_foreign_key :astronauts, :rockets
Y
Yves Senn 已提交
173

174 175 176
          assert @connection.foreign_key_exists?(:astronauts, :rockets)
          assert_not @connection.foreign_key_exists?(:astronauts, :stars)
        end
A
Anton 已提交
177

178 179
        def test_foreign_key_exists_by_column
          @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id"
A
Anton 已提交
180

181 182 183
          assert @connection.foreign_key_exists?(:astronauts, column: "rocket_id")
          assert_not @connection.foreign_key_exists?(:astronauts, column: "star_id")
        end
184

185 186
        def test_foreign_key_exists_by_name
          @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", name: "fancy_named_fk"
187

188 189 190
          assert @connection.foreign_key_exists?(:astronauts, name: "fancy_named_fk")
          assert_not @connection.foreign_key_exists?(:astronauts, name: "other_fancy_named_fk")
        end
191

192 193
        def test_remove_foreign_key_inferes_column
          @connection.add_foreign_key :astronauts, :rockets
Y
Yves Senn 已提交
194

195 196 197 198
          assert_equal 1, @connection.foreign_keys("astronauts").size
          @connection.remove_foreign_key :astronauts, :rockets
          assert_equal [], @connection.foreign_keys("astronauts")
        end
199

200 201
        def test_remove_foreign_key_by_column
          @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id"
202

203 204 205 206
          assert_equal 1, @connection.foreign_keys("astronauts").size
          @connection.remove_foreign_key :astronauts, column: "rocket_id"
          assert_equal [], @connection.foreign_keys("astronauts")
        end
207

208 209
        def test_remove_foreign_key_by_symbol_column
          @connection.add_foreign_key :astronauts, :rockets, column: :rocket_id
210

211 212 213 214
          assert_equal 1, @connection.foreign_keys("astronauts").size
          @connection.remove_foreign_key :astronauts, column: :rocket_id
          assert_equal [], @connection.foreign_keys("astronauts")
        end
D
dtaniwaki 已提交
215

216 217
        def test_remove_foreign_key_by_name
          @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", name: "fancy_named_fk"
D
dtaniwaki 已提交
218

219 220 221 222
          assert_equal 1, @connection.foreign_keys("astronauts").size
          @connection.remove_foreign_key :astronauts, name: "fancy_named_fk"
          assert_equal [], @connection.foreign_keys("astronauts")
        end
223

224 225 226 227 228
        def test_remove_foreign_non_existing_foreign_key_raises
          assert_raises ArgumentError do
            @connection.remove_foreign_key :astronauts, :rockets
          end
        end
Y
Yves Senn 已提交
229

230 231 232 233 234 235 236 237
        if ActiveRecord::Base.connection.supports_validate_constraints?
          def test_add_invalid_foreign_key
            @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", validate: false

            foreign_keys = @connection.foreign_keys("astronauts")
            assert_equal 1, foreign_keys.size

            fk = foreign_keys.first
238
            assert_not_predicate fk, :validated?
239 240 241 242
          end

          def test_validate_foreign_key_infers_column
            @connection.add_foreign_key :astronauts, :rockets, validate: false
243
            assert_not_predicate @connection.foreign_keys("astronauts").first, :validated?
244 245

            @connection.validate_foreign_key :astronauts, :rockets
246
            assert_predicate @connection.foreign_keys("astronauts").first, :validated?
247 248 249 250
          end

          def test_validate_foreign_key_by_column
            @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", validate: false
251
            assert_not_predicate @connection.foreign_keys("astronauts").first, :validated?
252 253

            @connection.validate_foreign_key :astronauts, column: "rocket_id"
254
            assert_predicate @connection.foreign_keys("astronauts").first, :validated?
255 256 257 258
          end

          def test_validate_foreign_key_by_symbol_column
            @connection.add_foreign_key :astronauts, :rockets, column: :rocket_id, validate: false
259
            assert_not_predicate @connection.foreign_keys("astronauts").first, :validated?
260 261

            @connection.validate_foreign_key :astronauts, column: :rocket_id
262
            assert_predicate @connection.foreign_keys("astronauts").first, :validated?
263 264 265 266
          end

          def test_validate_foreign_key_by_name
            @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", name: "fancy_named_fk", validate: false
267
            assert_not_predicate @connection.foreign_keys("astronauts").first, :validated?
268 269

            @connection.validate_foreign_key :astronauts, name: "fancy_named_fk"
270
            assert_predicate @connection.foreign_keys("astronauts").first, :validated?
271 272 273 274 275 276 277 278 279 280 281 282
          end

          def test_validate_foreign_non_existing_foreign_key_raises
            assert_raises ArgumentError do
              @connection.validate_foreign_key :astronauts, :rockets
            end
          end

          def test_validate_constraint_by_name
            @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", name: "fancy_named_fk", validate: false

            @connection.validate_constraint :astronauts, "fancy_named_fk"
283
            assert_predicate @connection.foreign_keys("astronauts").first, :validated?
284 285 286 287 288 289 290 291 292 293
          end
        else
          # Foreign key should still be created, but should not be invalid
          def test_add_invalid_foreign_key
            @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", validate: false

            foreign_keys = @connection.foreign_keys("astronauts")
            assert_equal 1, foreign_keys.size

            fk = foreign_keys.first
294
            assert_predicate fk, :validated?
295 296 297
          end
        end

298 299 300 301
        def test_schema_dumping
          @connection.add_foreign_key :astronauts, :rockets
          output = dump_table_schema "astronauts"
          assert_match %r{\s+add_foreign_key "astronauts", "rockets"$}, output
Y
Yves Senn 已提交
302 303
        end

304 305 306 307
        def test_schema_dumping_with_options
          output = dump_table_schema "fk_test_has_fk"
          assert_match %r{\s+add_foreign_key "fk_test_has_fk", "fk_test_has_pk", column: "fk_id", primary_key: "pk_id", name: "fk_name"$}, output
        end
Y
Yves Senn 已提交
308

309 310 311 312 313 314 315 316
        def test_schema_dumping_with_custom_fk_ignore_pattern
          ActiveRecord::Base.fk_ignore_pattern = /^ignored_/
          @connection.add_foreign_key :astronauts, :rockets, name: :ignored_fk_astronauts_rockets

          output = dump_table_schema "astronauts"
          assert_match %r{\s+add_foreign_key "astronauts", "rockets"$}, output
        end

317 318
        def test_schema_dumping_on_delete_and_on_update_options
          @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", on_delete: :nullify, on_update: :cascade
319

320 321 322
          output = dump_table_schema "astronauts"
          assert_match %r{\s+add_foreign_key "astronauts",.+on_update: :cascade,.+on_delete: :nullify$}, output
        end
323

324 325 326
        class CreateCitiesAndHousesMigration < ActiveRecord::Migration::Current
          def change
            create_table("cities") { |t| }
327

328
            create_table("houses") do |t|
329
              t.references :city
330 331
            end
            add_foreign_key :houses, :cities, column: "city_id"
332

333 334 335
            # remove and re-add to test that schema is updated and not accidentally cached
            remove_foreign_key :houses, :cities
            add_foreign_key :houses, :cities, column: "city_id", on_delete: :cascade
336 337 338
          end
        end

339 340 341 342 343 344 345
        def test_add_foreign_key_is_reversible
          migration = CreateCitiesAndHousesMigration.new
          silence_stream($stdout) { migration.migrate(:up) }
          assert_equal 1, @connection.foreign_keys("houses").size
        ensure
          silence_stream($stdout) { migration.migrate(:down) }
        end
346

347 348 349 350 351 352 353 354
        def test_foreign_key_constraint_is_not_cached_incorrectly
          migration = CreateCitiesAndHousesMigration.new
          silence_stream($stdout) { migration.migrate(:up) }
          output = dump_table_schema "houses"
          assert_match %r{\s+add_foreign_key "houses",.+on_delete: :cascade$}, output
        ensure
          silence_stream($stdout) { migration.migrate(:down) }
        end
355

356 357 358
        class CreateSchoolsAndClassesMigration < ActiveRecord::Migration::Current
          def change
            create_table(:schools)
359

360
            create_table(:classes) do |t|
361
              t.references :school
362 363
            end
            add_foreign_key :classes, :schools
364 365 366
          end
        end

367 368 369 370 371 372 373 374 375
        def test_add_foreign_key_with_prefix
          ActiveRecord::Base.table_name_prefix = "p_"
          migration = CreateSchoolsAndClassesMigration.new
          silence_stream($stdout) { migration.migrate(:up) }
          assert_equal 1, @connection.foreign_keys("p_classes").size
        ensure
          silence_stream($stdout) { migration.migrate(:down) }
          ActiveRecord::Base.table_name_prefix = nil
        end
376

377 378 379 380 381 382 383 384 385 386
        def test_add_foreign_key_with_suffix
          ActiveRecord::Base.table_name_suffix = "_s"
          migration = CreateSchoolsAndClassesMigration.new
          silence_stream($stdout) { migration.migrate(:up) }
          assert_equal 1, @connection.foreign_keys("classes_s").size
        ensure
          silence_stream($stdout) { migration.migrate(:down) }
          ActiveRecord::Base.table_name_suffix = nil
        end
      end
387 388
    end
  end
389
else
390 391 392 393 394 395
  module ActiveRecord
    class Migration
      class NoForeignKeySupportTest < ActiveRecord::TestCase
        setup do
          @connection = ActiveRecord::Base.connection
        end
396

397 398 399
        def test_add_foreign_key_should_be_noop
          @connection.add_foreign_key :clubs, :categories
        end
400

401 402 403
        def test_remove_foreign_key_should_be_noop
          @connection.remove_foreign_key :clubs, :categories
        end
404

R
Ryuta Kamizono 已提交
405 406 407 408 409
        unless current_adapter?(:SQLite3Adapter)
          def test_foreign_keys_should_raise_not_implemented
            assert_raises NotImplementedError do
              @connection.foreign_keys("clubs")
            end
410
          end
411 412 413 414 415
        end
      end
    end
  end
end