From e2ef25710682d884b2e6f5e99d47f18eb7083c68 Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Fri, 6 Jun 2014 17:24:46 +0200 Subject: fk: `add_foreign_key` and `remove_foreign_key` for PostgreSQL adapter. --- .../connection_adapters/abstract_adapter.rb | 5 ++++ .../postgresql/schema_statements.rb | 27 ++++++++++++++++++++++ .../connection_adapters/postgresql_adapter.rb | 4 ++++ 3 files changed, 36 insertions(+) (limited to 'activerecord/lib/active_record/connection_adapters') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index cc494a7f40..294ed6d7bf 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -233,6 +233,11 @@ module ActiveRecord false end + # Does this adapter support creating foreign key constraints? + def supports_foreign_keys? + false + end + # This is meant to be implemented by the adapters that support extensions def disable_extension(name) end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index b2aeb3a058..f09ce113d6 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -448,6 +448,33 @@ module ActiveRecord execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}" end + def add_foreign_key(from_table, to_table, options = {}) + foreign_key_column = options.fetch(:column) + referenced_column = "id" + foreign_key_name = foreign_key_name(from_table, options) + execute <<-SQL +ALTER TABLE #{quote_table_name(from_table)} +ADD CONSTRAINT #{foreign_key_name} + FOREIGN KEY (#{quote_column_name(foreign_key_column)}) + REFERENCES #{quote_table_name(to_table)} (#{quote_column_name(referenced_column)}) + SQL + end + + def remove_foreign_key(from_table, options = {}) + foreign_key_name = foreign_key_name(from_table, options) + execute <<-SQL +ALTER TABLE #{quote_table_name(from_table)} +DROP CONSTRAINT #{foreign_key_name} + SQL + end + + def foreign_key_name(table_name, options) + options.fetch(:name) do + column_name = options.fetch(:column) + "#{table_name}_#{column_name}_fk" + end + end + def index_name_length 63 end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index be4ae47d09..34262cf91d 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -159,6 +159,10 @@ module ActiveRecord true end + def supports_foreign_keys? + true + end + def index_algorithms { concurrently: 'CONCURRENTLY' } end -- cgit v1.2.3 From 09b3a2847ca51d0e5dcebcb636d8770b19c397a7 Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Tue, 10 Jun 2014 10:59:18 +0200 Subject: fk: add `foreign_keys` for PostgreSQL adapter. --- .../postgresql/schema_definitions.rb | 14 +++++++++++++ .../postgresql/schema_statements.rb | 24 ++++++++++++++++++++++ 2 files changed, 38 insertions(+) (limited to 'activerecord/lib/active_record/connection_adapters') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb index 0867e5ef54..724e3fa1ee 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb @@ -89,6 +89,20 @@ module ActiveRecord attr_accessor :array end + class ForeignKeyDefinition < Struct.new(:from_table, :to_table, :options) + def name + options[:name] + end + + def column + options[:column] + end + + def primary_key + options[:primary_key] + end + end + class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition include ColumnMethods diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index f09ce113d6..b87fb85ae2 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -448,6 +448,30 @@ module ActiveRecord execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}" end + def foreign_keys(table_name) + fk_info = select_all <<-SQL +SELECT t2.relname AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confdeltype AS dependency +FROM pg_constraint c +JOIN pg_class t1 ON c.conrelid = t1.oid +JOIN pg_class t2 ON c.confrelid = t2.oid +JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid +JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid +JOIN pg_namespace t3 ON c.connamespace = t3.oid +WHERE c.contype = 'f' + AND t1.relname = #{quote(table_name)} + AND t3.nspname = ANY (current_schemas(false)) +ORDER BY c.conname + SQL + + fk_info.map do |row| + options = { + column: row['column'], + name: row['name'], + primary_key: row['primary_key'] } + ForeignKeyDefinition.new(table_name, row["to_table"], options) + end + end + def add_foreign_key(from_table, to_table, options = {}) foreign_key_column = options.fetch(:column) referenced_column = "id" -- cgit v1.2.3 From 74b2fe4c0febe051cb48c7c25a565333ddf22bce Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Tue, 10 Jun 2014 11:10:54 +0200 Subject: fk: `foreign_keys`, `add_foreign_key` and `remove_foreign_key` for MySQL --- .../abstract/schema_definitions.rb | 14 +++++++ .../abstract/schema_statements.rb | 7 ++++ .../connection_adapters/abstract_mysql_adapter.rb | 44 ++++++++++++++++++++++ .../postgresql/schema_definitions.rb | 14 ------- .../postgresql/schema_statements.rb | 7 ---- 5 files changed, 65 insertions(+), 21 deletions(-) (limited to 'activerecord/lib/active_record/connection_adapters') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index a9b3e9cfb9..b132543332 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -25,6 +25,20 @@ module ActiveRecord class ChangeColumnDefinition < Struct.new(:column, :type, :options) #:nodoc: end + class ForeignKeyDefinition < Struct.new(:from_table, :to_table, :options) + def name + options[:name] + end + + def column + options[:column] + end + + def primary_key + options[:primary_key] + end + end + # Represents the schema of an SQL table in an abstract way. This class # provides methods for manipulating the schema representation. # diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 22823a8c58..55e3fb4477 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -625,6 +625,13 @@ module ActiveRecord end alias :add_belongs_to :add_reference + def foreign_key_name(table_name, options) + options.fetch(:name) do + column_name = options.fetch(:column) + "#{table_name}_#{column_name}_fk" + end + end + # Removes the reference(s). Also removes a +type+ column if one exists. # remove_reference, remove_references and remove_belongs_to are acceptable. # diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index def04dbed2..431db591e6 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -192,6 +192,10 @@ module ActiveRecord true end + def supports_foreign_keys? + true + end + def native_database_types NATIVE_DATABASE_TYPES end @@ -501,6 +505,46 @@ module ActiveRecord execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options} #{index_algorithm}" end + def foreign_keys(table_name) + fk_info = select_all %{ + SELECT fk.referenced_table_name as 'to_table' + ,fk.referenced_column_name as 'primary_key' + ,fk.column_name as 'column' + ,fk.constraint_name as 'name' + FROM information_schema.key_column_usage fk + WHERE fk.referenced_column_name is not null + AND fk.table_schema = '#{@config[:database]}' + AND fk.table_name = '#{table_name}' + } + + create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"] + + fk_info.map do |row| + options = {column: row['column'], name: row['name'], primary_key: row['primary_key']} + ForeignKeyDefinition.new(table_name, row['to_table'], options) + end + end + + def add_foreign_key(from_table, to_table, options = {}) + foreign_key_column = options.fetch(:column) + referenced_column = "id" + foreign_key_name = foreign_key_name(from_table, options) + execute <<-SQL +ALTER TABLE #{quote_table_name(from_table)} +ADD CONSTRAINT #{foreign_key_name} +FOREIGN KEY (#{quote_column_name(foreign_key_column)}) +REFERENCES #{quote_table_name(to_table)} (#{quote_column_name(referenced_column)}) + SQL + end + + def remove_foreign_key(from_table, options = {}) + foreign_key_name = foreign_key_name(from_table, options) + execute <<-SQL +ALTER TABLE #{quote_table_name(from_table)} +DROP FOREIGN KEY #{foreign_key_name} + SQL + end + # Maps logical Rails types to MySQL-specific data types. def type_to_sql(type, limit = nil, precision = nil, scale = nil) case type.to_s diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb index 724e3fa1ee..0867e5ef54 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb @@ -89,20 +89,6 @@ module ActiveRecord attr_accessor :array end - class ForeignKeyDefinition < Struct.new(:from_table, :to_table, :options) - def name - options[:name] - end - - def column - options[:column] - end - - def primary_key - options[:primary_key] - end - end - class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition include ColumnMethods diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index b87fb85ae2..7a93d9cde7 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -492,13 +492,6 @@ DROP CONSTRAINT #{foreign_key_name} SQL end - def foreign_key_name(table_name, options) - options.fetch(:name) do - column_name = options.fetch(:column) - "#{table_name}_#{column_name}_fk" - end - end - def index_name_length 63 end -- cgit v1.2.3 From 1c170fdea2be04691c7daa8266084766fe963fff Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Tue, 10 Jun 2014 11:50:37 +0200 Subject: fk: generalize using `AlterTable` and `SchemaCreation`. --- .../abstract/schema_creation.rb | 14 ++++++++ .../abstract/schema_definitions.rb | 12 +++++++ .../abstract/schema_statements.rb | 37 ++++++++++++++++++---- .../connection_adapters/abstract_mysql_adapter.rb | 24 +++----------- .../postgresql/schema_statements.rb | 20 ------------ 5 files changed, 60 insertions(+), 47 deletions(-) (limited to 'activerecord/lib/active_record/connection_adapters') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb index 47fe501752..ad62eab4d2 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb @@ -18,11 +18,25 @@ module ActiveRecord add_column_options!(sql, column_options(o)) end + def visit_AddForeignKey(o) + <<-SQL +ADD CONSTRAINT #{quote_column_name(o.name)} +FOREIGN KEY (#{quote_column_name(o.column)}) + REFERENCES #{quote_table_name(o.to_table)} (#{quote_column_name(o.primary_key)}) + SQL + end + + def visit_DropForeignKey(name) + "DROP CONSTRAINT #{name}" + end + private def visit_AlterTable(o) sql = "ALTER TABLE #{quote_table_name(o.name)} " sql << o.adds.map { |col| visit_AddColumn col }.join(' ') + sql << o.foreign_key_adds.map { |fk| visit_AddForeignKey fk }.join(' ') + sql << o.foreign_key_drops.map { |fk| visit_DropForeignKey fk }.join(' ') end def visit_ColumnDefinition(o) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index b132543332..c18ebf1014 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -317,14 +317,26 @@ module ActiveRecord class AlterTable # :nodoc: attr_reader :adds + attr_reader :foreign_key_adds + attr_reader :foreign_key_drops def initialize(td) @td = td @adds = [] + @foreign_key_adds = [] + @foreign_key_drops = [] end def name; @td.name; end + def add_foreign_key(to_table, options) + @foreign_key_adds << ForeignKeyDefinition.new(name, to_table, options) + end + + def drop_foreign_key(name) + @foreign_key_drops << name + end + def add_column(name, type, options) name = name.to_s type = type.to_sym diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 55e3fb4477..da6b15dad0 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -625,13 +625,6 @@ module ActiveRecord end alias :add_belongs_to :add_reference - def foreign_key_name(table_name, options) - options.fetch(:name) do - column_name = options.fetch(:column) - "#{table_name}_#{column_name}_fk" - end - end - # Removes the reference(s). Also removes a +type+ column if one exists. # remove_reference, remove_references and remove_belongs_to are acceptable. # @@ -649,6 +642,36 @@ module ActiveRecord end alias :remove_belongs_to :remove_reference + def foreign_keys(table_name) + raise NotImplementedError, "foreign_keys is not implemented" + end + + def add_foreign_key(from_table, to_table, options = {}) + options = { + column: options.fetch(:column), + primary_key: "id", + name: foreign_key_name(from_table, options) + } + at = create_alter_table from_table + at.add_foreign_key to_table, options + + execute schema_creation.accept at + end + + def remove_foreign_key(from_table, options = {}) + at = create_alter_table from_table + at.drop_foreign_key foreign_key_name(from_table, options) + + execute schema_creation.accept at + end + + def foreign_key_name(table_name, options) # :nodoc: + options.fetch(:name) do + column_name = options.fetch(:column) + "#{table_name}_#{column_name}_fk" + end + end + def dump_schema_information #:nodoc: sm_table = ActiveRecord::Migrator.schema_migrations_table_name diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 431db591e6..6ba226765c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -10,6 +10,10 @@ module ActiveRecord add_column_position!(super, column_options(o)) end + def visit_DropForeignKey(name) + "DROP FOREIGN KEY #{name}" + end + private def visit_TableDefinition(o) @@ -525,26 +529,6 @@ module ActiveRecord end end - def add_foreign_key(from_table, to_table, options = {}) - foreign_key_column = options.fetch(:column) - referenced_column = "id" - foreign_key_name = foreign_key_name(from_table, options) - execute <<-SQL -ALTER TABLE #{quote_table_name(from_table)} -ADD CONSTRAINT #{foreign_key_name} -FOREIGN KEY (#{quote_column_name(foreign_key_column)}) -REFERENCES #{quote_table_name(to_table)} (#{quote_column_name(referenced_column)}) - SQL - end - - def remove_foreign_key(from_table, options = {}) - foreign_key_name = foreign_key_name(from_table, options) - execute <<-SQL -ALTER TABLE #{quote_table_name(from_table)} -DROP FOREIGN KEY #{foreign_key_name} - SQL - end - # Maps logical Rails types to MySQL-specific data types. def type_to_sql(type, limit = nil, precision = nil, scale = nil) case type.to_s diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index 7a93d9cde7..c061337e71 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -472,26 +472,6 @@ ORDER BY c.conname end end - def add_foreign_key(from_table, to_table, options = {}) - foreign_key_column = options.fetch(:column) - referenced_column = "id" - foreign_key_name = foreign_key_name(from_table, options) - execute <<-SQL -ALTER TABLE #{quote_table_name(from_table)} -ADD CONSTRAINT #{foreign_key_name} - FOREIGN KEY (#{quote_column_name(foreign_key_column)}) - REFERENCES #{quote_table_name(to_table)} (#{quote_column_name(referenced_column)}) - SQL - end - - def remove_foreign_key(from_table, options = {}) - foreign_key_name = foreign_key_name(from_table, options) - execute <<-SQL -ALTER TABLE #{quote_table_name(from_table)} -DROP CONSTRAINT #{foreign_key_name} - SQL - end - def index_name_length 63 end -- cgit v1.2.3 From a48b675d54101b048228d1011ffa426c2b7fe94d Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Tue, 10 Jun 2014 12:09:58 +0200 Subject: fk: `:primary_key` option for non-standard pk's. --- .../active_record/connection_adapters/abstract/schema_statements.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'activerecord/lib/active_record/connection_adapters') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index da6b15dad0..db04ebc802 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -647,9 +647,11 @@ module ActiveRecord end def add_foreign_key(from_table, to_table, options = {}) + primary_key = options.fetch(:primary_key, "id") + options = { column: options.fetch(:column), - primary_key: "id", + primary_key: primary_key, name: foreign_key_name(from_table, options) } at = create_alter_table from_table -- cgit v1.2.3 From 402f303f1d938cf2c7781d7734c4ff8e6b874f35 Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Tue, 10 Jun 2014 14:26:50 +0200 Subject: fk: support dependent option (:delete, :nullify and :restrict). --- .../connection_adapters/abstract/schema_creation.rb | 13 ++++++++++++- .../connection_adapters/abstract/schema_definitions.rb | 4 ++++ .../connection_adapters/abstract/schema_statements.rb | 3 ++- .../connection_adapters/abstract_mysql_adapter.rb | 14 +++++++++++++- .../connection_adapters/postgresql/schema_statements.rb | 9 ++++++++- 5 files changed, 39 insertions(+), 4 deletions(-) (limited to 'activerecord/lib/active_record/connection_adapters') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb index ad62eab4d2..57790d5667 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb @@ -19,11 +19,13 @@ module ActiveRecord end def visit_AddForeignKey(o) - <<-SQL + sql = <<-SQL ADD CONSTRAINT #{quote_column_name(o.name)} FOREIGN KEY (#{quote_column_name(o.column)}) REFERENCES #{quote_table_name(o.to_table)} (#{quote_column_name(o.primary_key)}) SQL + sql << " #{dependency_sql(o.dependent)}" if o.dependent + sql end def visit_DropForeignKey(name) @@ -98,6 +100,15 @@ FOREIGN KEY (#{quote_column_name(o.column)}) def options_include_default?(options) options.include?(:default) && !(options[:null] == false && options[:default].nil?) end + + def dependency_sql(dependency) + case dependency + when :nullify then "ON DELETE SET NULL" + when :delete then "ON DELETE CASCADE" + when :restrict then "ON DELETE RESTRICT" + else "" + end + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index c18ebf1014..66ebf82971 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -37,6 +37,10 @@ module ActiveRecord def primary_key options[:primary_key] end + + def dependent + options[:dependent] + end end # Represents the schema of an SQL table in an abstract way. This class diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index db04ebc802..fe752126ad 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -652,7 +652,8 @@ module ActiveRecord options = { column: options.fetch(:column), primary_key: primary_key, - name: foreign_key_name(from_table, options) + name: foreign_key_name(from_table, options), + dependent: options.fetch(:dependent, nil) } at = create_alter_table from_table at.add_foreign_key to_table, options diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 6ba226765c..9610296043 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -524,7 +524,19 @@ module ActiveRecord create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"] fk_info.map do |row| - options = {column: row['column'], name: row['name'], primary_key: row['primary_key']} + options = { + column: row['column'], + name: row['name'], + primary_key: row['primary_key'] + } + + if create_table_info =~ /CONSTRAINT #{quote_column_name(row['name'])} FOREIGN KEY .* REFERENCES .* ON DELETE (CASCADE|SET NULL|RESTRICT)/ + options[:dependent] = case $1 + when 'CASCADE' then :delete + when 'SET NULL' then :nullify + end + end + ForeignKeyDefinition.new(table_name, row['to_table'], options) end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index c061337e71..7b61ff81ba 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -467,7 +467,14 @@ ORDER BY c.conname options = { column: row['column'], name: row['name'], - primary_key: row['primary_key'] } + primary_key: row['primary_key'] + } + + options[:dependent] = case row['dependency'] + when 'c'; :delete + when 'n'; :nullify + when 'r'; :restrict + end ForeignKeyDefinition.new(table_name, row["to_table"], options) end end -- cgit v1.2.3 From d074b821489b6d58101d1474dd514990f4bdf0fa Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Tue, 10 Jun 2014 15:29:19 +0200 Subject: fk: infere column name from table names. This allows to create and remove foreign keys without specifying a column. --- .../connection_adapters/abstract/schema_statements.rb | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) (limited to 'activerecord/lib/active_record/connection_adapters') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index fe752126ad..0f2af2c6d2 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -647,10 +647,11 @@ module ActiveRecord end def add_foreign_key(from_table, to_table, options = {}) + options[:column] ||= foreign_key_column_for(to_table) primary_key = options.fetch(:primary_key, "id") options = { - column: options.fetch(:column), + column: options[:column], primary_key: primary_key, name: foreign_key_name(from_table, options), dependent: options.fetch(:dependent, nil) @@ -661,17 +662,26 @@ module ActiveRecord execute schema_creation.accept at end - def remove_foreign_key(from_table, options = {}) + def remove_foreign_key(from_table, options_or_to_table = {}) + if options_or_to_table.is_a?(Hash) + options = options_or_to_table + else + options = { column: foreign_key_column_for(options_or_to_table) } + end + at = create_alter_table from_table at.drop_foreign_key foreign_key_name(from_table, options) execute schema_creation.accept at end + def foreign_key_column_for(table_name) # :nodoc: + "#{table_name.to_s.singularize}_id" + end + def foreign_key_name(table_name, options) # :nodoc: options.fetch(:name) do - column_name = options.fetch(:column) - "#{table_name}_#{column_name}_fk" + "#{table_name}_#{options.fetch(:column)}_fk" end end -- cgit v1.2.3 From 6955d864ceb0ba994ef4fb4c5e866463f247944b Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Wed, 11 Jun 2014 08:23:17 +0200 Subject: fk: rename `dependent` to `on_delete` --- .../connection_adapters/abstract/schema_creation.rb | 11 +++++------ .../connection_adapters/abstract/schema_definitions.rb | 4 ++-- .../connection_adapters/abstract/schema_statements.rb | 2 +- .../connection_adapters/abstract_mysql_adapter.rb | 4 ++-- .../connection_adapters/postgresql/schema_statements.rb | 4 ++-- 5 files changed, 12 insertions(+), 13 deletions(-) (limited to 'activerecord/lib/active_record/connection_adapters') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb index 57790d5667..aad4431910 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb @@ -24,7 +24,7 @@ ADD CONSTRAINT #{quote_column_name(o.name)} FOREIGN KEY (#{quote_column_name(o.column)}) REFERENCES #{quote_table_name(o.to_table)} (#{quote_column_name(o.primary_key)}) SQL - sql << " #{dependency_sql(o.dependent)}" if o.dependent + sql << " #{action_sql(o.on_delete)}" if o.on_delete sql end @@ -101,12 +101,11 @@ FOREIGN KEY (#{quote_column_name(o.column)}) options.include?(:default) && !(options[:null] == false && options[:default].nil?) end - def dependency_sql(dependency) + def action_sql(action = "DELETE", dependency) case dependency - when :nullify then "ON DELETE SET NULL" - when :delete then "ON DELETE CASCADE" - when :restrict then "ON DELETE RESTRICT" - else "" + when :nullify then "ON #{action} SET NULL" + when :cascade then "ON #{action} CASCADE" + when :restrict then "ON #{action} RESTRICT" end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 66ebf82971..2d6cf2427c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -38,8 +38,8 @@ module ActiveRecord options[:primary_key] end - def dependent - options[:dependent] + def on_delete + options[:on_delete] end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 0f2af2c6d2..3f72e35bb5 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -654,7 +654,7 @@ module ActiveRecord column: options[:column], primary_key: primary_key, name: foreign_key_name(from_table, options), - dependent: options.fetch(:dependent, nil) + on_delete: options.fetch(:on_delete, nil) } at = create_alter_table from_table at.add_foreign_key to_table, options diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 9610296043..f3b7fe172e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -531,8 +531,8 @@ module ActiveRecord } if create_table_info =~ /CONSTRAINT #{quote_column_name(row['name'])} FOREIGN KEY .* REFERENCES .* ON DELETE (CASCADE|SET NULL|RESTRICT)/ - options[:dependent] = case $1 - when 'CASCADE' then :delete + options[:on_delete] = case $1 + when 'CASCADE' then :cascade when 'SET NULL' then :nullify end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index 7b61ff81ba..bf87395ef1 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -470,8 +470,8 @@ ORDER BY c.conname primary_key: row['primary_key'] } - options[:dependent] = case row['dependency'] - when 'c'; :delete + options[:on_delete] = case row['dependency'] + when 'c'; :cascade when 'n'; :nullify when 'r'; :restrict end -- cgit v1.2.3 From acd0287dc18a3fbba6fa4301cb31a7aecd22922b Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Wed, 11 Jun 2014 08:47:53 +0200 Subject: fk: support for on_update --- .../connection_adapters/abstract/schema_creation.rb | 5 +++-- .../abstract/schema_definitions.rb | 4 ++++ .../connection_adapters/abstract/schema_statements.rb | 3 ++- .../connection_adapters/abstract_mysql_adapter.rb | 17 +++++++++++------ .../postgresql/schema_statements.rb | 19 ++++++++++++------- 5 files changed, 32 insertions(+), 16 deletions(-) (limited to 'activerecord/lib/active_record/connection_adapters') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb index aad4431910..b896bd25e4 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb @@ -24,7 +24,8 @@ ADD CONSTRAINT #{quote_column_name(o.name)} FOREIGN KEY (#{quote_column_name(o.column)}) REFERENCES #{quote_table_name(o.to_table)} (#{quote_column_name(o.primary_key)}) SQL - sql << " #{action_sql(o.on_delete)}" if o.on_delete + sql << " #{action_sql('DELETE', o.on_delete)}" if o.on_delete + sql << " #{action_sql('UPDATE', o.on_update)}" if o.on_update sql end @@ -101,7 +102,7 @@ FOREIGN KEY (#{quote_column_name(o.column)}) options.include?(:default) && !(options[:null] == false && options[:default].nil?) end - def action_sql(action = "DELETE", dependency) + def action_sql(action, dependency) case dependency when :nullify then "ON #{action} SET NULL" when :cascade then "ON #{action} CASCADE" diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 2d6cf2427c..2e47e68754 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -41,6 +41,10 @@ module ActiveRecord def on_delete options[:on_delete] end + + def on_update + options[:on_update] + end end # Represents the schema of an SQL table in an abstract way. This class diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 3f72e35bb5..18e73b0200 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -654,7 +654,8 @@ module ActiveRecord column: options[:column], primary_key: primary_key, name: foreign_key_name(from_table, options), - on_delete: options.fetch(:on_delete, nil) + on_delete: options[:on_delete], + on_update: options[:on_update] } at = create_alter_table from_table at.add_foreign_key to_table, options diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index f3b7fe172e..868181e677 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -530,17 +530,22 @@ module ActiveRecord primary_key: row['primary_key'] } - if create_table_info =~ /CONSTRAINT #{quote_column_name(row['name'])} FOREIGN KEY .* REFERENCES .* ON DELETE (CASCADE|SET NULL|RESTRICT)/ - options[:on_delete] = case $1 - when 'CASCADE' then :cascade - when 'SET NULL' then :nullify - end - end + options[:on_update] = extract_foreign_key_action(create_table_info, row['name'], "UPDATE") + options[:on_delete] = extract_foreign_key_action(create_table_info, row['name'], "DELETE") ForeignKeyDefinition.new(table_name, row['to_table'], options) end end + def extract_foreign_key_action(structure, name, action) # :nodoc: + if structure =~ /CONSTRAINT #{quote_column_name(name)} FOREIGN KEY .* REFERENCES .* ON #{action} (CASCADE|SET NULL|RESTRICT)/ + case $1 + when 'CASCADE'; :cascade + when 'SET NULL'; :nullify + end + end + end + # Maps logical Rails types to MySQL-specific data types. def type_to_sql(type, limit = nil, precision = nil, scale = nil) case type.to_s diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index bf87395ef1..7ffbe4434f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -450,7 +450,7 @@ module ActiveRecord def foreign_keys(table_name) fk_info = select_all <<-SQL -SELECT t2.relname AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confdeltype AS dependency +SELECT t2.relname AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete FROM pg_constraint c JOIN pg_class t1 ON c.conrelid = t1.oid JOIN pg_class t2 ON c.confrelid = t2.oid @@ -470,12 +470,17 @@ ORDER BY c.conname primary_key: row['primary_key'] } - options[:on_delete] = case row['dependency'] - when 'c'; :cascade - when 'n'; :nullify - when 'r'; :restrict - end - ForeignKeyDefinition.new(table_name, row["to_table"], options) + options[:on_delete] = extract_foreign_key_action(row['on_delete']) + options[:on_update] = extract_foreign_key_action(row['on_update']) + ForeignKeyDefinition.new(table_name, row['to_table'], options) + end + end + + def extract_foreign_key_action(specifier) # :nodoc: + case specifier + when 'c'; :cascade + when 'n'; :nullify + when 'r'; :restrict end end -- cgit v1.2.3 From 9ae1a2c69f51a9065090a9c505f4d22ffbb84094 Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Wed, 11 Jun 2014 11:16:31 +0200 Subject: fk: raise when identifiers are longer than `allowed_index_name_length`. --- .../active_record/connection_adapters/abstract/schema_statements.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'activerecord/lib/active_record/connection_adapters') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 18e73b0200..5a863717e5 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -682,7 +682,11 @@ module ActiveRecord def foreign_key_name(table_name, options) # :nodoc: options.fetch(:name) do - "#{table_name}_#{options.fetch(:column)}_fk" + identifier = "#{table_name}_#{options.fetch(:column)}_fk" + 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 -- cgit v1.2.3 From 31e4c19331c9262574d354250675bba7dcf9dba2 Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Wed, 11 Jun 2014 17:26:51 +0200 Subject: fk: `add/remove_foreign_key` are noop for adapters that don't support fk --- .../active_record/connection_adapters/abstract/schema_statements.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'activerecord/lib/active_record/connection_adapters') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 5a863717e5..4da42717c1 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -647,6 +647,8 @@ module ActiveRecord end def add_foreign_key(from_table, to_table, options = {}) + return unless supports_foreign_keys? + options[:column] ||= foreign_key_column_for(to_table) primary_key = options.fetch(:primary_key, "id") @@ -664,6 +666,8 @@ module ActiveRecord end def remove_foreign_key(from_table, options_or_to_table = {}) + return unless supports_foreign_keys? + if options_or_to_table.is_a?(Hash) options = options_or_to_table else -- cgit v1.2.3 From 8550ba307d712ebede0d0695b5172bb3e9af16c9 Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Fri, 20 Jun 2014 08:56:30 +0200 Subject: fk: raise for invalid :on_update / :on_delete values --- .../connection_adapters/abstract/schema_creation.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'activerecord/lib/active_record/connection_adapters') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb index b896bd25e4..e495304376 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb @@ -104,9 +104,14 @@ FOREIGN KEY (#{quote_column_name(o.column)}) def action_sql(action, dependency) case dependency - when :nullify then "ON #{action} SET NULL" - when :cascade then "ON #{action} CASCADE" - when :restrict then "ON #{action} RESTRICT" + when :nullify then "ON #{action} SET NULL" + when :cascade then "ON #{action} CASCADE" + when :restrict then "ON #{action} RESTRICT" + else + raise ArgumentError, <<-MSG +'#{dependency}' is not supported for :on_update or :on_delete. +Supported values are: :nullify, :cascade, :restrict + MSG end end end -- cgit v1.2.3 From 8768305f20d12c40241396092a63e0d56269fefe Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Fri, 20 Jun 2014 11:54:17 +0200 Subject: fk: use random digest names 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. --- .../connection_adapters/abstract/schema_creation.rb | 2 +- .../abstract/schema_definitions.rb | 11 ++++++++++- .../abstract/schema_statements.rb | 20 ++++++++++++-------- 3 files changed, 23 insertions(+), 10 deletions(-) (limited to 'activerecord/lib/active_record/connection_adapters') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb index e495304376..5d13ee3633 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb @@ -30,7 +30,7 @@ FOREIGN KEY (#{quote_column_name(o.column)}) end def visit_DropForeignKey(name) - "DROP CONSTRAINT #{name}" + "DROP CONSTRAINT #{quote_column_name(name)}" end private diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 2e47e68754..785cfd9dbc 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -35,7 +35,7 @@ module ActiveRecord end def primary_key - options[:primary_key] + options[:primary_key] || default_primary_key end def on_delete @@ -45,6 +45,15 @@ module ActiveRecord def on_update options[:on_update] end + + def custom_primary_key? + options[:primary_key] != default_primary_key + end + + private + def default_primary_key + "id" + end end # Represents the schema of an SQL table in an abstract way. This class diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 4da42717c1..e203767992 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -650,11 +650,10 @@ module ActiveRecord return unless supports_foreign_keys? options[:column] ||= foreign_key_column_for(to_table) - primary_key = options.fetch(:primary_key, "id") options = { column: options[:column], - primary_key: primary_key, + primary_key: options[:primary_key], name: foreign_key_name(from_table, options), on_delete: options[:on_delete], on_update: options[:on_update] @@ -674,8 +673,17 @@ module ActiveRecord options = { column: foreign_key_column_for(options_or_to_table) } 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.drop_foreign_key foreign_key_name(from_table, options) + at.drop_foreign_key fk_name_to_delete execute schema_creation.accept at end @@ -686,11 +694,7 @@ module ActiveRecord def foreign_key_name(table_name, options) # :nodoc: options.fetch(:name) do - identifier = "#{table_name}_#{options.fetch(:column)}_fk" - 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 + "fk_rails_#{SecureRandom.hex(5)}" end end -- cgit v1.2.3 From 24e1aefb4b2d7b2b4babfd4bae1e9e613283b003 Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Thu, 26 Jun 2014 13:09:45 +0200 Subject: fk: review corrections: indent, visibility, syntax, wording. --- .../abstract/schema_creation.rb | 36 +++++++++++----------- .../abstract/schema_statements.rb | 17 +++++----- .../connection_adapters/abstract_mysql_adapter.rb | 22 ++++++------- .../postgresql/schema_statements.rb | 24 +++++++-------- 4 files changed, 50 insertions(+), 49 deletions(-) (limited to 'activerecord/lib/active_record/connection_adapters') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb index 5d13ee3633..c1379f6bec 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb @@ -18,21 +18,6 @@ module ActiveRecord add_column_options!(sql, column_options(o)) end - def visit_AddForeignKey(o) - sql = <<-SQL -ADD CONSTRAINT #{quote_column_name(o.name)} -FOREIGN KEY (#{quote_column_name(o.column)}) - REFERENCES #{quote_table_name(o.to_table)} (#{quote_column_name(o.primary_key)}) - SQL - sql << " #{action_sql('DELETE', o.on_delete)}" if o.on_delete - sql << " #{action_sql('UPDATE', o.on_update)}" if o.on_update - sql - end - - def visit_DropForeignKey(name) - "DROP CONSTRAINT #{quote_column_name(name)}" - end - private def visit_AlterTable(o) @@ -58,6 +43,21 @@ FOREIGN KEY (#{quote_column_name(o.column)}) create_sql end + def visit_AddForeignKey(o) + sql = <<-SQL.strip_heredoc + ADD CONSTRAINT #{quote_column_name(o.name)} + FOREIGN KEY (#{quote_column_name(o.column)}) + REFERENCES #{quote_table_name(o.to_table)} (#{quote_column_name(o.primary_key)}) + SQL + sql << " #{action_sql('DELETE', o.on_delete)}" if o.on_delete + sql << " #{action_sql('UPDATE', o.on_update)}" if o.on_update + sql + end + + def visit_DropForeignKey(name) + "DROP CONSTRAINT #{quote_column_name(name)}" + end + def column_options(o) column_options = {} column_options[:null] = o.null unless o.null.nil? @@ -108,9 +108,9 @@ FOREIGN KEY (#{quote_column_name(o.column)}) when :cascade then "ON #{action} CASCADE" when :restrict then "ON #{action} RESTRICT" else - raise ArgumentError, <<-MSG -'#{dependency}' is not supported for :on_update or :on_delete. -Supported values are: :nullify, :cascade, :restrict + raise ArgumentError, <<-MSG.strip_heredoc + '#{dependency}' is not supported for :on_update or :on_delete. + Supported values are: :nullify, :cascade, :restrict MSG end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index e203767992..1789cce123 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -661,7 +661,7 @@ module ActiveRecord at = create_alter_table from_table at.add_foreign_key to_table, options - execute schema_creation.accept at + execute schema_creation.accept(at) end def remove_foreign_key(from_table, options_or_to_table = {}) @@ -675,6 +675,7 @@ module ActiveRecord 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 @@ -685,19 +686,13 @@ module ActiveRecord at = create_alter_table from_table at.drop_foreign_key fk_name_to_delete - execute schema_creation.accept at + execute schema_creation.accept(at) end def foreign_key_column_for(table_name) # :nodoc: "#{table_name.to_s.singularize}_id" end - def foreign_key_name(table_name, options) # :nodoc: - options.fetch(:name) do - "fk_rails_#{SecureRandom.hex(5)}" - end - end - def dump_schema_information #:nodoc: sm_table = ActiveRecord::Migrator.schema_migrations_table_name @@ -908,6 +903,12 @@ module ActiveRecord def create_alter_table(name) AlterTable.new create_table_definition(name, false, {}) end + + def foreign_key_name(table_name, options) # :nodoc: + options.fetch(:name) do + "fk_rails_#{SecureRandom.hex(5)}" + end + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 868181e677..4924f345fc 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -510,7 +510,7 @@ module ActiveRecord end def foreign_keys(table_name) - fk_info = select_all %{ + fk_info = select_all <<-SQL.strip_heredoc SELECT fk.referenced_table_name as 'to_table' ,fk.referenced_column_name as 'primary_key' ,fk.column_name as 'column' @@ -519,7 +519,7 @@ module ActiveRecord WHERE fk.referenced_column_name is not null AND fk.table_schema = '#{@config[:database]}' AND fk.table_name = '#{table_name}' - } + SQL create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"] @@ -537,15 +537,6 @@ module ActiveRecord end end - def extract_foreign_key_action(structure, name, action) # :nodoc: - if structure =~ /CONSTRAINT #{quote_column_name(name)} FOREIGN KEY .* REFERENCES .* ON #{action} (CASCADE|SET NULL|RESTRICT)/ - case $1 - when 'CASCADE'; :cascade - when 'SET NULL'; :nullify - end - end - end - # Maps logical Rails types to MySQL-specific data types. def type_to_sql(type, limit = nil, precision = nil, scale = nil) case type.to_s @@ -824,6 +815,15 @@ module ActiveRecord # ...and send them all in one query @connection.query "SET #{encoding} #{variable_assignments}" end + + def extract_foreign_key_action(structure, name, action) # :nodoc: + if structure =~ /CONSTRAINT #{quote_column_name(name)} FOREIGN KEY .* REFERENCES .* ON #{action} (CASCADE|SET NULL|RESTRICT)/ + case $1 + when 'CASCADE'; :cascade + when 'SET NULL'; :nullify + end + end + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index 7ffbe4434f..30df98be1b 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -449,18 +449,18 @@ module ActiveRecord end def foreign_keys(table_name) - fk_info = select_all <<-SQL -SELECT t2.relname AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete -FROM pg_constraint c -JOIN pg_class t1 ON c.conrelid = t1.oid -JOIN pg_class t2 ON c.confrelid = t2.oid -JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid -JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid -JOIN pg_namespace t3 ON c.connamespace = t3.oid -WHERE c.contype = 'f' - AND t1.relname = #{quote(table_name)} - AND t3.nspname = ANY (current_schemas(false)) -ORDER BY c.conname + fk_info = select_all <<-SQL.strip_heredoc + SELECT t2.relname AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete + FROM pg_constraint c + JOIN pg_class t1 ON c.conrelid = t1.oid + JOIN pg_class t2 ON c.confrelid = t2.oid + JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid + JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid + JOIN pg_namespace t3 ON c.connamespace = t3.oid + WHERE c.contype = 'f' + AND t1.relname = #{quote(table_name)} + AND t3.nspname = ANY (current_schemas(false)) + ORDER BY c.conname SQL fk_info.map do |row| -- cgit v1.2.3 From a5b3f372ab30e043d25b25b05e603e6ed33c0ee9 Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Thu, 12 Jun 2014 08:42:00 +0200 Subject: fk: add docs --- .../abstract/schema_statements.rb | 58 ++++++++++++++++++++++ 1 file changed, 58 insertions(+) (limited to 'activerecord/lib/active_record/connection_adapters') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 1789cce123..5814c2b711 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -642,10 +642,54 @@ module ActiveRecord end alias :remove_belongs_to :remove_reference + # Returns an array of foreign keys for the given table. + # The foreign keys are represented as +ForeignKeyDefinition+ objects. def foreign_keys(table_name) raise NotImplementedError, "foreign_keys is not implemented" end + # Adds a new foreign key. +from_table+ is the table with the key column, + # +to_table+ contains the referenced primary key. + # + # The foreign key will be named after the following pattern: fk_rails_. + # +identifier+ is a 10 character long random string. A custom name can be specified with + # the :name option. + # + # ====== Creating a simple foreign key + # + # add_foreign_key :articles, :authors + # + # generates: + # + # ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id") + # + # ====== Creating a foreign key on a specific column + # + # add_foreign_key :articles, :users, column: :author_id, primary_key: "lng_id" + # + # generates: + # + # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_58ca3d3a82 FOREIGN KEY ("author_id") REFERENCES "users" ("lng_id") + # + # ====== Creating a cascading foreign key + # + # add_foreign_key :articles, :authors, on_delete: :cascade + # + # generates: + # + # ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE + # + # The +options+ hash can include the following keys: + # [:column] + # The foreign key column name on +from_table+. Defaults to to_table.singularize + "_id" + # [:primary_key] + # The primary key column name on +to_table+. Defaults to +id+. + # [:name] + # The constraint name. Defaults to fk_rails_. + # [:on_delete] + # Action that happens ON DELETE. Valid values are +:nullify+, +:cascade:+ and +:restrict+ + # [:on_update] + # Action that happens ON UPDATE. Valid values are +:nullify+, +:cascade:+ and +:restrict+ def add_foreign_key(from_table, to_table, options = {}) return unless supports_foreign_keys? @@ -664,6 +708,20 @@ module ActiveRecord execute schema_creation.accept(at) end + # Removes the given foreign key from the table. + # + # Removes the foreign key on +accounts.branch_id+. + # + # remove_foreign_key :accounts, :branches + # + # Removes the foreign key on +accounts.owner_id+. + # + # remove_foreign_key :accounts, column: :owner_id + # + # Removes the foreign key named +special_fk_name+ on the +accounts+ table. + # + # remove_foreign_key :accounts, name: :special_fk_name + # def remove_foreign_key(from_table, options_or_to_table = {}) return unless supports_foreign_keys? -- cgit v1.2.3