Skip to content

Commit

Permalink
fk: use random digest names
Browse files Browse the repository at this point in the history
The name of the foreign key is not relevant from a users perspective.
Using random names resolves the urge to rename the foreign key when the
respective table or column is renamed.
  • Loading branch information
senny committed Jun 26, 2014
1 parent 8550ba3 commit 8768305
Show file tree
Hide file tree
Showing 8 changed files with 59 additions and 33 deletions.
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def visit_AddForeignKey(o)
end end


def visit_DropForeignKey(name) def visit_DropForeignKey(name)
"DROP CONSTRAINT #{name}" "DROP CONSTRAINT #{quote_column_name(name)}"
end end


private private
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def column
end end


def primary_key def primary_key
options[:primary_key] options[:primary_key] || default_primary_key
end end


def on_delete def on_delete
Expand All @@ -45,6 +45,15 @@ def on_delete
def on_update def on_update
options[:on_update] options[:on_update]
end end

def custom_primary_key?
options[:primary_key] != default_primary_key
end

private
def default_primary_key
"id"
end
end end


# Represents the schema of an SQL table in an abstract way. This class # Represents the schema of an SQL table in an abstract way. This class
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -650,11 +650,10 @@ def add_foreign_key(from_table, to_table, options = {})
return unless supports_foreign_keys? return unless supports_foreign_keys?


options[:column] ||= foreign_key_column_for(to_table) options[:column] ||= foreign_key_column_for(to_table)
primary_key = options.fetch(:primary_key, "id")


options = { options = {
column: options[:column], column: options[:column],
primary_key: primary_key, primary_key: options[:primary_key],
name: foreign_key_name(from_table, options), name: foreign_key_name(from_table, options),
on_delete: options[:on_delete], on_delete: options[:on_delete],
on_update: options[:on_update] on_update: options[:on_update]
Expand All @@ -674,8 +673,17 @@ def remove_foreign_key(from_table, options_or_to_table = {})
options = { column: foreign_key_column_for(options_or_to_table) } options = { column: foreign_key_column_for(options_or_to_table) }
end end


fk_name_to_delete = options.fetch(:name) do
fk_to_delete = foreign_keys(from_table).detect {|fk| fk.column == options[:column] }
if fk_to_delete
fk_to_delete.name
else
raise ArgumentError, "Table '#{from_table}' has no foreign key on column '#{options[:column]}'"
end
end

at = create_alter_table from_table at = create_alter_table from_table
at.drop_foreign_key foreign_key_name(from_table, options) at.drop_foreign_key fk_name_to_delete


execute schema_creation.accept at execute schema_creation.accept at
end end
Expand All @@ -686,11 +694,7 @@ def foreign_key_column_for(table_name) # :nodoc:


def foreign_key_name(table_name, options) # :nodoc: def foreign_key_name(table_name, options) # :nodoc:
options.fetch(:name) do options.fetch(:name) do
identifier = "#{table_name}_#{options.fetch(:column)}_fk" "fk_rails_#{SecureRandom.hex(5)}"
if identifier.length > allowed_index_name_length
raise ArgumentError, "Foreign key name '#{identifier}' is too long; the limit is #{allowed_index_name_length} characters"
end
identifier
end end
end end


Expand Down
16 changes: 13 additions & 3 deletions activerecord/lib/active_record/schema_dumper.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -226,10 +226,20 @@ def foreign_keys(table, stream)
parts = [ parts = [
'add_foreign_key ' + remove_prefix_and_suffix(foreign_key.from_table).inspect, 'add_foreign_key ' + remove_prefix_and_suffix(foreign_key.from_table).inspect,
remove_prefix_and_suffix(foreign_key.to_table).inspect, remove_prefix_and_suffix(foreign_key.to_table).inspect,
'column: ' + foreign_key.column.inspect,
'primary_key: ' + foreign_key.primary_key.inspect,
'name: ' + foreign_key.name.inspect
] ]

if foreign_key.column != @connection.foreign_key_column_for(foreign_key.to_table)
parts << ('column: ' + foreign_key.column.inspect)
end

if foreign_key.custom_primary_key?
parts << ('primary_key: ' + foreign_key.primary_key.inspect)
end

if foreign_key.name !~ /^fk_rails_[0-9a-f]{10}$/
parts << ('name: ' + foreign_key.name.inspect)
end

parts << ('on_update: ' + foreign_key.on_update.inspect) if foreign_key.on_update parts << ('on_update: ' + foreign_key.on_update.inspect) if foreign_key.on_update
parts << ('on_delete: ' + foreign_key.on_delete.inspect) if foreign_key.on_delete parts << ('on_delete: ' + foreign_key.on_delete.inspect) if foreign_key.on_delete


Expand Down
31 changes: 17 additions & 14 deletions activerecord/test/cases/migration/foreign_key_test.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def test_foreign_keys
assert_equal "fk_test_has_fk", fk.from_table assert_equal "fk_test_has_fk", fk.from_table
assert_equal "fk_test_has_pk", fk.to_table assert_equal "fk_test_has_pk", fk.to_table
assert_equal "fk_id", fk.column assert_equal "fk_id", fk.column
assert_equal "id", fk.primary_key assert_equal "pk_id", fk.primary_key
assert_equal "fk_name", fk.name assert_equal "fk_name", fk.name
end end


Expand All @@ -57,7 +57,7 @@ def test_add_foreign_key_inferes_column
assert_equal "rockets", fk.to_table assert_equal "rockets", fk.to_table
assert_equal "rocket_id", fk.column assert_equal "rocket_id", fk.column
assert_equal "id", fk.primary_key assert_equal "id", fk.primary_key
assert_equal "astronauts_rocket_id_fk", fk.name assert_match(/^fk_rails_.{10}$/, fk.name)
end end


def test_add_foreign_key_with_column def test_add_foreign_key_with_column
Expand All @@ -71,7 +71,7 @@ def test_add_foreign_key_with_column
assert_equal "rockets", fk.to_table assert_equal "rockets", fk.to_table
assert_equal "rocket_id", fk.column assert_equal "rocket_id", fk.column
assert_equal "id", fk.primary_key assert_equal "id", fk.primary_key
assert_equal "astronauts_rocket_id_fk", fk.name assert_match(/^fk_rails_.{10}$/, fk.name)
end end


def test_add_foreign_key_with_non_standard_primary_key def test_add_foreign_key_with_non_standard_primary_key
Expand Down Expand Up @@ -146,15 +146,6 @@ def test_add_foreign_key_with_on_update
assert_equal :nullify, fk.on_update assert_equal :nullify, fk.on_update
end end


def test_add_foreign_key_with_too_long_identifier
with_example_table @connection, "long_table_name_will_result_in_a_long_foreign_key_name", "rocket_id integer" do
e = assert_raises(ArgumentError) do
@connection.add_foreign_key "long_table_name_will_result_in_a_long_foreign_key_name", "rockets"
end
assert_match(/^Foreign key name 'long_table_name_will_result_in_a_long_foreign_key_name_rocket_id_fk' is too long;/, e.message)
end
end

def test_remove_foreign_key_inferes_column def test_remove_foreign_key_inferes_column
@connection.add_foreign_key :astronauts, :rockets @connection.add_foreign_key :astronauts, :rockets


Expand All @@ -179,9 +170,21 @@ def test_remove_foreign_key_by_name
assert_equal [], @connection.foreign_keys("astronauts") assert_equal [], @connection.foreign_keys("astronauts")
end end


def test_remove_foreign_non_existing_foreign_key_raises
assert_raises ArgumentError do
@connection.remove_foreign_key :astronauts, :rockets
end
end

def test_schema_dumping 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
end

def test_schema_dumping_with_options
output = dump_table_schema "fk_test_has_fk" 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: "id", name: "fk_name"$}, output 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 end


def test_schema_dumping_on_delete_and_on_update_options def test_schema_dumping_on_delete_and_on_update_options
Expand All @@ -205,7 +208,7 @@ def change
def test_add_foreign_key_is_reversible def test_add_foreign_key_is_reversible
migration = CreateCitiesAndHousesMigration.new migration = CreateCitiesAndHousesMigration.new
silence_stream($stdout) { migration.migrate(:up) } silence_stream($stdout) { migration.migrate(:up) }
assert_equal ["houses_city_id_fk"], @connection.foreign_keys("houses").map(&:name) assert_equal 1, @connection.foreign_keys("houses").size
ensure ensure
silence_stream($stdout) { migration.migrate(:down) } silence_stream($stdout) { migration.migrate(:down) }
end end
Expand Down
2 changes: 1 addition & 1 deletion activerecord/test/fixtures/fk_test_has_pk.yml
Original file line number Original file line Diff line number Diff line change
@@ -1,2 +1,2 @@
first: first:
id: 1 pk_id: 1
4 changes: 2 additions & 2 deletions activerecord/test/schema/schema.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -855,10 +855,10 @@ def except(adapter_names_to_exclude)
t.integer :fk_id, null: false t.integer :fk_id, null: false
end end


create_table :fk_test_has_pk, force: true do |t| create_table :fk_test_has_pk, force: true, primary_key: "pk_id" do |t|
end end


execute "ALTER TABLE fk_test_has_fk ADD CONSTRAINT fk_name FOREIGN KEY (#{quote_column_name 'fk_id'}) REFERENCES #{quote_table_name 'fk_test_has_pk'} (#{quote_column_name 'id'})" execute "ALTER TABLE fk_test_has_fk ADD CONSTRAINT fk_name FOREIGN KEY (#{quote_column_name 'fk_id'}) REFERENCES #{quote_table_name 'fk_test_has_pk'} (#{quote_column_name 'pk_id'})"


execute "ALTER TABLE lessons_students ADD CONSTRAINT student_id_fk FOREIGN KEY (#{quote_column_name 'student_id'}) REFERENCES #{quote_table_name 'students'} (#{quote_column_name 'id'})" execute "ALTER TABLE lessons_students ADD CONSTRAINT student_id_fk FOREIGN KEY (#{quote_column_name 'student_id'}) REFERENCES #{quote_table_name 'students'} (#{quote_column_name 'id'})"
end end
Expand Down
6 changes: 3 additions & 3 deletions activerecord/test/schema/sqlite_specific_schema.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
execute "DROP TABLE fk_test_has_pk" rescue nil execute "DROP TABLE fk_test_has_pk" rescue nil
execute <<_SQL execute <<_SQL
CREATE TABLE 'fk_test_has_pk' ( CREATE TABLE 'fk_test_has_pk' (
'id' INTEGER NOT NULL PRIMARY KEY 'pk_id' INTEGER NOT NULL PRIMARY KEY
); );
_SQL _SQL


Expand All @@ -16,7 +16,7 @@
'id' INTEGER NOT NULL PRIMARY KEY, 'id' INTEGER NOT NULL PRIMARY KEY,
'fk_id' INTEGER NOT NULL, 'fk_id' INTEGER NOT NULL,
FOREIGN KEY ('fk_id') REFERENCES 'fk_test_has_pk'('id') FOREIGN KEY ('fk_id') REFERENCES 'fk_test_has_pk'('pk_id')
); );
_SQL _SQL
end end

0 comments on commit 8768305

Please sign in to comment.