pg_power 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. data/README.markdown +212 -0
  2. data/lib/core_ext/active_record/connection_adapters/abstract/schema_statements.rb +139 -0
  3. data/lib/core_ext/active_record/connection_adapters/postgresql_adapter.rb +135 -0
  4. data/lib/core_ext/active_record/schema_dumper.rb +40 -0
  5. data/lib/pg_power.rb +16 -0
  6. data/lib/pg_power/connection_adapters.rb +9 -0
  7. data/lib/pg_power/connection_adapters/abstract_adapter.rb +20 -0
  8. data/lib/pg_power/connection_adapters/abstract_adapter/comment_methods.rb +62 -0
  9. data/lib/pg_power/connection_adapters/abstract_adapter/foreigner_methods.rb +67 -0
  10. data/lib/pg_power/connection_adapters/abstract_adapter/index_methods.rb +6 -0
  11. data/lib/pg_power/connection_adapters/abstract_adapter/schema_methods.rb +18 -0
  12. data/lib/pg_power/connection_adapters/foreign_key_definition.rb +5 -0
  13. data/lib/pg_power/connection_adapters/index_definition.rb +6 -0
  14. data/lib/pg_power/connection_adapters/postgresql_adapter.rb +16 -0
  15. data/lib/pg_power/connection_adapters/postgresql_adapter/comment_methods.rb +79 -0
  16. data/lib/pg_power/connection_adapters/postgresql_adapter/foreigner_methods.rb +190 -0
  17. data/lib/pg_power/connection_adapters/postgresql_adapter/index_methods.rb +42 -0
  18. data/lib/pg_power/connection_adapters/postgresql_adapter/schema_methods.rb +22 -0
  19. data/lib/pg_power/connection_adapters/table.rb +17 -0
  20. data/lib/pg_power/connection_adapters/table/comment_methods.rb +58 -0
  21. data/lib/pg_power/connection_adapters/table/foreigner_methods.rb +51 -0
  22. data/lib/pg_power/engine.rb +46 -0
  23. data/lib/pg_power/migration.rb +4 -0
  24. data/lib/pg_power/migration/command_recorder.rb +13 -0
  25. data/lib/pg_power/migration/command_recorder/comment_methods.rb +52 -0
  26. data/lib/pg_power/migration/command_recorder/foreigner_methods.rb +29 -0
  27. data/lib/pg_power/migration/command_recorder/schema_methods.rb +39 -0
  28. data/lib/pg_power/schema_dumper.rb +21 -0
  29. data/lib/pg_power/schema_dumper/comment_methods.rb +36 -0
  30. data/lib/pg_power/schema_dumper/foreigner_methods.rb +58 -0
  31. data/lib/pg_power/schema_dumper/schema_methods.rb +51 -0
  32. data/lib/pg_power/tools.rb +56 -0
  33. data/lib/pg_power/version.rb +4 -0
  34. data/lib/tasks/pg_power_tasks.rake +4 -0
  35. metadata +213 -0
@@ -0,0 +1,40 @@
1
+ module ActiveRecord #:nodoc:
2
+ # Patched version: 3.1.3
3
+ # Patched methods::
4
+ # * indexes
5
+ class SchemaDumper #:nodoc:
6
+ # Writes out index-related details to the schema stream
7
+ #
8
+ # == Patch reason:
9
+ # {ActiveRecord::SchemaDumper#indexes} does not support writing out
10
+ # details related to partial indexes.
11
+ #
12
+ # == Patch:
13
+ # Append :where clause if there's a partial index
14
+ #
15
+ def indexes(table, stream)
16
+ if (indexes = @connection.indexes(table)).any?
17
+ add_index_statements = indexes.map do |index|
18
+ statement_parts = [
19
+ ('add_index ' + index.table.inspect),
20
+ index.columns.inspect,
21
+ (':name => ' + index.name.inspect),
22
+ ]
23
+ statement_parts << ':unique => true' if index.unique
24
+
25
+ index_lengths = (index.lengths || []).compact
26
+ statement_parts << (':length => ' + Hash[index.columns.zip(index.lengths)].inspect) unless index_lengths.empty?
27
+
28
+ # Patch
29
+ # Append :where clause if a partial index
30
+ statement_parts << (':where => ' + index.where.inspect) if index.where
31
+
32
+ ' ' + statement_parts.join(', ')
33
+ end
34
+
35
+ stream.puts add_index_statements.sort.join("\n")
36
+ stream.puts
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,16 @@
1
+ require "pg_power/engine"
2
+
3
+ # Rails engine which allows to use some PostgreSQL features:
4
+ # * Schemas.
5
+ # * Comments on columns and tables.
6
+ # * Foreign keys.
7
+ # * Partial indexes.
8
+ module PgPower
9
+ extend ActiveSupport::Autoload
10
+
11
+ autoload :Adapter
12
+ autoload :SchemaDumper
13
+ autoload :Tools
14
+ autoload :Migration
15
+ autoload :ConnectionAdapters
16
+ end
@@ -0,0 +1,9 @@
1
+ module PgPower::ConnectionAdapters # :nodoc:
2
+ extend ActiveSupport::Autoload
3
+
4
+ autoload :AbstractAdapter
5
+ autoload :PostgreSQLAdapter, 'pg_power/connection_adapters/postgresql_adapter'
6
+ autoload :Table
7
+ autoload :ForeignKeyDefinition
8
+ autoload :IndexDefinition, 'pg_power/connection_adapters/index_definition'
9
+ end
@@ -0,0 +1,20 @@
1
+ # Extends {ActiveRecord::ConnectionAdapters::AbstractAdapter} class.
2
+ module PgPower::ConnectionAdapters::AbstractAdapter
3
+ extend ActiveSupport::Autoload
4
+ extend ActiveSupport::Concern
5
+
6
+ autoload :CommentMethods
7
+ autoload :ForeignerMethods
8
+ autoload :SchemaMethods
9
+ autoload :IndexMethods
10
+
11
+ include CommentMethods
12
+ include ForeignerMethods
13
+ include SchemaMethods
14
+ include IndexMethods
15
+
16
+ included do
17
+ alias_method_chain :create_table, :schema_option
18
+ alias_method_chain :drop_table , :schema_option
19
+ end
20
+ end
@@ -0,0 +1,62 @@
1
+ # Extends {ActiveRecord::ConnectionAdapters::AbstractAdapter} with
2
+ # empty methods for comments feature.
3
+ module PgPower::ConnectionAdapters::AbstractAdapter::CommentMethods
4
+ def supports_comments?
5
+ false
6
+ end
7
+
8
+ # Sets a comment on the given table.
9
+ #
10
+ # ===== Example
11
+ # ====== Creating a comment on phone_numbers table
12
+ # set_table_comment :phone_numbers, 'This table stores phone numbers that conform to the North American Numbering Plan.'
13
+ def set_table_comment(table_name, comment)
14
+ # Does nothing
15
+ end
16
+
17
+ # Sets a comment on a given column of a given table.
18
+ #
19
+ # ===== Example
20
+ # ====== Creating a comment on npa column of table phone_numbers
21
+ # set_column_comment :phone_numbers, :npa, 'Numbering Plan Area Code - Allowed ranges: [2-9] for first digit, [0-9] for second and third digit.'
22
+ def set_column_comment(table_name, column_name, comment)
23
+ # Does nothing
24
+ end
25
+
26
+ # Sets comments on multiple columns. 'comments' is a hash of column_name => comment pairs.
27
+ #
28
+ # ===== Example
29
+ # ====== Setting comments on the columns of the phone_numbers table
30
+ # set_column_comments :phone_numbers, :npa => 'Numbering Plan Area Code - Allowed ranges: [2-9] for first digit, [0-9] for second and third digit.',
31
+ # :nxx => 'Central Office Number'
32
+ def set_column_comments(table_name, comments)
33
+
34
+ end
35
+
36
+ # Removes any comment from the given table.
37
+ #
38
+ # ===== Example
39
+ # ====== Removing comment from phone numbers table
40
+ # remove_table_comment :phone_numbers
41
+ def remove_table_comment(table_name)
42
+
43
+ end
44
+
45
+ # Removes any comment from the given column of a given table.
46
+ #
47
+ # ===== Example
48
+ # ====== Removing comment from the npa column of table phone_numbers
49
+ # remove_column_comment :phone_numbers, :npa
50
+ def remove_column_comment(table_name, column_name)
51
+
52
+ end
53
+
54
+ # Removes any comment from the given columns of a given table.
55
+ #
56
+ # ===== Example
57
+ # ====== Removing comment from the npa and nxx columns of table phone_numbers
58
+ # remove_column_comments :phone_numbers, :npa, :nxx
59
+ def remove_column_comments(table_name, *column_names)
60
+
61
+ end
62
+ end
@@ -0,0 +1,67 @@
1
+ # Extends {ActiveRecord::ConnectionAdapters::AbstractAdapter} with
2
+ # empty methods for foreign keys feature.
3
+ module PgPower::ConnectionAdapters::AbstractAdapter::ForeignerMethods
4
+ def supports_foreign_keys?
5
+ false
6
+ end
7
+
8
+ # Adds a new foreign key to the +from_table+, referencing the primary key of +to_table+
9
+ #
10
+ # The foreign key will be named after the from and to tables unless you pass
11
+ # <tt>:name</tt> as an option.
12
+ #
13
+ # ===== Examples
14
+ # ====== Creating a foreign key
15
+ # add_foreign_key(:comments, :posts)
16
+ # generates
17
+ # ALTER TABLE `comments` ADD CONSTRAINT
18
+ # `comments_post_id_fk` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`)
19
+ #
20
+ # ====== Creating a named foreign key
21
+ # add_foreign_key(:comments, :posts, :name => 'comments_belongs_to_posts')
22
+ # generates
23
+ # ALTER TABLE `comments` ADD CONSTRAINT
24
+ # `comments_belongs_to_posts` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`)
25
+ #
26
+ # ====== Creating a cascading foreign_key on a custom column
27
+ # add_foreign_key(:people, :people, :column => 'best_friend_id', :dependent => :nullify)
28
+ # generates
29
+ # ALTER TABLE `people` ADD CONSTRAINT
30
+ # `people_best_friend_id_fk` FOREIGN KEY (`best_friend_id`) REFERENCES `people` (`id`)
31
+ # ON DELETE SET NULL
32
+ #
33
+ # === Supported options
34
+ # [:column]
35
+ # Specify the column name on the from_table that references the to_table. By default this is guessed
36
+ # to be the singular name of the to_table with "_id" suffixed. So a to_table of :posts will use "post_id"
37
+ # as the default <tt>:column</tt>.
38
+ # [:primary_key]
39
+ # Specify the column name on the to_table that is referenced by this foreign key. By default this is
40
+ # assumed to be "id".
41
+ # [:name]
42
+ # Specify the name of the foreign key constraint. This defaults to use from_table and foreign key column.
43
+ # [:dependent]
44
+ # If set to <tt>:delete</tt>, the associated records in from_table are deleted when records in to_table table are deleted.
45
+ # If set to <tt>:nullify</tt>, the foreign key column is set to +NULL+.
46
+ # [:options]
47
+ # Any extra options you want appended to the foreign key definition.
48
+ def add_foreign_key(from_table, to_table, options = {})
49
+ end
50
+
51
+ # Remove the given foreign key from the table.
52
+ #
53
+ # ===== Examples
54
+ # ====== Remove the suppliers_company_id_fk in the suppliers table.
55
+ # remove_foreign_key :suppliers, :companies
56
+ # ====== Remove the foreign key named accounts_branch_id_fk in the accounts table.
57
+ # remove_foreign_key :accounts, :column => :branch_id
58
+ # ====== Remove the foreign key named party_foreign_key in the accounts table.
59
+ # remove_foreign_key :accounts, :name => :party_foreign_key
60
+ def remove_foreign_key(from_table, options)
61
+ end
62
+
63
+ # Return the foreign keys for the schema_dumper
64
+ def foreign_keys(table_name)
65
+ []
66
+ end
67
+ end
@@ -0,0 +1,6 @@
1
+ # Extends {ActiveRecord::ConnectionAdapters::AbstractAdapter}
2
+ module PgPower::ConnectionAdapters::AbstractAdapter::IndexMethods
3
+ def supports_partial_index?
4
+ false
5
+ end
6
+ end
@@ -0,0 +1,18 @@
1
+ # Extends {ActiveRecord::ConnectionAdapters::AbstractAdapter}
2
+ # with methods for multi schema support.
3
+ module PgPower::ConnectionAdapters::AbstractAdapter::SchemaMethods
4
+
5
+ # Provides :schema option to +create_table+ method.
6
+ def create_table_with_schema_option(table_name, options = {}, &block)
7
+ schema_name = options.delete(:schema)
8
+ table_name = "#{schema_name}.#{table_name}" if schema_name
9
+ create_table_without_schema_option(table_name, options, &block)
10
+ end
11
+
12
+ # Provides :schema option to +drop_table+ method.
13
+ def drop_table_with_schema_option(table_name, options = {})
14
+ schema_name = options.delete(:schema)
15
+ table_name = "#{schema_name}.#{table_name}" if schema_name
16
+ drop_table_without_schema_option(table_name, options)
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ module PgPower::ConnectionAdapters
2
+ # Structure to store information about foreign keys related to from_table.
3
+ class ForeignKeyDefinition < Struct.new(:from_table, :to_table, :options)
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ module PgPower::ConnectionAdapters
2
+ # Structure to store index parameters
3
+ # Overrides ActiveRecord::ConnectionAdapters::IndexDefinition with the additional :where parameter
4
+ class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :where)
5
+ end
6
+ end
@@ -0,0 +1,16 @@
1
+ # Provides methods to extend {ActiveRecord::ConnectionAdapters::PostgreSQLAdapter}
2
+ # to support pg_power features.
3
+ module PgPower::ConnectionAdapters::PostgreSQLAdapter
4
+ extend ActiveSupport::Autoload
5
+ extend ActiveSupport::Concern
6
+
7
+ autoload :SchemaMethods , 'pg_power/connection_adapters/postgresql_adapter/schema_methods'
8
+ autoload :CommentMethods , 'pg_power/connection_adapters/postgresql_adapter/comment_methods'
9
+ autoload :ForeignerMethods, 'pg_power/connection_adapters/postgresql_adapter/foreigner_methods'
10
+ autoload :IndexMethods , 'pg_power/connection_adapters/postgresql_adapter/index_methods'
11
+
12
+ include SchemaMethods
13
+ include CommentMethods
14
+ include ForeignerMethods
15
+ include IndexMethods
16
+ end
@@ -0,0 +1,79 @@
1
+ # Provides methods to extend {ActiveRecord::ConnectionAdapters::PostgreSQLAdapter}
2
+ # to support comments feature.
3
+ module PgPower::ConnectionAdapters::PostgreSQLAdapter::CommentMethods
4
+ def supports_comments?
5
+ true
6
+ end
7
+
8
+ # Executes SQL to set comment on table
9
+ # @param [String, Symbol] table_name name of table to set a comment on
10
+ # @param [String] comment
11
+ def set_table_comment(table_name, comment)
12
+ sql = "COMMENT ON TABLE #{quote_table_name(table_name)} IS $$#{comment}$$;"
13
+ execute sql
14
+ end
15
+
16
+ # Executes SQL to set comment on column.
17
+ # @param [String, Symbol] table_name
18
+ # @param [String, Symbol] column_name
19
+ # @param [String] comment
20
+ def set_column_comment(table_name, column_name, comment)
21
+ sql = "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} IS $$#{comment}$$;"
22
+ execute sql
23
+ end
24
+
25
+ # Sets comments on columns of passed table.
26
+ # @param [String, Symbol] table_name
27
+ # @param [Hash] comments every key is a column name and value is a comment.
28
+ def set_column_comments(table_name, comments)
29
+ comments.each_pair do |column_name, comment|
30
+ set_column_comment table_name, column_name, comment
31
+ end
32
+ end
33
+
34
+ # Executes SQL to remove comment on passed table.
35
+ # @param [String, Symbol] table_name
36
+ def remove_table_comment(table_name)
37
+ sql = "COMMENT ON TABLE #{quote_table_name(table_name)} IS NULL;"
38
+ execute sql
39
+ end
40
+
41
+ # Executes SQL to remove comment on column.
42
+ # @param [String, Symbol] table_name
43
+ # @param [String, Symbol] column_name
44
+ def remove_column_comment(table_name, column_name)
45
+ sql = "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} IS NULL;"
46
+ execute sql
47
+ end
48
+
49
+ # Remove comments on passed table columns.
50
+ def remove_column_comments(table_name, *column_names)
51
+ column_names.each do |column_name|
52
+ remove_column_comment table_name, column_name
53
+ end
54
+ end
55
+
56
+ # Fetches all comments related to passed table.
57
+ # I returns table comment and column comments as well.
58
+ # ===Example
59
+ # comments("users") # => [[ "" , "Comment on table" ],
60
+ # ["id" , "Comment on id column" ],
61
+ # ["email", "Comment on email column"]]
62
+ def comments(table_name)
63
+ relation_name, schema_name = table_name.split(".", 2).reverse
64
+ schema_name ||= "public"
65
+
66
+ com = select_all <<-SQL
67
+ SELECT a.attname AS column_name, d.description AS comment
68
+ FROM pg_description d
69
+ JOIN pg_class c on c.oid = d.objoid
70
+ LEFT OUTER JOIN pg_attribute a ON c.oid = a.attrelid AND a.attnum = d.objsubid
71
+ JOIN pg_namespace ON c.relnamespace = pg_namespace.oid
72
+ WHERE c.relkind = 'r' AND c.relname = '#{relation_name}' AND
73
+ pg_namespace.nspname = '#{schema_name}'
74
+ SQL
75
+ com.map do |row|
76
+ [ row['column_name'], row['comment'] ]
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,190 @@
1
+ module PgPower # :nodoc:
2
+ # Raised when an unexpected index exists
3
+ class IndexExistsError < StandardError
4
+ end
5
+
6
+ # Provides methods to extend {ActiveRecord::ConnectionAdapters::PostgreSQLAdapter}
7
+ # to support foreign keys feature.
8
+ module ConnectionAdapters::PostgreSQLAdapter::ForeignerMethods
9
+ def supports_foreign_keys?
10
+ true
11
+ end
12
+
13
+ # Fetches information about foreign keys related to passed table.
14
+ # @param [String, Symbol] table_name name of table (e.g. "users", "music.bands")
15
+ # @return [Foreigner::ConnectionAdapters::ForeignKeyDefinition]
16
+ def foreign_keys(table_name)
17
+ relation, schema = table_name.to_s.split('.', 2).reverse
18
+ quoted_schema = schema ? "'#{schema}'" : "ANY (current_schemas(false))"
19
+
20
+ fk_info = select_all <<-SQL
21
+ SELECT nsp.nspname || '.' || t2.relname AS to_table,
22
+ a1.attname AS column ,
23
+ a2.attname AS primary_key,
24
+ c.conname AS name ,
25
+ c.confdeltype AS dependency
26
+ FROM pg_constraint c
27
+ JOIN pg_class t1 ON c.conrelid = t1.oid
28
+ JOIN pg_class t2 ON c.confrelid = t2.oid
29
+ JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
30
+ JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
31
+ JOIN pg_namespace t3 ON c.connamespace = t3.oid
32
+ JOIN pg_namespace nsp ON nsp.oid = t2.relnamespace
33
+ WHERE c.contype = 'f'
34
+ AND t1.relname = '#{relation}'
35
+ AND t3.nspname = #{quoted_schema}
36
+ ORDER BY c.conname
37
+ SQL
38
+
39
+ fk_info.map do |row|
40
+ options = {:column => row['column'], :name => row['name'], :primary_key => row['primary_key']}
41
+
42
+ options[:dependent] = case row['dependency']
43
+ when 'c' then :delete
44
+ when 'n' then :nullify
45
+ when 'r' then :restrict
46
+ end
47
+
48
+ PgPower::ConnectionAdapters::ForeignKeyDefinition.new(table_name, row['to_table'], options)
49
+ end
50
+ end
51
+
52
+ # (optionally disable triggers) and drop table
53
+ # changes adapted from https://github.com/matthuhiggins/foreigner/blob/e72ab9c454c156056d3f037d55e3359cd972af32/lib/foreigner/connection_adapters/sql2003.rb
54
+ # NOTE: disabling referential integrity requires superuser access in postgres. Default AR behavior is to just drop_table.
55
+ #
56
+ # == Options:
57
+ # * :force - force disabling of referential integrity
58
+ #
59
+ # Note: I don't know a good way to test this -mike 20120420
60
+ def drop_table(*args)
61
+ options = args.clone.extract_options!
62
+ if options[:force]
63
+ disable_referential_integrity { super }
64
+ else
65
+ super
66
+ end
67
+ end
68
+
69
+ # Adds foreign key.
70
+ #
71
+ # Ensures that an index is created for the foreign key, unless :exclude_index is true.
72
+ #
73
+ # Raises a [PgPower::IndexExistsError] when :exclude_index is true, but the index already exists.
74
+ #
75
+ # == Options:
76
+ # * :column
77
+ # * :primary_key
78
+ # * :dependent
79
+ # * :exclude_index [Boolean]
80
+ #
81
+ # @param [String, Symbol] from_table
82
+ # @param [String, Symbol] to_table
83
+ # @param [Hash] options
84
+ #
85
+ def add_foreign_key(from_table, to_table, options = {})
86
+ options[:column] ||= id_column_name_from_table_name(to_table)
87
+ options[:exclude_index] ||= false
88
+
89
+ if index_exists?(from_table, options[:column]) and !options[:exclude_index]
90
+ raise PgPower::IndexExistsError, "The index, #{index_name(from_table, options[:column])}, already exists. Use :exclude_index => true when adding the foreign key."
91
+ end
92
+
93
+ sql = "ALTER TABLE #{quote_table_name(from_table)} #{add_foreign_key_sql(from_table, to_table, options)}"
94
+ execute(sql)
95
+
96
+ add_index(from_table, options[:column]) unless options[:exclude_index]
97
+ end
98
+
99
+ # Returns chunk of SQL to add foreign key based on table names and options.
100
+ def add_foreign_key_sql(from_table, to_table, options = {})
101
+ foreign_key_name = foreign_key_name(from_table, options[:column], options)
102
+ primary_key = options[:primary_key] || "id"
103
+ dependency = dependency_sql(options[:dependent])
104
+
105
+ sql =
106
+ "ADD CONSTRAINT #{quote_column_name(foreign_key_name)} " +
107
+ "FOREIGN KEY (#{quote_column_name(options[:column])}) " +
108
+ "REFERENCES #{quote_table_name(ActiveRecord::Migrator.proper_table_name(to_table))}(#{primary_key})"
109
+ sql << " #{dependency}" if dependency.present?
110
+ sql << " #{options[:options]}" if options[:options]
111
+
112
+ sql
113
+ end
114
+
115
+ #
116
+ # TODO Determine if we can refactor the method signature
117
+ # remove_foreign_key(from_table, to_table_or_options_hash, options={}) => remove_foreign_key(from_table, to_table, options={})
118
+ #
119
+ # Removes foreign key.
120
+ # @param [String, Symbol] from_table
121
+ # @param [String, Hash] to_table_or_options_hash
122
+ #
123
+ # The flexible method signature allows calls of two principal forms. Examples:
124
+ #
125
+ # remove_foreign_key(from_table, options_hash)
126
+ #
127
+ # or:
128
+ #
129
+ # remove_foreign_key(from_table, to_table, options_hash)
130
+ #
131
+ def remove_foreign_key(from_table, to_table_or_options_hash, options={})
132
+ if Hash === to_table_or_options_hash
133
+ options = to_table_or_options_hash
134
+ column = options[:column]
135
+ foreign_key_name = foreign_key_name(from_table, column, options)
136
+ column ||= id_column_name_from_foreign_key_metadata(from_table, foreign_key_name)
137
+ else
138
+ column = id_column_name_from_table_name(to_table_or_options_hash)
139
+ foreign_key_name = foreign_key_name(from_table, column)
140
+ end
141
+
142
+ execute "ALTER TABLE #{quote_table_name(from_table)} #{remove_foreign_key_sql(foreign_key_name)}"
143
+
144
+ options[:exclude_index] ||= false
145
+ remove_index(from_table, column) unless options[:exclude_index] || !index_exists?(from_table, column)
146
+ end
147
+
148
+ # Returns chunk of SQL to remove foreign key based on table name and options.
149
+ def remove_foreign_key_sql(foreign_key_name)
150
+ "DROP CONSTRAINT #{quote_column_name(foreign_key_name)}"
151
+ end
152
+
153
+ # Builds the foreign key column id from the referenced table
154
+ def id_column_name_from_table_name(table)
155
+ "#{table.to_s.split('.').last.singularize}_id"
156
+ end
157
+ private :id_column_name_from_table_name
158
+
159
+ # Extracts the foreign key column id from the foreign key metadata
160
+ # @param [String, Symbol] from_table
161
+ # @param [String] foreign_key_name
162
+ def id_column_name_from_foreign_key_metadata(from_table, foreign_key_name)
163
+ keys = foreign_keys(from_table)
164
+ this_key = keys.find {|key| key.options[:name] == foreign_key_name}
165
+ this_key.options[:column]
166
+ end
167
+ private :id_column_name_from_foreign_key_metadata
168
+
169
+ # Builds default name for constraint
170
+ def foreign_key_name(table, column, options = {})
171
+ if options[:name]
172
+ options[:name]
173
+ else
174
+ prefix = table.gsub(".", "_")
175
+ "#{prefix}_#{column}_fk"
176
+ end
177
+ end
178
+ private :foreign_key_name
179
+
180
+ def dependency_sql(dependency)
181
+ case dependency
182
+ when :nullify then "ON DELETE SET NULL"
183
+ when :delete then "ON DELETE CASCADE"
184
+ when :restrict then "ON DELETE RESTRICT"
185
+ else ""
186
+ end
187
+ end
188
+ private :dependency_sql
189
+ end
190
+ end