frac-foreigner 0.0.1
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/MIT-LICENSE +20 -0
- data/README.rdoc +92 -0
- data/Rakefile +23 -0
- data/lib/foreigner/connection_adapters/abstract/schema_definitions.rb +78 -0
- data/lib/foreigner/connection_adapters/abstract/schema_statements.rb +77 -0
- data/lib/foreigner/connection_adapters/postgresql_adapter.rb +68 -0
- data/lib/foreigner/connection_adapters/sql_2003.rb +44 -0
- data/lib/foreigner/connection_adapters/sqlserver_adapter.rb +90 -0
- data/lib/foreigner/schema_dumper.rb +81 -0
- data/lib/frac-foreigner.rb +52 -0
- data/test/helper.rb +5 -0
- data/test/mysql_adapter_test.rb +100 -0
- metadata +57 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 [name of plugin creator]
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
= Foreigner
|
2
|
+
|
3
|
+
Rails does not come with methods to add foreign keys. Foreigner introduces a few
|
4
|
+
methods to your migrations for adding and removing foreign key constraints.
|
5
|
+
|
6
|
+
Since each adapter implements the API, migrations using Foreigner will continue to
|
7
|
+
work on databases that do not support foreign keys, such as sqlite3.
|
8
|
+
|
9
|
+
== Installation
|
10
|
+
|
11
|
+
In Rails 2, install as a gem by adding the following to config/environment.rb:
|
12
|
+
|
13
|
+
config.gem 'foreigner'
|
14
|
+
|
15
|
+
In Rails 3, install as a gem by adding the following to your Gemfile:
|
16
|
+
|
17
|
+
gem 'foreigner'
|
18
|
+
|
19
|
+
== API
|
20
|
+
|
21
|
+
An adapter implementing the Foreigner API implements three methods.
|
22
|
+
(Options are documented in connection_adapters/abstract/schema_definitions.rb):
|
23
|
+
|
24
|
+
add_foreign_key(from_table, to_table, options)
|
25
|
+
remove_foreign_key(from_table, options)
|
26
|
+
foreign_keys(table_name)
|
27
|
+
|
28
|
+
== Example
|
29
|
+
|
30
|
+
The most common use of foreign keys is to reference a table that a model belongs to.
|
31
|
+
For example, given the following model:
|
32
|
+
|
33
|
+
class Comment < ActiveRecord::Base
|
34
|
+
belongs_to :post
|
35
|
+
end
|
36
|
+
|
37
|
+
class Post < ActiveRecord::Base
|
38
|
+
has_many :comments, :dependent => :delete_all
|
39
|
+
end
|
40
|
+
|
41
|
+
You should add a foreign key in your migration:
|
42
|
+
|
43
|
+
add_foreign_key(:comments, :posts)
|
44
|
+
|
45
|
+
The :dependent option can be moved from the has_many definition to the foreign key:
|
46
|
+
|
47
|
+
add_foreign_key(:comments, :posts, :dependent => :delete)
|
48
|
+
|
49
|
+
If the column is named article_id instead of post_id, use the :column option:
|
50
|
+
|
51
|
+
add_foreign_key(:comments, :posts, :column => 'article_id')
|
52
|
+
|
53
|
+
A name can be specified for the foreign key constraint:
|
54
|
+
|
55
|
+
add_foreign_key(:comments, :posts, :name => 'comment_article_foreign_key')
|
56
|
+
|
57
|
+
== Change Table Shorthand
|
58
|
+
|
59
|
+
Foreigner adds extra behavior to change_table, which lets you define foreign keys using shorthand.
|
60
|
+
|
61
|
+
Add a missing foreign key to comments:
|
62
|
+
|
63
|
+
change_table :comments do |t|
|
64
|
+
t.foreign_key :posts, :dependent => :delete
|
65
|
+
end
|
66
|
+
|
67
|
+
t.foreign_key accepts the same options as add_foreign_key.
|
68
|
+
|
69
|
+
|
70
|
+
== Additional t.references option
|
71
|
+
|
72
|
+
Foreigner extends table.references with the :foreign_key option. Pass true, and the default
|
73
|
+
foreign key options are used:
|
74
|
+
|
75
|
+
change_table :comments do |t|
|
76
|
+
t.references :post, :foreign_key => true
|
77
|
+
end
|
78
|
+
|
79
|
+
An options hash can also be passed. It accepts the same options as add_foreign_key:
|
80
|
+
|
81
|
+
change_table :comments do |t|
|
82
|
+
t.references :author, :foreign_key => {:dependent => :restrict}
|
83
|
+
end
|
84
|
+
|
85
|
+
By default, t.references will not generate a foreign key.
|
86
|
+
|
87
|
+
== schema.rb
|
88
|
+
|
89
|
+
Similar to indexes, the foreign keys in your database are automatically dumped to schema.rb.
|
90
|
+
This allows you to use foreign keys without switching to the :sql schema.
|
91
|
+
|
92
|
+
Copyright (c) 2009 Matthew Higgins, released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'Test the frac-foreigner plugin.'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.libs << 'test'
|
12
|
+
t.pattern = 'test/**/*_test.rb'
|
13
|
+
t.verbose = true
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'Generate documentation for the foreigner plugin.'
|
17
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
18
|
+
rdoc.rdoc_dir = 'rdoc'
|
19
|
+
rdoc.title = 'Frac-Foreigner'
|
20
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
21
|
+
rdoc.rdoc_files.include('README')
|
22
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
23
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Foreigner
|
2
|
+
module ConnectionAdapters
|
3
|
+
class ForeignKeyDefinition < Struct.new(:from_table, :to_table, :options) #:nodoc:
|
4
|
+
end
|
5
|
+
|
6
|
+
module SchemaDefinitions
|
7
|
+
def self.included(base)
|
8
|
+
base::Table.class_eval do
|
9
|
+
include Foreigner::ConnectionAdapters::Table
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module Table
|
15
|
+
def self.included(base)
|
16
|
+
base.class_eval do
|
17
|
+
include InstanceMethods
|
18
|
+
alias_method_chain :references, :foreign_keys
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module InstanceMethods
|
23
|
+
# Adds a new foreign key to the table. +to_table+ can be a single Symbol, or
|
24
|
+
# an Array of Symbols. See SchemaStatements#add_foreign_key
|
25
|
+
#
|
26
|
+
# ===== Examples
|
27
|
+
# ====== Creating a simple foreign key
|
28
|
+
# t.foreign_key(:people)
|
29
|
+
# ====== Defining the column
|
30
|
+
# t.foreign_key(:people, :column => :sender_id)
|
31
|
+
# ====== Creating a named foreign key
|
32
|
+
# t.foreign_key(:people, :column => :sender_id, :name => 'sender_foreign_key')
|
33
|
+
# ====== Defining the column of the +to_table+.
|
34
|
+
# t.foreign_key(:people, :column => :sender_id, :primary_key => :person_id)
|
35
|
+
def foreign_key(to_table, options = {})
|
36
|
+
@base.add_foreign_key(@table_name, to_table, options)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Remove the given foreign key from the table.
|
40
|
+
#
|
41
|
+
# ===== Examples
|
42
|
+
# ====== Remove the suppliers_company_id_fk in the suppliers table.
|
43
|
+
# t.remove_foreign_key :companies
|
44
|
+
# ====== Remove the foreign key named accounts_branch_id_fk in the accounts table.
|
45
|
+
# remove_foreign_key :column => :branch_id
|
46
|
+
# ====== Remove the foreign key named party_foreign_key in the accounts table.
|
47
|
+
# remove_index :name => :party_foreign_key
|
48
|
+
def remove_foreign_key(options = {})
|
49
|
+
@base.remove_foreign_key(@table_name, options)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Adds a :foreign_key option to Table.references.
|
53
|
+
# If :foreign_key is true, a foreign key constraint is added to the table.
|
54
|
+
# You can also specify a hash, which is passed as foreign key options.
|
55
|
+
#
|
56
|
+
# ===== Examples
|
57
|
+
# ====== Add goat_id column and a foreign key to the goats table.
|
58
|
+
# t.references(:goat, :foreign_key => true)
|
59
|
+
# ====== Add goat_id column and a cascading foreign key to the goats table.
|
60
|
+
# t.references(:goat, :foreign_key => {:dependent => :delete})
|
61
|
+
#
|
62
|
+
# Note: No foreign key is created if :polymorphic => true is used.
|
63
|
+
def references_with_foreign_keys(*args)
|
64
|
+
options = args.extract_options!
|
65
|
+
polymorphic = options[:polymorphic]
|
66
|
+
fk_options = options.delete(:foreign_key)
|
67
|
+
|
68
|
+
references_without_foreign_keys(*(args.dup << options))
|
69
|
+
|
70
|
+
if fk_options && !polymorphic
|
71
|
+
fk_options = {} if fk_options == true
|
72
|
+
args.each { |to_table| foreign_key(to_table, fk_options) }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Foreigner
|
2
|
+
module ConnectionAdapters
|
3
|
+
module SchemaStatements
|
4
|
+
def self.included(base)
|
5
|
+
base::AbstractAdapter.class_eval do
|
6
|
+
include Foreigner::ConnectionAdapters::AbstractAdapter
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module AbstractAdapter
|
12
|
+
def supports_foreign_keys?
|
13
|
+
false
|
14
|
+
end
|
15
|
+
|
16
|
+
# Adds a new foreign key to the +from_table+, referencing the primary key of +to_table+
|
17
|
+
#
|
18
|
+
# The foreign key will be named after the from and to tables unless you pass
|
19
|
+
# <tt>:name</tt> as an option.
|
20
|
+
#
|
21
|
+
# ===== Examples
|
22
|
+
# ====== Creating a foreign key
|
23
|
+
# add_foreign_key(:comments, :posts)
|
24
|
+
# generates
|
25
|
+
# ALTER TABLE `comments` ADD CONSTRAINT
|
26
|
+
# `comments_post_id_fk` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`)
|
27
|
+
#
|
28
|
+
# ====== Creating a named foreign key
|
29
|
+
# add_foreign_key(:comments, :posts, :name => 'comments_belongs_to_posts')
|
30
|
+
# generates
|
31
|
+
# ALTER TABLE `comments` ADD CONSTRAINT
|
32
|
+
# `comments_belongs_to_posts` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`)
|
33
|
+
#
|
34
|
+
# ====== Creating a cascading foreign_key on a custom column
|
35
|
+
# add_foreign_key(:people, :people, :column => 'best_friend_id', :dependent => :nullify)
|
36
|
+
# generates
|
37
|
+
# ALTER TABLE `people` ADD CONSTRAINT
|
38
|
+
# `people_best_friend_id_fk` FOREIGN KEY (`best_friend_id`) REFERENCES `people` (`id`)
|
39
|
+
# ON DELETE SET NULL
|
40
|
+
#
|
41
|
+
# === Supported options
|
42
|
+
# [:column]
|
43
|
+
# Specify the column name on the from_table that references the to_table. By default this is guessed
|
44
|
+
# to be the singular name of the to_table with "_id" suffixed. So a to_table of :posts will use "post_id"
|
45
|
+
# as the default <tt>:column</tt>.
|
46
|
+
# [:primary_key]
|
47
|
+
# Specify the column name on the to_table that is referenced by this foreign key. By default this is
|
48
|
+
# assumed to be "id".
|
49
|
+
# [:name]
|
50
|
+
# Specify the name of the foreign key constraint. This defaults to use from_table and foreign key column.
|
51
|
+
# [:dependent]
|
52
|
+
# If set to <tt>:delete</tt>, the associated records in from_table are deleted when records in to_table table are deleted.
|
53
|
+
# If set to <tt>:nullify</tt>, the foreign key column is set to +NULL+.
|
54
|
+
# [:options]
|
55
|
+
# Any extra options you want appended to the foreign key definition.
|
56
|
+
def add_foreign_key(from_table, to_table, options = {})
|
57
|
+
end
|
58
|
+
|
59
|
+
# Remove the given foreign key from the table.
|
60
|
+
#
|
61
|
+
# ===== Examples
|
62
|
+
# ====== Remove the suppliers_company_id_fk in the suppliers table.
|
63
|
+
# remove_foreign_key :suppliers, :companies
|
64
|
+
# ====== Remove the foreign key named accounts_branch_id_fk in the accounts table.
|
65
|
+
# remove_foreign_key :accounts, :column => :branch_id
|
66
|
+
# ====== Remove the foreign key named party_foreign_key in the accounts table.
|
67
|
+
# remove_foreign_key :accounts, :name => :party_foreign_key
|
68
|
+
def remove_foreign_key(from_table, options)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Return the foreign keys for the schema_dumper
|
72
|
+
def foreign_keys(table_name)
|
73
|
+
[]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Foreigner
|
2
|
+
module ConnectionAdapters
|
3
|
+
module PostgreSQLAdapter
|
4
|
+
include Foreigner::ConnectionAdapters::Sql2003
|
5
|
+
|
6
|
+
def remove_foreign_key(table, options)
|
7
|
+
if Hash === options
|
8
|
+
foreign_key_name = foreign_key_name(table, options[:column], options)
|
9
|
+
else
|
10
|
+
foreign_key_name = foreign_key_name(table, "#{options.to_s.singularize}_id")
|
11
|
+
end
|
12
|
+
|
13
|
+
execute "ALTER TABLE #{quote_table_name(table)} DROP CONSTRAINT #{quote_column_name(foreign_key_name)}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_primary_key(table,options)
|
17
|
+
constraint_name = "pk_#{table}_#{options[:column]}"
|
18
|
+
execute "ALTER TABLE #{table} ADD CONSTRAINT #{constraint_name} PRIMARY KEY (#{options[:column]})"
|
19
|
+
end
|
20
|
+
|
21
|
+
def remove_primary_key(table, options)
|
22
|
+
constraint_name = "pk_#{table}_#{options[:column]}"
|
23
|
+
execute "ALTER TABLE #{table} DROP CONSTRAINT #{constraint_name}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def foreign_keys(table_name)
|
27
|
+
fk_info = select_all %{
|
28
|
+
SELECT tc.constraint_name as name
|
29
|
+
,ccu.table_name as to_table
|
30
|
+
,ccu.column_name as primary_key
|
31
|
+
,kcu.column_name as column
|
32
|
+
,rc.delete_rule as dependency
|
33
|
+
FROM information_schema.table_constraints tc
|
34
|
+
JOIN information_schema.key_column_usage kcu
|
35
|
+
USING (constraint_catalog, constraint_schema, constraint_name)
|
36
|
+
JOIN information_schema.referential_constraints rc
|
37
|
+
USING (constraint_catalog, constraint_schema, constraint_name)
|
38
|
+
JOIN information_schema.constraint_column_usage ccu
|
39
|
+
USING (constraint_catalog, constraint_schema, constraint_name)
|
40
|
+
WHERE tc.constraint_type = 'FOREIGN KEY'
|
41
|
+
AND tc.constraint_catalog = '#{@config[:database]}'
|
42
|
+
AND tc.table_name = '#{table_name}'
|
43
|
+
}
|
44
|
+
|
45
|
+
fk_info.map do |row|
|
46
|
+
options = {:column => row['column'], :name => row['name'], :primary_key => row['primary_key']}
|
47
|
+
|
48
|
+
options[:dependent] = case row['dependency']
|
49
|
+
when 'CASCADE' then :delete
|
50
|
+
when 'SET NULL' then :nullify
|
51
|
+
when 'RESTRICT' then :restrict
|
52
|
+
end
|
53
|
+
|
54
|
+
ForeignKeyDefinition.new(table_name, row['to_table'], options)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
[:PostgreSQLAdapter, :JdbcAdapter].each do |adapter|
|
62
|
+
begin
|
63
|
+
ActiveRecord::ConnectionAdapters.const_get(adapter).class_eval do
|
64
|
+
include Foreigner::ConnectionAdapters::PostgreSQLAdapter
|
65
|
+
end
|
66
|
+
rescue
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Foreigner
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Sql2003
|
4
|
+
def supports_foreign_keys?
|
5
|
+
true
|
6
|
+
end
|
7
|
+
|
8
|
+
def add_foreign_key(from_table, to_table, options = {})
|
9
|
+
column = options[:column] || "#{to_table.to_s.singularize}_id"
|
10
|
+
foreign_key_name = foreign_key_name(from_table, column, options)
|
11
|
+
primary_key = options[:primary_key] || "id"
|
12
|
+
dependency = dependency_sql(options[:dependent])
|
13
|
+
|
14
|
+
sql =
|
15
|
+
"ALTER TABLE #{quote_table_name(from_table)} " +
|
16
|
+
"ADD CONSTRAINT #{quote_column_name(foreign_key_name)} " +
|
17
|
+
"FOREIGN KEY (#{quote_column_name(column)}) " +
|
18
|
+
"REFERENCES #{quote_table_name(ActiveRecord::Migrator.proper_table_name(to_table))}(#{primary_key})"
|
19
|
+
sql << " #{dependency}" if dependency.present?
|
20
|
+
sql << " #{options[:options]}" if options[:options]
|
21
|
+
|
22
|
+
execute(sql)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def foreign_key_name(table, column, options = {})
|
27
|
+
if options[:name]
|
28
|
+
options[:name]
|
29
|
+
else
|
30
|
+
"#{table}_#{column}_fk"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def dependency_sql(dependency)
|
35
|
+
case dependency
|
36
|
+
when :nullify then "ON DELETE SET NULL"
|
37
|
+
when :delete then "ON DELETE CASCADE"
|
38
|
+
when :restrict then "ON DELETE RESTRICT"
|
39
|
+
else ""
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Foreigner
|
2
|
+
module ConnectionAdapters
|
3
|
+
module SqlserverAdapter
|
4
|
+
include Foreigner::ConnectionAdapters::Sql2003
|
5
|
+
|
6
|
+
def remove_foreign_key(table, options)
|
7
|
+
if Hash === options
|
8
|
+
foreign_key_name = foreign_key_name(table, options[:column], options)
|
9
|
+
else
|
10
|
+
foreign_key_name = foreign_key_name(table, "#{options.to_s.singularize}_id")
|
11
|
+
end
|
12
|
+
execute "IF EXISTS (SELECT 1 from sys.objects where name = '#{foreign_key_name}') ALTER TABLE #{table} DROP CONSTRAINT #{foreign_key_name}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_primary_key(table,options)
|
16
|
+
constraint_name = "pk_#{table}_#{options[:column]}"
|
17
|
+
execute "alter table #{table} add constraint #{constraint_name} primary key (#{options[:column]})"
|
18
|
+
end
|
19
|
+
|
20
|
+
def remove_primary_key(table, options)
|
21
|
+
constraint_name = "pk_#{table}_#{options[:column]}"
|
22
|
+
execute "alter table #{table} drop constraint #{constraint_name}"
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def pk(table_name)
|
27
|
+
pk_info = select_all %{
|
28
|
+
select c.COLUMN_NAME as 'pk'
|
29
|
+
from INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk ,
|
30
|
+
INFORMATION_SCHEMA.KEY_COLUMN_USAGE c
|
31
|
+
where pk.TABLE_NAME = '#{table_name}'
|
32
|
+
and CONSTRAINT_TYPE = 'PRIMARY KEY'
|
33
|
+
and c.TABLE_NAME = pk.TABLE_NAME
|
34
|
+
and c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME
|
35
|
+
}
|
36
|
+
pk_info.map do |row|
|
37
|
+
options = {:pk => row['pk']}
|
38
|
+
options
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def foreign_keys(table_name)
|
43
|
+
fk_info = select_all %{
|
44
|
+
SELECT
|
45
|
+
'column' = CU.COLUMN_NAME,
|
46
|
+
to_table = PK.TABLE_NAME,
|
47
|
+
primary_key = PT.COLUMN_NAME,
|
48
|
+
'name' = C.CONSTRAINT_NAME
|
49
|
+
FROM
|
50
|
+
INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS C
|
51
|
+
INNER JOIN
|
52
|
+
INFORMATION_SCHEMA.TABLE_CONSTRAINTS FK
|
53
|
+
ON C.CONSTRAINT_NAME = FK.CONSTRAINT_NAME
|
54
|
+
INNER JOIN
|
55
|
+
INFORMATION_SCHEMA.TABLE_CONSTRAINTS PK
|
56
|
+
ON C.UNIQUE_CONSTRAINT_NAME = PK.CONSTRAINT_NAME
|
57
|
+
INNER JOIN
|
58
|
+
INFORMATION_SCHEMA.KEY_COLUMN_USAGE CU
|
59
|
+
ON C.CONSTRAINT_NAME = CU.CONSTRAINT_NAME
|
60
|
+
INNER JOIN
|
61
|
+
(
|
62
|
+
SELECT
|
63
|
+
i1.TABLE_NAME, i2.COLUMN_NAME
|
64
|
+
FROM
|
65
|
+
INFORMATION_SCHEMA.TABLE_CONSTRAINTS i1
|
66
|
+
INNER JOIN
|
67
|
+
INFORMATION_SCHEMA.KEY_COLUMN_USAGE i2
|
68
|
+
ON i1.CONSTRAINT_NAME = i2.CONSTRAINT_NAME
|
69
|
+
WHERE i1.CONSTRAINT_TYPE = 'PRIMARY KEY'
|
70
|
+
) PT ON PT.TABLE_NAME = PK.TABLE_NAME
|
71
|
+
WHERE FK.TABLE_NAME= '#{table_name}'
|
72
|
+
}
|
73
|
+
|
74
|
+
fk_info.map do |row|
|
75
|
+
options = {:column => row['column'], :name => row['name'], :primary_key => row['primary_key']}
|
76
|
+
ForeignKeyDefinition.new(table_name, row['to_table'], options)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
[:SQLServerAdapter].each do |adapter|
|
84
|
+
begin
|
85
|
+
ActiveRecord::ConnectionAdapters.const_get(adapter).class_eval do
|
86
|
+
include Foreigner::ConnectionAdapters::SqlserverAdapter
|
87
|
+
end
|
88
|
+
rescue
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Foreigner
|
2
|
+
module SchemaDumper
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.class_eval do
|
6
|
+
include InstanceMethods
|
7
|
+
alias_method_chain :tables, :foreign_keys
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module InstanceMethods
|
12
|
+
def tables_with_foreign_keys(stream)
|
13
|
+
@connection.tables.sort.each do |table|
|
14
|
+
remove_foreign_keys(table, stream)
|
15
|
+
end
|
16
|
+
tables_without_foreign_keys(stream)
|
17
|
+
@connection.tables.sort.each do |table|
|
18
|
+
primary_keys(table, stream)
|
19
|
+
end
|
20
|
+
@connection.tables.sort.each do |table|
|
21
|
+
foreign_keys(table, stream)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def remove_foreign_keys(table_name, stream)
|
28
|
+
if (foreign_keys = @connection.foreign_keys(table_name)).any?
|
29
|
+
remove_foreign_key_statements = foreign_keys.map do |foreign_key|
|
30
|
+
statement_parts = [('remove_foreign_key ' + foreign_key.from_table.inspect)]
|
31
|
+
statement_parts << (':name => ' + foreign_key.options[:name].inspect)
|
32
|
+
' ' + statement_parts.join(', ')
|
33
|
+
end
|
34
|
+
|
35
|
+
stream.puts remove_foreign_key_statements.sort.join("\n")
|
36
|
+
stream.puts
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def primary_keys(table_name,stream)
|
41
|
+
if (primary_keys = @connection.pk(table_name)).any? && table_name != "sysdiagrams"
|
42
|
+
add_primary_keys_statement = primary_keys.map do |primary_key|
|
43
|
+
if primary_key[:pk] != 'id'
|
44
|
+
statement_parts = [('add_primary_key ' + table_name.inspect)]
|
45
|
+
statement_parts << (':column => ' + primary_key[:pk].inspect)
|
46
|
+
' ' + statement_parts.join(', ')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
stream.puts add_primary_keys_statement.sort.join("\n")
|
50
|
+
stream.puts
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def foreign_keys(table_name, stream)
|
56
|
+
if (foreign_keys = @connection.foreign_keys(table_name)).any?
|
57
|
+
add_foreign_key_statements = foreign_keys.map do |foreign_key|
|
58
|
+
statement_parts = [('add_foreign_key ' + foreign_key.from_table.inspect)]
|
59
|
+
statement_parts << foreign_key.to_table.inspect
|
60
|
+
statement_parts << (':name => ' + foreign_key.options[:name].inspect)
|
61
|
+
|
62
|
+
if foreign_key.options[:column] != "#{foreign_key.to_table.singularize}_id"
|
63
|
+
statement_parts << (':column => ' + foreign_key.options[:column].inspect)
|
64
|
+
end
|
65
|
+
if foreign_key.options[:primary_key] != 'id'
|
66
|
+
statement_parts << (':primary_key => ' + foreign_key.options[:primary_key].inspect)
|
67
|
+
end
|
68
|
+
if foreign_key.options[:dependent].present?
|
69
|
+
statement_parts << (':dependent => ' + foreign_key.options[:dependent].inspect)
|
70
|
+
end
|
71
|
+
|
72
|
+
' ' + statement_parts.join(', ')
|
73
|
+
end
|
74
|
+
|
75
|
+
stream.puts add_foreign_key_statements.sort.join("\n")
|
76
|
+
stream.puts
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'foreigner/connection_adapters/abstract/schema_statements'
|
2
|
+
require 'foreigner/connection_adapters/abstract/schema_definitions'
|
3
|
+
require 'foreigner/connection_adapters/sql_2003'
|
4
|
+
require 'foreigner/schema_dumper'
|
5
|
+
|
6
|
+
module Foreigner
|
7
|
+
class << self
|
8
|
+
def adapters
|
9
|
+
@@adapters ||= {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def register(adapter_name, file_name)
|
13
|
+
adapters[adapter_name] = file_name
|
14
|
+
end
|
15
|
+
|
16
|
+
def load_adapter!
|
17
|
+
ActiveRecord::ConnectionAdapters.module_eval do
|
18
|
+
include Foreigner::ConnectionAdapters::SchemaStatements
|
19
|
+
include Foreigner::ConnectionAdapters::SchemaDefinitions
|
20
|
+
end
|
21
|
+
|
22
|
+
ActiveRecord::SchemaDumper.class_eval do
|
23
|
+
include Foreigner::SchemaDumper
|
24
|
+
end
|
25
|
+
|
26
|
+
if adapters.key?(configured_adapter)
|
27
|
+
require adapters[configured_adapter]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def configured_adapter
|
32
|
+
ActiveRecord::Base.connection_pool.spec.config[:adapter]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
Foreigner.register 'postgresql', 'foreigner/connection_adapters/postgresql_adapter'
|
38
|
+
Foreigner.register 'sqlserver', 'foreigner/connection_adapters/sqlserver_adapter'
|
39
|
+
|
40
|
+
if defined?(Rails::Railtie)
|
41
|
+
module Foreigner
|
42
|
+
class Railtie < Rails::Railtie
|
43
|
+
initializer 'foreigner.load_adapter' do
|
44
|
+
ActiveSupport.on_load :active_record do
|
45
|
+
Foreigner.load_adapter!
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
else
|
51
|
+
Foreigner.load_adapter!
|
52
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'foreigner/connection_adapters/mysql_adapter'
|
3
|
+
|
4
|
+
class MysqlAdapterTest < ActiveRecord::TestCase
|
5
|
+
include Foreigner::ConnectionAdapters::MysqlAdapter
|
6
|
+
|
7
|
+
def test_add_without_options
|
8
|
+
assert_equal(
|
9
|
+
"ALTER TABLE `employees` ADD CONSTRAINT `employees_company_id_fk` FOREIGN KEY (`company_id`) REFERENCES `companies`(id)",
|
10
|
+
add_foreign_key(:employees, :companies)
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_add_with_name
|
15
|
+
assert_equal(
|
16
|
+
"ALTER TABLE `employees` ADD CONSTRAINT `favorite_company_fk` FOREIGN KEY (`company_id`) REFERENCES `companies`(id)",
|
17
|
+
add_foreign_key(:employees, :companies, :name => 'favorite_company_fk')
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_add_with_column
|
22
|
+
assert_equal(
|
23
|
+
"ALTER TABLE `employees` ADD CONSTRAINT `employees_last_employer_id_fk` FOREIGN KEY (`last_employer_id`) REFERENCES `companies`(id)",
|
24
|
+
add_foreign_key(:employees, :companies, :column => 'last_employer_id')
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_add_with_column_and_name
|
29
|
+
assert_equal(
|
30
|
+
"ALTER TABLE `employees` ADD CONSTRAINT `favorite_company_fk` FOREIGN KEY (`last_employer_id`) REFERENCES `companies`(id)",
|
31
|
+
add_foreign_key(:employees, :companies, :column => 'last_employer_id', :name => 'favorite_company_fk')
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_add_with_delete_dependency
|
36
|
+
assert_equal(
|
37
|
+
"ALTER TABLE `employees` ADD CONSTRAINT `employees_company_id_fk` FOREIGN KEY (`company_id`) REFERENCES `companies`(id) " +
|
38
|
+
"ON DELETE CASCADE",
|
39
|
+
add_foreign_key(:employees, :companies, :dependent => :delete)
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_add_with_nullify_dependency
|
44
|
+
assert_equal(
|
45
|
+
"ALTER TABLE `employees` ADD CONSTRAINT `employees_company_id_fk` FOREIGN KEY (`company_id`) REFERENCES `companies`(id) " +
|
46
|
+
"ON DELETE SET NULL",
|
47
|
+
add_foreign_key(:employees, :companies, :dependent => :nullify)
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_add_with_restrict_dependency
|
52
|
+
assert_equal(
|
53
|
+
"ALTER TABLE `employees` ADD CONSTRAINT `employees_company_id_fk` FOREIGN KEY (`company_id`) REFERENCES `companies`(id) " +
|
54
|
+
"ON DELETE RESTRICT",
|
55
|
+
add_foreign_key(:employees, :companies, :dependent => :restrict)
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_add_with_options
|
60
|
+
assert_equal(
|
61
|
+
"ALTER TABLE `employees` ADD CONSTRAINT `employees_company_id_fk` FOREIGN KEY (`company_id`) REFERENCES `companies`(id) " +
|
62
|
+
"on delete foo",
|
63
|
+
add_foreign_key(:employees, :companies, :options => 'on delete foo')
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_remove_by_table
|
68
|
+
assert_equal(
|
69
|
+
"ALTER TABLE `suppliers` DROP FOREIGN KEY `suppliers_company_id_fk`",
|
70
|
+
remove_foreign_key(:suppliers, :companies)
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_remove_by_name
|
75
|
+
assert_equal(
|
76
|
+
"ALTER TABLE `suppliers` DROP FOREIGN KEY `belongs_to_supplier`",
|
77
|
+
remove_foreign_key(:suppliers, :name => "belongs_to_supplier")
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_remove_by_column
|
82
|
+
assert_equal(
|
83
|
+
"ALTER TABLE `suppliers` DROP FOREIGN KEY `suppliers_ship_to_id_fk`",
|
84
|
+
remove_foreign_key(:suppliers, :column => "ship_to_id")
|
85
|
+
)
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
def execute(sql, name = nil)
|
90
|
+
sql
|
91
|
+
end
|
92
|
+
|
93
|
+
def quote_column_name(name)
|
94
|
+
"`#{name}`"
|
95
|
+
end
|
96
|
+
|
97
|
+
def quote_table_name(name)
|
98
|
+
quote_column_name(name).gsub('.', '`.`')
|
99
|
+
end
|
100
|
+
end
|
metadata
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: frac-foreigner
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Cloned by Matthew Higgins
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-04-05 00:00:00.000000000Z
|
13
|
+
dependencies: []
|
14
|
+
description: ''
|
15
|
+
email: ''
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files:
|
19
|
+
- README.rdoc
|
20
|
+
files:
|
21
|
+
- MIT-LICENSE
|
22
|
+
- Rakefile
|
23
|
+
- README.rdoc
|
24
|
+
- lib/foreigner/schema_dumper.rb
|
25
|
+
- lib/foreigner/connection_adapters/sqlserver_adapter.rb
|
26
|
+
- lib/foreigner/connection_adapters/sql_2003.rb
|
27
|
+
- lib/foreigner/connection_adapters/postgresql_adapter.rb
|
28
|
+
- lib/foreigner/connection_adapters/abstract/schema_definitions.rb
|
29
|
+
- lib/foreigner/connection_adapters/abstract/schema_statements.rb
|
30
|
+
- lib/frac-foreigner.rb
|
31
|
+
- test/mysql_adapter_test.rb
|
32
|
+
- test/helper.rb
|
33
|
+
homepage: ''
|
34
|
+
licenses: []
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options: []
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: 1.8.6
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
|
+
none: false
|
47
|
+
requirements:
|
48
|
+
- - ! '>='
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: 1.3.5
|
51
|
+
requirements: []
|
52
|
+
rubyforge_project: frac-foreigner
|
53
|
+
rubygems_version: 1.7.1
|
54
|
+
signing_key:
|
55
|
+
specification_version: 3
|
56
|
+
summary: Foreigner adapted to SQLServer and with (partial) natural pk support
|
57
|
+
test_files: []
|