foreign_keys 1.2.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.
- data/.gitignore +3 -0
- data/README.markdown +73 -0
- data/Rakefile +28 -0
- data/VERSION.yml +4 -0
- data/foreign_keys.gemspec +42 -0
- data/init.rb +1 -0
- data/lib/foreign_keys/mysql_adapter.rb +22 -0
- data/lib/foreign_keys/postgresql_adapter.rb +45 -0
- data/lib/foreign_keys/schema_dumper.rb +33 -0
- data/lib/foreign_keys/schema_statements.rb +70 -0
- data/lib/foreign_keys.rb +4 -0
- metadata +65 -0
data/.gitignore
ADDED
data/README.markdown
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
Foreign Keys Plugin
|
2
|
+
===================
|
3
|
+
|
4
|
+
This plugin adds basic support for foreign keys, by providing some additional
|
5
|
+
schema statements as well as integration into the standard schema dumper.
|
6
|
+
|
7
|
+
Currently, mysql and postgresql connection adapters are supported.
|
8
|
+
|
9
|
+
Installation
|
10
|
+
------------
|
11
|
+
|
12
|
+
Via standard git clone:
|
13
|
+
|
14
|
+
git clone git://github.com/dwalters/foreign_keys.git vendor/plugins/foreign_keys
|
15
|
+
|
16
|
+
Via rails plugin (requires 2.1):
|
17
|
+
|
18
|
+
script/plugin install git://github.com/dwalters/foreign_keys.git
|
19
|
+
|
20
|
+
Adding Foreign Keys
|
21
|
+
-------------------
|
22
|
+
|
23
|
+
Just place the appropriate statements in your migrations. The simplest example is:
|
24
|
+
|
25
|
+
add_foreign_key :orders, :user_id
|
26
|
+
|
27
|
+
The usual rails conventions are used to deduce that this should result in a
|
28
|
+
foreign key from the `orders.user_id` column to the `users.id` column
|
29
|
+
|
30
|
+
Of course, you may specify these values explicitly too, and will need to when
|
31
|
+
the conventions don't match exactly:
|
32
|
+
|
33
|
+
add_foreign_key :articles, :author_id, :references => :users
|
34
|
+
|
35
|
+
Some other options of note:
|
36
|
+
|
37
|
+
* `references`: the name of the referenced table
|
38
|
+
* `keys`: the corresponding column name(s) in the referenced table
|
39
|
+
* `on_delete` / `on_update`: one of `:restrict`, `:set_null`, `:cascade`
|
40
|
+
* `name`: the name to assign to the foreign key constraint
|
41
|
+
|
42
|
+
Note that recent mysql versions will create an index on the referenced
|
43
|
+
column(s) if one does not already exist.
|
44
|
+
|
45
|
+
You may wish to check the documentation for further options.
|
46
|
+
|
47
|
+
Removing Foreign Keys
|
48
|
+
---------------------
|
49
|
+
|
50
|
+
You can remove foreign keys by name:
|
51
|
+
|
52
|
+
remove_foreign_key :orders, :name => "index_orders_on_user_id"
|
53
|
+
|
54
|
+
Or the name can be deduced if you used standard conventions when you created it:
|
55
|
+
|
56
|
+
remove_foreign_key :orders, :user_id
|
57
|
+
|
58
|
+
This will not automatically remove any indexes.
|
59
|
+
|
60
|
+
Schema Dump
|
61
|
+
-----------
|
62
|
+
|
63
|
+
The usual rake targets for `db:schema:dump`, `db:schema:load`, and `db:reset`
|
64
|
+
should all work as desired.
|
65
|
+
|
66
|
+
Author
|
67
|
+
------
|
68
|
+
|
69
|
+
Dan Walters
|
70
|
+
|
71
|
+
<http://github.com/dwalters/foreign_keys>
|
72
|
+
|
73
|
+
Max Lapshin -- PostgreSQL support
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'jeweler'
|
7
|
+
Jeweler::Tasks.new do |jewel|
|
8
|
+
jewel.name = 'foreign_keys'
|
9
|
+
jewel.summary = 'Foreign key sypport for Rails (MySQL, PostgreSQL)'
|
10
|
+
jewel.homepage = 'http://github.com/dwalters/foreign_keys/tree/master'
|
11
|
+
jewel.description = 'Foreign key support plugin for Rails and MySQL, with schema dumper integration'
|
12
|
+
jewel.authors = ["Dan Walters"]
|
13
|
+
end
|
14
|
+
rescue LoadError
|
15
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
16
|
+
end
|
17
|
+
|
18
|
+
desc 'Default: run unit tests.'
|
19
|
+
task :default => :rdoc
|
20
|
+
|
21
|
+
desc 'Generate documentation for the smurf plugin.'
|
22
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
23
|
+
rdoc.rdoc_dir = 'rdoc'
|
24
|
+
rdoc.title = 'Foreign keys'
|
25
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
26
|
+
rdoc.rdoc_files.include('README.markdown')
|
27
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
28
|
+
end
|
data/VERSION.yml
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{foreign_keys}
|
5
|
+
s.version = "1.2.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Dan Walters"]
|
9
|
+
s.date = %q{2009-05-26}
|
10
|
+
s.description = %q{Foreign key support plugin for Rails and MySQL, with schema dumper integration}
|
11
|
+
s.extra_rdoc_files = [
|
12
|
+
"README.markdown"
|
13
|
+
]
|
14
|
+
s.files = [
|
15
|
+
".gitignore",
|
16
|
+
"README.markdown",
|
17
|
+
"Rakefile",
|
18
|
+
"VERSION.yml",
|
19
|
+
"foreign_keys.gemspec",
|
20
|
+
"init.rb",
|
21
|
+
"lib/foreign_keys.rb",
|
22
|
+
"lib/foreign_keys/mysql_adapter.rb",
|
23
|
+
"lib/foreign_keys/postgresql_adapter.rb",
|
24
|
+
"lib/foreign_keys/schema_dumper.rb",
|
25
|
+
"lib/foreign_keys/schema_statements.rb"
|
26
|
+
]
|
27
|
+
s.homepage = %q{http://github.com/dwalters/foreign_keys/tree/master}
|
28
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
29
|
+
s.require_paths = ["lib"]
|
30
|
+
s.rubygems_version = %q{1.3.3}
|
31
|
+
s.summary = %q{Foreign key sypport for Rails (MySQL, PostgreSQL)}
|
32
|
+
|
33
|
+
if s.respond_to? :specification_version then
|
34
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
35
|
+
s.specification_version = 3
|
36
|
+
|
37
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
38
|
+
else
|
39
|
+
end
|
40
|
+
else
|
41
|
+
end
|
42
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'lib/foreign_keys'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
|
2
|
+
def foreign_keys(table_name)#:nodoc:
|
3
|
+
sql = execute("SHOW CREATE TABLE #{quote_table_name(table_name)}").fetch_hash["Create Table"]
|
4
|
+
|
5
|
+
foreign_keys = []
|
6
|
+
sql.scan(/CONSTRAINT `(\w+)` FOREIGN KEY \((.*?)\) REFERENCES `(\w+)` \((.*?)\)(?:\s+ON DELETE\s+(.*?))?(?:\s+ON UPDATE\s+(.*?))?[,\)\n]/) do |name, column_list, references, key_list, on_delete, on_update|
|
7
|
+
columns = []; column_list.scan(/`(\w+)`/) { |match| columns << match.first }
|
8
|
+
keys = []; key_list.scan(/`(\w+)`/) { |match| keys << match.first }
|
9
|
+
|
10
|
+
foreign_keys << {
|
11
|
+
:name => name,
|
12
|
+
:columns => columns,
|
13
|
+
:references => references,
|
14
|
+
:keys => keys,
|
15
|
+
:on_update => on_update,
|
16
|
+
:on_delete => on_delete
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
foreign_keys
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
|
2
|
+
def foreign_keys(table_name)#:nodoc:
|
3
|
+
data = select_all(<<-EOF)
|
4
|
+
select t.constraint_name as name,
|
5
|
+
k.table_name as from_table,
|
6
|
+
k.column_name as from_column,
|
7
|
+
c.table_name as to_table,
|
8
|
+
c.column_name as to_column,
|
9
|
+
r.update_rule as update_rule,
|
10
|
+
r.delete_rule as delete_rule
|
11
|
+
from information_schema.table_constraints t,
|
12
|
+
information_schema.constraint_column_usage c,
|
13
|
+
information_schema.key_column_usage k,
|
14
|
+
information_schema.referential_constraints r
|
15
|
+
where t.constraint_name = c.constraint_name and
|
16
|
+
k.constraint_name = c.constraint_name and
|
17
|
+
r.constraint_name = c.constraint_name and
|
18
|
+
t.constraint_type = 'FOREIGN KEY' and
|
19
|
+
t.table_name = '#{table_name}'
|
20
|
+
EOF
|
21
|
+
|
22
|
+
foreign_keys = data.inject({}) do |list, row|
|
23
|
+
if !list[row["name"]]
|
24
|
+
list[row["name"]] = {
|
25
|
+
:name => row["name"],
|
26
|
+
:columns => [row["from_column"]],
|
27
|
+
:references => row["to_table"],
|
28
|
+
:keys => [row["to_column"]],
|
29
|
+
:on_update => row["update_rule"],
|
30
|
+
:on_delete => row["delete_rule"]
|
31
|
+
}
|
32
|
+
else
|
33
|
+
list[row["name"]][:columns] << row["from_column"]
|
34
|
+
list[row["name"]][:keys] << row["to_column"]
|
35
|
+
end
|
36
|
+
list
|
37
|
+
end.map do |key, ref|
|
38
|
+
ref[:columns] = Array(ref[:columns]).uniq.compact.join(", ")
|
39
|
+
ref[:keys] = Array(ref[:keys]).uniq.compact.join(", ")
|
40
|
+
ref.delete(:on_update) if ref[:on_update] == "NO ACTION"
|
41
|
+
ref.delete(:on_delete) if ref[:on_delete] == "NO ACTION"
|
42
|
+
ref
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
ActiveRecord::SchemaDumper.class_eval do
|
2
|
+
private
|
3
|
+
|
4
|
+
def tables(stream)
|
5
|
+
table_names = @connection.tables.sort.reject do |tbl|
|
6
|
+
['schema_migrations', 'schema_info', ignore_tables].flatten.any? do |ignored|
|
7
|
+
case ignored
|
8
|
+
when String; tbl == ignored
|
9
|
+
when Regexp; tbl =~ ignored
|
10
|
+
else
|
11
|
+
raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
table_names.each { |tbl| table(tbl, stream) }
|
17
|
+
table_names.each { |tbl| foreign_keys(tbl, stream) } if @connection.respond_to?(:foreign_keys)
|
18
|
+
end
|
19
|
+
|
20
|
+
def foreign_keys(table, stream)
|
21
|
+
foreign_keys = @connection.foreign_keys(table).sort { |a, b| a[:columns] <=> b[:columns] }
|
22
|
+
foreign_keys.each do |foreign_key|
|
23
|
+
stream.print " add_foreign_key #{table.inspect}, #{foreign_key[:columns].inspect}"
|
24
|
+
stream.print ", :references => #{foreign_key[:references].inspect}" if foreign_key[:references] && foreign_key[:references] != foreign_key[:columns].first.gsub(/_id$/, '').tableize
|
25
|
+
stream.print ", :keys => #{foreign_key[:keys].inspect}" if foreign_key[:keys] && foreign_key[:keys] != ["id"]
|
26
|
+
stream.print ", :name => #{foreign_key[:name].inspect}" if foreign_key[:name]
|
27
|
+
stream.print ", :on_delete => #{foreign_key[:on_delete].downcase.gsub(/ /, '_').to_sym.inspect}" if foreign_key[:on_delete]
|
28
|
+
stream.print ", :on_update => #{foreign_key[:on_update].downcase.gsub(/ /, '_').to_sym.inspect}" if foreign_key[:on_update]
|
29
|
+
stream.puts
|
30
|
+
end
|
31
|
+
stream.puts unless foreign_keys.empty?
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
ActiveRecord::ConnectionAdapters::SchemaStatements.module_eval do
|
2
|
+
# Adds a new foreign key to a table. +columns+ can be a single Symbol, or an
|
3
|
+
# array of Symbols, on which the key will be added.
|
4
|
+
#
|
5
|
+
# The referenced table name and columns are deduced using the usual conventions
|
6
|
+
# unless explicitly specified via +:references+ and/or +:keys+ options.
|
7
|
+
#
|
8
|
+
# The foreign key will be named using the same rules as #add_index, unless you
|
9
|
+
# pass +:name+ as an option.
|
10
|
+
#
|
11
|
+
# Options for +:on_delete+ and +:on_update+ may be specified. Acceptable
|
12
|
+
# values are +:restrict+, +:set_null+, and +:cascade+.
|
13
|
+
#
|
14
|
+
# Note that some databases will automatically create an index on the constrained
|
15
|
+
# columns.
|
16
|
+
#
|
17
|
+
# ===== Examples
|
18
|
+
# ====== Creating a simple foreign key
|
19
|
+
# add_foreign_key :orders, :user_id
|
20
|
+
# generates
|
21
|
+
# ALTER TABLE orders ADD CONSTRAINT index_orders_on_user_id FOREIGN KEY (user_id) REFERENCES users (id)
|
22
|
+
# ====== Specifying the target table
|
23
|
+
# add_foreign_key :articles, :author_id, :references => :users
|
24
|
+
# generates
|
25
|
+
# ALTER TABLE articles ADD CONSTRAINT index_articles_on_author_id FOREIGN KEY (author_id) REFERENCES users (id)
|
26
|
+
# ====== Cascading deletes
|
27
|
+
# add_foreign_key :comments, :post_id, :on_delete => :cascade
|
28
|
+
# generates
|
29
|
+
# ALTER TABLE comments ADD CONSTRAINT index_comments_on_post_id FOREIGN KEY (post_id) REFERENCES posts (id) ON DELETE CASCADE
|
30
|
+
def add_foreign_key(table_name, columns, options = {})
|
31
|
+
columns = Array(columns)
|
32
|
+
name = options[:name] || index_name(table_name, :column => columns)
|
33
|
+
column_list = columns.collect { |column| quote_column_name(column) }.join(", ")
|
34
|
+
keys = Array(options[:keys] || :id)
|
35
|
+
key_list = keys.collect { |key| quote_column_name(key) }.join(", ")
|
36
|
+
references = if options[:references]
|
37
|
+
options[:references]
|
38
|
+
else
|
39
|
+
columns.first.to_s.gsub(/_id$/, '').tableize
|
40
|
+
end
|
41
|
+
|
42
|
+
sql = "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{quote_column_name(name)} FOREIGN KEY (#{column_list}) REFERENCES #{quote_table_name(references)} (#{key_list})"
|
43
|
+
sql << " ON DELETE #{options[:on_delete].to_s.gsub(/_/,' ')}" if options[:on_delete]
|
44
|
+
sql << " ON UPDATE #{options[:on_update].to_s.gsub(/_/,' ')}" if options[:on_update]
|
45
|
+
execute sql
|
46
|
+
end
|
47
|
+
|
48
|
+
# Remove the given foreign key from the table. If +:name+ is specified, it will be used
|
49
|
+
# directly as the name of the key to delete. Otherwise, the same conventions used
|
50
|
+
# when by #add_foreign_key may be provided.
|
51
|
+
#
|
52
|
+
# Any underlying indexes will not be automatically dropped.
|
53
|
+
#
|
54
|
+
# ===== Examples
|
55
|
+
# ====== Removing a foreign key by name
|
56
|
+
# remove_foreign_key :orders, :name => "index_orders_on_user_id"
|
57
|
+
# ====== Removing a foreign key by column
|
58
|
+
# remove_foreign_key :orders, :user_id
|
59
|
+
def remove_foreign_key(table_name, *args)
|
60
|
+
options = args.extract_options!
|
61
|
+
name = if options[:name]
|
62
|
+
options[:name]
|
63
|
+
else
|
64
|
+
columns = args.first
|
65
|
+
index_name(table_name, :column => Array(columns))
|
66
|
+
end
|
67
|
+
|
68
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} DROP FOREIGN KEY #{quote_column_name(name)}"
|
69
|
+
end
|
70
|
+
end
|
data/lib/foreign_keys.rb
ADDED
@@ -0,0 +1,4 @@
|
|
1
|
+
require 'foreign_keys/mysql_adapter' if defined?(ActiveRecord::ConnectionAdapters::MysqlAdapter)
|
2
|
+
require 'foreign_keys/postgresql_adapter' if defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
|
3
|
+
require 'foreign_keys/schema_dumper'
|
4
|
+
require 'foreign_keys/schema_statements'
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: foreign_keys
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dan Walters
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-05-26 00:00:00 +03:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Foreign key support plugin for Rails and MySQL, with schema dumper integration
|
17
|
+
email:
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.markdown
|
24
|
+
files:
|
25
|
+
- .gitignore
|
26
|
+
- README.markdown
|
27
|
+
- Rakefile
|
28
|
+
- VERSION.yml
|
29
|
+
- foreign_keys.gemspec
|
30
|
+
- init.rb
|
31
|
+
- lib/foreign_keys.rb
|
32
|
+
- lib/foreign_keys/mysql_adapter.rb
|
33
|
+
- lib/foreign_keys/postgresql_adapter.rb
|
34
|
+
- lib/foreign_keys/schema_dumper.rb
|
35
|
+
- lib/foreign_keys/schema_statements.rb
|
36
|
+
has_rdoc: true
|
37
|
+
homepage: http://github.com/dwalters/foreign_keys/tree/master
|
38
|
+
licenses: []
|
39
|
+
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options:
|
42
|
+
- --charset=UTF-8
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
version:
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
version:
|
57
|
+
requirements: []
|
58
|
+
|
59
|
+
rubyforge_project:
|
60
|
+
rubygems_version: 1.3.5
|
61
|
+
signing_key:
|
62
|
+
specification_version: 3
|
63
|
+
summary: Foreign key sypport for Rails (MySQL, PostgreSQL)
|
64
|
+
test_files: []
|
65
|
+
|