foreign_key_saver 2.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.
- data/.gitignore +1 -0
- data/MIT-LICENSE +20 -0
- data/README +36 -0
- data/Rakefile +12 -0
- data/foreign_key_saver.gemspec +58 -0
- data/init.rb +1 -0
- data/lib/foreign_key_saver.rb +3 -0
- data/lib/foreign_key_saver/foreign_key_saver_patches.rb +202 -0
- data/lib/foreign_key_saver/version.rb +3 -0
- data/test/database.yml +11 -0
- data/test/foreign_key_constraints_test.rb +226 -0
- data/test/test_helper.rb +29 -0
- metadata +181 -0
data/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
test/log/*
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2007-2008 Will Bryant, Sekuda Ltd
|
|
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
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
ForeignKeySaver
|
|
2
|
+
===============
|
|
3
|
+
|
|
4
|
+
This plugin adds an add_foreign_key_constraint schema method, and extends the
|
|
5
|
+
schema dump code to output these foreign key constraints.
|
|
6
|
+
|
|
7
|
+
Only MySQL and PostgreSQL are currently supported.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
Examples
|
|
11
|
+
========
|
|
12
|
+
|
|
13
|
+
# adds a constraint on projects.customer_id with parent customers.id
|
|
14
|
+
add_foreign_key :projects, :customer_id, :customers, :id
|
|
15
|
+
|
|
16
|
+
# adds a constraint on projects(a, b) with parent(a, b) with the default RESTRICT update/delete actions
|
|
17
|
+
add_foreign_key "child", ["a", "b"], "parent", ["a", "b"]
|
|
18
|
+
|
|
19
|
+
# adds a constraint with the ON UPDATE action set to CASCADE and the ON DELETE action set to SET NULL
|
|
20
|
+
add_foreign_key 'projects', 'customer_id', 'customers', 'id', :on_update => :cascade, :on_delete => :set_null
|
|
21
|
+
|
|
22
|
+
The following actions are defined:
|
|
23
|
+
:restrict
|
|
24
|
+
:no_action
|
|
25
|
+
:cascade
|
|
26
|
+
:set_null (aka :nullify)
|
|
27
|
+
:set_default
|
|
28
|
+
Note that MySQL does not support :set_default, and also treats :no_action as :restrict.
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
Compatibility
|
|
32
|
+
=============
|
|
33
|
+
|
|
34
|
+
Supports mysql, mysql2, postgresql.
|
|
35
|
+
|
|
36
|
+
Currently tested against Rails 3.2.13 on 2.0.0p0 and Rails 3.2.13, 3.1.8, 3.0.17, and 2.3.14 on Ruby 1.8.7.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
require 'rake'
|
|
2
|
+
require 'rake/testtask'
|
|
3
|
+
|
|
4
|
+
desc 'Default: run unit tests.'
|
|
5
|
+
task :default => :test
|
|
6
|
+
|
|
7
|
+
desc 'Test the foreign_key_constraints plugin.'
|
|
8
|
+
Rake::TestTask.new(:test) do |t|
|
|
9
|
+
t.libs << 'lib'
|
|
10
|
+
t.pattern = 'test/**/*_test.rb'
|
|
11
|
+
t.verbose = true
|
|
12
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
require File.expand_path('../lib/foreign_key_saver/version', __FILE__)
|
|
3
|
+
|
|
4
|
+
spec = Gem::Specification.new do |gem|
|
|
5
|
+
gem.name = 'foreign_key_saver'
|
|
6
|
+
gem.version = ForeignKeySaver::VERSION
|
|
7
|
+
gem.summary = "Adds support for foreign key constraints to ActiveRecord schema operations."
|
|
8
|
+
gem.description = <<-EOF
|
|
9
|
+
Adds a add_foreign_key_constraint schema method, and extends the schema dump code to output these
|
|
10
|
+
foreign key constraints.
|
|
11
|
+
|
|
12
|
+
Only MySQL and PostgreSQL are currently supported.
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
Examples
|
|
16
|
+
========
|
|
17
|
+
|
|
18
|
+
# adds a constraint on projects.customer_id with parent customers.id
|
|
19
|
+
add_foreign_key :projects, :customer_id, :customers, :id
|
|
20
|
+
|
|
21
|
+
# adds a constraint on projects(a, b) with parent(a, b) with the default RESTRICT update/delete actions
|
|
22
|
+
add_foreign_key "child", ["a", "b"], "parent", ["a", "b"]
|
|
23
|
+
|
|
24
|
+
# adds a constraint with the ON UPDATE action set to CASCADE and the ON DELETE action set to SET NULL
|
|
25
|
+
add_foreign_key 'projects', 'customer_id', 'customers', 'id', :on_update => :cascade, :on_delete => :set_null
|
|
26
|
+
|
|
27
|
+
The following actions are defined:
|
|
28
|
+
:restrict
|
|
29
|
+
:no_action
|
|
30
|
+
:cascade
|
|
31
|
+
:set_null (aka :nullify)
|
|
32
|
+
:set_default
|
|
33
|
+
Note that MySQL does not support :set_default, and also treats :no_action as :restrict.
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
Compatibility
|
|
37
|
+
=============
|
|
38
|
+
|
|
39
|
+
Supports mysql, mysql2, postgresql.
|
|
40
|
+
|
|
41
|
+
Currently tested against Rails 3.2.13 on 2.0.0p0 and Rails 3.2.13, 3.1.8, 3.0.17, and 2.3.14 on Ruby 1.8.7.
|
|
42
|
+
EOF
|
|
43
|
+
gem.has_rdoc = false
|
|
44
|
+
gem.author = "Will Bryant"
|
|
45
|
+
gem.email = "will.bryant@gmail.com"
|
|
46
|
+
gem.homepage = "http://github.com/willbryant/foreign_key_saver"
|
|
47
|
+
|
|
48
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
49
|
+
gem.files = `git ls-files`.split("\n")
|
|
50
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
51
|
+
gem.require_path = "lib"
|
|
52
|
+
|
|
53
|
+
gem.add_dependency "activerecord"
|
|
54
|
+
gem.add_development_dependency "rake"
|
|
55
|
+
gem.add_development_dependency "mysql"
|
|
56
|
+
gem.add_development_dependency "mysql2"
|
|
57
|
+
gem.add_development_dependency "pg"
|
|
58
|
+
end
|
data/init.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'foreign_key_saver/foreign_key_saver_patches'
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
require 'active_record'
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
class ForeignKeyConstraint
|
|
5
|
+
ACTIONS = { :restrict => 'RESTRICT', :no_action => 'NO ACTION', :cascade => 'CASCADE',
|
|
6
|
+
:set_null => 'SET NULL', :set_default => 'SET DEFAULT' }.freeze
|
|
7
|
+
|
|
8
|
+
attr_accessor :name, :referenced_table
|
|
9
|
+
attr_reader :key, :referenced_key, :update_action, :delete_action
|
|
10
|
+
|
|
11
|
+
def initialize(name, key, referenced_table, referenced_key, update_action = nil, delete_action = nil)
|
|
12
|
+
self.name = name
|
|
13
|
+
self.key = key
|
|
14
|
+
self.referenced_table = referenced_table
|
|
15
|
+
self.referenced_key = referenced_key
|
|
16
|
+
self.update_action = update_action
|
|
17
|
+
self.delete_action = delete_action
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def key=(columns)
|
|
21
|
+
@key = columns.is_a?(Enumerable) && columns.length == 1 ? columns.first : columns
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def referenced_key=(columns)
|
|
25
|
+
@referenced_key = columns.is_a?(Enumerable) && columns.length == 1 ? columns.first : columns
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def update_action=(action)
|
|
29
|
+
@update_action = ACTIONS.invert[action] || action || :restrict
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def delete_action=(action)
|
|
33
|
+
@delete_action = ACTIONS.invert[action] || action || :restrict
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def ==(other)
|
|
37
|
+
name == other.name && key == other.key &&
|
|
38
|
+
referenced_table == other.referenced_table && referenced_key == other.referenced_key &&
|
|
39
|
+
update_action == other.update_action && delete_action == other.delete_action
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def quote_constraint_action(action)
|
|
43
|
+
ACTIONS[action.to_sym] || action.to_s
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def to_sql(connection)
|
|
47
|
+
(name.blank? ? "" : "CONSTRAINT #{connection.quote_column_name(name)} ") +
|
|
48
|
+
"FOREIGN KEY (#{connection.quote_column_names(key)}) " +
|
|
49
|
+
"REFERENCES #{connection.quote_table_name(referenced_table)} (#{connection.quote_column_names(referenced_key)}) " +
|
|
50
|
+
"ON UPDATE #{quote_constraint_action(update_action)} " +
|
|
51
|
+
"ON DELETE #{quote_constraint_action(delete_action)}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def to_dump
|
|
55
|
+
dump = "#{key.inspect}, #{referenced_table.inspect}, #{referenced_key.inspect}"
|
|
56
|
+
dump << ", :name => #{name.inspect}" unless name.blank?
|
|
57
|
+
dump << ", :on_update => #{update_action.inspect}" if update_action != :restrict
|
|
58
|
+
dump << ", :on_delete => #{delete_action.inspect}" if delete_action != :restrict
|
|
59
|
+
dump
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def to_s
|
|
63
|
+
name
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.constraint_action_from_sql(action)
|
|
67
|
+
ACTIONS.invert[action] || action
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
module ConnectionAdapters
|
|
72
|
+
module SchemaStatements
|
|
73
|
+
VALID_FOREIGN_KEY_OPTIONS = [:name, :on_update, :on_delete]
|
|
74
|
+
|
|
75
|
+
def add_foreign_key_constraint(table_name, key, referenced_table, referenced_key, options = {})
|
|
76
|
+
options.assert_valid_keys(VALID_FOREIGN_KEY_OPTIONS)
|
|
77
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} ADD #{ForeignKeyConstraint.new(options[:name], key, referenced_table, referenced_key, options[:on_update], options[:on_delete]).to_sql(self)}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def remove_foreign_key_constraint(table_name, constraint)
|
|
81
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(constraint)}"
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
class AbstractAdapter
|
|
86
|
+
def quote_column_names(*column_names)
|
|
87
|
+
column_names.flatten.map {|column_name| quote_column_name(column_name)} * ', '
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def drop_table_with_foreign_keys(table_name, options = {})
|
|
91
|
+
remove_foreign_key_constraints_referencing(table_name) if tables.include?(table_name) # the if is an optimization for the sake of create_table with :force => true, which is crucial for db:test:prepare performance on mysql as mysql's INFORMATION_SCHEMA implementation is excrutiatingly slow; it's not needed for postgres, but does give a small boost to performance there too
|
|
92
|
+
drop_table_without_foreign_keys(table_name, options)
|
|
93
|
+
end
|
|
94
|
+
alias_method_chain :drop_table, :foreign_keys
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
module MysqlAdapterForeignKeyMethods # common to mysql & mysql2
|
|
98
|
+
def remove_foreign_key_constraint(table_name, constraint)
|
|
99
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} DROP FOREIGN KEY #{quote_column_name(constraint)}"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def remove_foreign_key_constraints_referencing(table_name)
|
|
103
|
+
select_rows(
|
|
104
|
+
"SELECT DISTINCT TABLE_NAME, CONSTRAINT_NAME" +
|
|
105
|
+
" FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE" +
|
|
106
|
+
" WHERE REFERENCED_TABLE_SCHEMA = SCHEMA()" +
|
|
107
|
+
" AND REFERENCED_TABLE_NAME = #{quote(table_name)}").each do |table_name, constraint_name|
|
|
108
|
+
remove_foreign_key_constraint(table_name, constraint_name)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def foreign_key_constraints_on(table_name)
|
|
113
|
+
self.class.constraints_from_sql(select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"])
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def self.constraints_from_sql(create_table_sql)
|
|
117
|
+
# the clauses look like this: CONSTRAINT `ab` FOREIGN KEY (`ac`, `bc`) REFERENCES `parent` (`a`, `b`) ON DELETE SET NULL ON UPDATE CASCADE
|
|
118
|
+
create_table_sql.scan(/CONSTRAINT `([^`]+)` FOREIGN KEY \((`(?:[^`]+)`(?:, `(?:[^`]+)`)*)\) REFERENCES `([^`]+)` \((`(?:[^`]+)`(?:, `(?:[^`]+)`)*)\)(?: ON DELETE (CASCADE|RESTRICT|NO ACTION|SET NULL|SET DEFAULT))?(?: ON UPDATE (CASCADE|RESTRICT|NO ACTION|SET NULL|SET DEFAULT))?/).collect do |capture|
|
|
119
|
+
ForeignKeyConstraint.new(capture[0], columns_from_sql(capture[1]), capture[2], columns_from_sql(capture[3]), capture[5], capture[4])
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def self.columns_from_sql(column_list_sql)
|
|
124
|
+
column_list_sql.scan(/`([^`]+)`/).collect(&:first)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
if const_defined?(:MysqlAdapter)
|
|
129
|
+
class MysqlAdapter
|
|
130
|
+
include MysqlAdapterForeignKeyMethods
|
|
131
|
+
|
|
132
|
+
def self.constraints_from_sql(create_table_sql)
|
|
133
|
+
MysqlAdapterForeignKeyMethods.constraints_from_sql(create_table_sql)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def self.columns_from_sql(column_list_sql)
|
|
137
|
+
MysqlAdapterForeignKeyMethods.columns_from_sql(column_list_sql)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
if const_defined?(:Mysql2Adapter)
|
|
143
|
+
class Mysql2Adapter
|
|
144
|
+
include MysqlAdapterForeignKeyMethods
|
|
145
|
+
|
|
146
|
+
def self.constraints_from_sql(create_table_sql)
|
|
147
|
+
MysqlAdapterForeignKeyMethods.constraints_from_sql(create_table_sql)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def self.columns_from_sql(column_list_sql)
|
|
151
|
+
MysqlAdapterForeignKeyMethods.columns_from_sql(column_list_sql)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
if const_defined?(:PostgreSQLAdapter)
|
|
157
|
+
class PostgreSQLAdapter
|
|
158
|
+
def remove_foreign_key_constraints_referencing(table_name)
|
|
159
|
+
select_rows(
|
|
160
|
+
"SELECT referenced.relname, pg_constraint.conname" +
|
|
161
|
+
" FROM pg_constraint, pg_class, pg_class referenced" +
|
|
162
|
+
" WHERE pg_constraint.confrelid = pg_class.oid" +
|
|
163
|
+
" AND pg_class.relname = #{quote(table_name)}" +
|
|
164
|
+
" AND referenced.oid = pg_constraint.conrelid").each do |table_name, constraint_name|
|
|
165
|
+
remove_foreign_key_constraint(table_name, constraint_name)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def foreign_key_constraints_on(table_name)
|
|
170
|
+
select_rows(
|
|
171
|
+
"SELECT pg_constraint.conname, pg_get_constraintdef(pg_constraint.oid)" +
|
|
172
|
+
" FROM pg_constraint, pg_class" +
|
|
173
|
+
" WHERE pg_constraint.conrelid = pg_class.oid" +
|
|
174
|
+
" AND pg_class.relname = #{quote(table_name)}").collect do |name, constraintdef|
|
|
175
|
+
self.class.foreign_key_from_sql(name, constraintdef)
|
|
176
|
+
end.compact
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def self.foreign_key_from_sql(name, foreign_key_sql)
|
|
180
|
+
# the clauses look like this: FOREIGN KEY (ac, bc) REFERENCES parent(ap, bp) ON UPDATE CASCADE ON DELETE SET NULL
|
|
181
|
+
capture = foreign_key_sql.match(/FOREIGN KEY \(((?:\w+)(?:, \w+)*)\) REFERENCES (\w+)\(((?:\w+)(?:, \w+)*)\)(?: ON UPDATE (CASCADE|RESTRICT|NO ACTION|SET NULL|SET DEFAULT))?(?: ON DELETE (CASCADE|RESTRICT|NO ACTION|SET NULL|SET DEFAULT))?/)
|
|
182
|
+
ForeignKeyConstraint.new(name, capture[1].split(', '), capture[2], capture[3].split(', '), capture[4], capture[5]) if capture
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
class SchemaDumper
|
|
189
|
+
def foreign_key_constraints_on(table_name, stream)
|
|
190
|
+
constraints = @connection.foreign_key_constraints_on(table_name)
|
|
191
|
+
constraints.each {|constraint| stream.puts " add_foreign_key_constraint #{table_name.inspect}, #{constraint.to_dump}"}
|
|
192
|
+
stream.puts unless constraints.empty?
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def tables_with_foreign_key_constraints(stream)
|
|
196
|
+
tables_without_foreign_key_constraints(stream)
|
|
197
|
+
@connection.tables.sort.each {|table| foreign_key_constraints_on(table, stream)}
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
alias_method_chain :tables, :foreign_key_constraints
|
|
201
|
+
end
|
|
202
|
+
end
|
data/test/database.yml
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
|
|
2
|
+
|
|
3
|
+
class ForeignKeyConstraintsTest < Test::Unit::TestCase
|
|
4
|
+
def schema_dump
|
|
5
|
+
stream = StringIO.new
|
|
6
|
+
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
|
|
7
|
+
stream.rewind
|
|
8
|
+
stream.read
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def schema(&block)
|
|
12
|
+
ActiveRecord::Schema.define(:version => 1, &block)
|
|
13
|
+
schema_dump
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def test_constraints_to_dump
|
|
17
|
+
assert_equal '"ac", "parent", "ap", :name => "cn"',
|
|
18
|
+
ActiveRecord::ForeignKeyConstraint.new('cn', 'ac', 'parent', 'ap').to_dump
|
|
19
|
+
|
|
20
|
+
assert_equal '["ac", "bc"], "parent", ["ap", "bp"], :name => "cn"',
|
|
21
|
+
ActiveRecord::ForeignKeyConstraint.new('cn', ['ac', 'bc'], 'parent', ['ap', 'bp']).to_dump
|
|
22
|
+
|
|
23
|
+
assert_equal '"ac", "parent", "ap", :name => "cn", :on_update => :cascade',
|
|
24
|
+
ActiveRecord::ForeignKeyConstraint.new('cn', 'ac', 'parent', 'ap', :cascade).to_dump
|
|
25
|
+
|
|
26
|
+
assert_equal '"ac", "parent", "ap", :name => "cn", :on_delete => :set_default',
|
|
27
|
+
ActiveRecord::ForeignKeyConstraint.new('cn', 'ac', 'parent', 'ap', nil, :set_default).to_dump
|
|
28
|
+
|
|
29
|
+
assert_equal '"ac", "parent", "ap", :name => "cn", :on_update => :set_null, :on_delete => :no_action',
|
|
30
|
+
ActiveRecord::ForeignKeyConstraint.new('cn', 'ac', 'parent', 'ap', :set_null, :no_action).to_dump
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
if ActiveRecord::Base.connection.class.name == 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter'
|
|
34
|
+
def test_postgresql_constraints_to_sql
|
|
35
|
+
assert_equal 'CONSTRAINT "cn" FOREIGN KEY ("ac") REFERENCES "parent" ("ap") ON UPDATE RESTRICT ON DELETE RESTRICT',
|
|
36
|
+
ActiveRecord::ForeignKeyConstraint.new('cn', 'ac', 'parent', 'ap').to_sql(ActiveRecord::Base.connection)
|
|
37
|
+
|
|
38
|
+
assert_equal 'CONSTRAINT "cn" FOREIGN KEY ("ac", "bc") REFERENCES "parent" ("ap", "bp") ON UPDATE RESTRICT ON DELETE RESTRICT',
|
|
39
|
+
ActiveRecord::ForeignKeyConstraint.new('cn', ['ac', 'bc'], 'parent', ['ap', 'bp']).to_sql(ActiveRecord::Base.connection)
|
|
40
|
+
|
|
41
|
+
assert_equal 'CONSTRAINT "cn" FOREIGN KEY ("ac") REFERENCES "parent" ("ap") ON UPDATE CASCADE ON DELETE RESTRICT',
|
|
42
|
+
ActiveRecord::ForeignKeyConstraint.new('cn', 'ac', 'parent', 'ap', :cascade).to_sql(ActiveRecord::Base.connection)
|
|
43
|
+
|
|
44
|
+
assert_equal 'CONSTRAINT "cn" FOREIGN KEY ("ac") REFERENCES "parent" ("ap") ON UPDATE RESTRICT ON DELETE SET DEFAULT',
|
|
45
|
+
ActiveRecord::ForeignKeyConstraint.new('cn', 'ac', 'parent', 'ap', nil, :set_default).to_sql(ActiveRecord::Base.connection)
|
|
46
|
+
|
|
47
|
+
assert_equal 'CONSTRAINT "cn" FOREIGN KEY ("ac") REFERENCES "parent" ("ap") ON UPDATE SET NULL ON DELETE NO ACTION',
|
|
48
|
+
ActiveRecord::ForeignKeyConstraint.new('cn', 'ac', 'parent', 'ap', :set_null, :no_action).to_sql(ActiveRecord::Base.connection)
|
|
49
|
+
end
|
|
50
|
+
else
|
|
51
|
+
def test_mysql_constraints_to_sql
|
|
52
|
+
assert_equal 'CONSTRAINT `cn` FOREIGN KEY (`ac`) REFERENCES `parent` (`ap`) ON UPDATE RESTRICT ON DELETE RESTRICT',
|
|
53
|
+
ActiveRecord::ForeignKeyConstraint.new('cn', 'ac', 'parent', 'ap').to_sql(ActiveRecord::Base.connection)
|
|
54
|
+
|
|
55
|
+
assert_equal 'CONSTRAINT `cn` FOREIGN KEY (`ac`, `bc`) REFERENCES `parent` (`ap`, `bp`) ON UPDATE RESTRICT ON DELETE RESTRICT',
|
|
56
|
+
ActiveRecord::ForeignKeyConstraint.new('cn', ['ac', 'bc'], 'parent', ['ap', 'bp']).to_sql(ActiveRecord::Base.connection)
|
|
57
|
+
|
|
58
|
+
assert_equal 'CONSTRAINT `cn` FOREIGN KEY (`ac`) REFERENCES `parent` (`ap`) ON UPDATE CASCADE ON DELETE RESTRICT',
|
|
59
|
+
ActiveRecord::ForeignKeyConstraint.new('cn', 'ac', 'parent', 'ap', :cascade).to_sql(ActiveRecord::Base.connection)
|
|
60
|
+
|
|
61
|
+
assert_equal 'CONSTRAINT `cn` FOREIGN KEY (`ac`) REFERENCES `parent` (`ap`) ON UPDATE RESTRICT ON DELETE SET DEFAULT',
|
|
62
|
+
ActiveRecord::ForeignKeyConstraint.new('cn', 'ac', 'parent', 'ap', nil, :set_default).to_sql(ActiveRecord::Base.connection)
|
|
63
|
+
|
|
64
|
+
assert_equal 'CONSTRAINT `cn` FOREIGN KEY (`ac`) REFERENCES `parent` (`ap`) ON UPDATE SET NULL ON DELETE NO ACTION',
|
|
65
|
+
ActiveRecord::ForeignKeyConstraint.new('cn', 'ac', 'parent', 'ap', :set_null, :no_action).to_sql(ActiveRecord::Base.connection)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def test_mysql_columns_from_sql
|
|
69
|
+
assert_equal ['abc'], ActiveRecord::Base.connection.class.columns_from_sql('`abc`')
|
|
70
|
+
assert_equal ['abc', 'def'], ActiveRecord::Base.connection.class.columns_from_sql('`abc`, `def`')
|
|
71
|
+
assert_equal ['abc', 'def', 'ghi'], ActiveRecord::Base.connection.class.columns_from_sql('`abc`, `def`, `ghi`')
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def test_mysql_constraints_from_sql
|
|
75
|
+
assert_equal [ActiveRecord::ForeignKeyConstraint.new('cn', 'ac', 'parent', 'ap')], ActiveRecord::Base.connection.class.
|
|
76
|
+
constraints_from_sql('CONSTRAINT `cn` FOREIGN KEY (`ac`) REFERENCES `parent` (`ap`)')
|
|
77
|
+
|
|
78
|
+
assert_equal [ActiveRecord::ForeignKeyConstraint.new('cn', ['ac', 'bc'], 'parent', ['ap', 'bp'])], ActiveRecord::Base.connection.class.
|
|
79
|
+
constraints_from_sql('CONSTRAINT `cn` FOREIGN KEY (`ac`, `bc`) REFERENCES `parent` (`ap`, `bp`)')
|
|
80
|
+
|
|
81
|
+
assert_equal [ActiveRecord::ForeignKeyConstraint.new('cn', 'ac', 'parent', 'ap', :cascade)], ActiveRecord::Base.connection.class.
|
|
82
|
+
constraints_from_sql('CONSTRAINT `cn` FOREIGN KEY (`ac`) REFERENCES `parent` (`ap`) ON UPDATE CASCADE')
|
|
83
|
+
|
|
84
|
+
assert_equal [ActiveRecord::ForeignKeyConstraint.new('cn', 'ac', 'parent', 'ap', nil, :set_default)], ActiveRecord::Base.connection.class.
|
|
85
|
+
constraints_from_sql('CONSTRAINT `cn` FOREIGN KEY (`ac`) REFERENCES `parent` (`ap`) ON DELETE SET DEFAULT')
|
|
86
|
+
|
|
87
|
+
assert_equal [ActiveRecord::ForeignKeyConstraint.new('cn', 'ac', 'parent', 'ap', :set_null, :no_action)], ActiveRecord::Base.connection.class.
|
|
88
|
+
constraints_from_sql('CONSTRAINT `cn` FOREIGN KEY (`ac`) REFERENCES `parent` (`ap`) ON DELETE NO ACTION ON UPDATE SET NULL') # mysql has update and delete kinda around the wrong way
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def test_fkc_define_roundtrip
|
|
93
|
+
dump = schema do
|
|
94
|
+
drop_table :child rescue nil
|
|
95
|
+
drop_table :parent rescue nil
|
|
96
|
+
create_table "parent" do end
|
|
97
|
+
create_table "child" do |t| t.integer :parent_id end
|
|
98
|
+
add_foreign_key_constraint "child", "parent_id", "parent", "id"
|
|
99
|
+
end
|
|
100
|
+
assert_match /create_table "parent"/, dump
|
|
101
|
+
assert_match /add_foreign_key_constraint "child", "parent_id", "parent", "id"/, dump
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def test_remove_fkc
|
|
105
|
+
dump = schema do
|
|
106
|
+
drop_table :child rescue nil
|
|
107
|
+
drop_table :parent rescue nil
|
|
108
|
+
create_table "parent" do end
|
|
109
|
+
create_table "child" do |t| t.integer :parent_id end
|
|
110
|
+
add_foreign_key_constraint "child", "parent_id", "parent", "id", :name => 'test_fk_name'
|
|
111
|
+
remove_foreign_key_constraint "child", "test_fk_name"
|
|
112
|
+
end
|
|
113
|
+
assert_no_match /add_foreign_key_constraint "child"/, dump
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def test_drop_parent_with_child_fkcs
|
|
117
|
+
dump = schema do
|
|
118
|
+
drop_table :child rescue nil
|
|
119
|
+
drop_table :parent rescue nil
|
|
120
|
+
create_table "parent" do end
|
|
121
|
+
create_table "child" do |t| t.integer :parent_id end
|
|
122
|
+
add_foreign_key_constraint "child", "parent_id", "parent", "id"
|
|
123
|
+
drop_table :parent
|
|
124
|
+
end
|
|
125
|
+
assert_no_match /create_table "parent"/, dump
|
|
126
|
+
assert_no_match /add_foreign_key_constraint "child", "parent_id", "parent", "id"/, dump
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def test_force_table_create
|
|
130
|
+
dump = schema do
|
|
131
|
+
drop_table :child rescue nil
|
|
132
|
+
drop_table :parent rescue nil
|
|
133
|
+
create_table "parent" do end
|
|
134
|
+
create_table "child" do |t| t.integer :parent_id end
|
|
135
|
+
add_foreign_key_constraint "child", "parent_id", "parent", "id"
|
|
136
|
+
create_table "parent", :force => true do end
|
|
137
|
+
create_table "child", :force => true do |t| t.integer :parent_id end
|
|
138
|
+
add_foreign_key_constraint "child", "parent_id", "parent", "id"
|
|
139
|
+
create_table "child", :force => true do |t| t.integer :parent_id end
|
|
140
|
+
create_table "parent", :force => true do end
|
|
141
|
+
add_foreign_key_constraint "child", "parent_id", "parent", "id"
|
|
142
|
+
end
|
|
143
|
+
assert_match /create_table "parent"/, dump
|
|
144
|
+
assert_match /add_foreign_key_constraint "child", "parent_id", "parent", "id"/, dump
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def test_fkc_composite_key
|
|
148
|
+
dump = schema do
|
|
149
|
+
create_table :parent, :force => true do |t| t.integer :afield end
|
|
150
|
+
add_index :parent, [:id, :afield], :unique => true
|
|
151
|
+
create_table :child, :force => true do |t| t.integer :parent_id, :afield end
|
|
152
|
+
add_foreign_key_constraint :child, [:parent_id, :afield], :parent, [:id, :afield]
|
|
153
|
+
end
|
|
154
|
+
assert_match /add_foreign_key_constraint "child", \["parent_id", "afield"\], "parent", \["id", "afield"\]/, dump
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def test_define_actions
|
|
158
|
+
dump = schema do
|
|
159
|
+
create_table "parent", :force => true do end
|
|
160
|
+
create_table "child", :force => true do |t| t.integer :parent_id end
|
|
161
|
+
add_foreign_key_constraint "child", "parent_id", "parent", "id", :on_update => :cascade, :on_delete => :set_null
|
|
162
|
+
end
|
|
163
|
+
assert_match /add_foreign_key_constraint "child", "parent_id", "parent", "id", :name => "\w+", :on_update => :cascade, :on_delete => :set_null/, dump
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def test_no_extraneous_actions_in_dump
|
|
167
|
+
dump = schema do
|
|
168
|
+
create_table "parent", :force => true do end
|
|
169
|
+
create_table "child", :force => true do |t| t.integer :parent_id end
|
|
170
|
+
add_foreign_key_constraint "child", "parent_id", "parent", "id", :on_delete => :restrict
|
|
171
|
+
end
|
|
172
|
+
assert_no_match /add_foreign_key_constraint "child".*on_update/, dump
|
|
173
|
+
assert_no_match /add_foreign_key_constraint "child".*on_delete/, dump
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def test_explicit_names
|
|
177
|
+
dump = schema do
|
|
178
|
+
create_table "parent", :force => true do end
|
|
179
|
+
create_table "child", :force => true do |t| t.integer :parent_id end
|
|
180
|
+
add_foreign_key_constraint "child", "parent_id", "parent", "id", :name => 'test_fk_name'
|
|
181
|
+
end
|
|
182
|
+
assert_match /add_foreign_key_constraint "child", "parent_id", "parent", "id", :name => "test_fk_name"/, dump
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def test_multiple_parents
|
|
186
|
+
dump = schema do
|
|
187
|
+
create_table "parent1", :force => true do end
|
|
188
|
+
create_table "parent2", :force => true do end
|
|
189
|
+
create_table "child", :force => true do |t| t.integer :parent1_id, :parent2_id end
|
|
190
|
+
add_foreign_key_constraint "child", "parent1_id", "parent1", "id"
|
|
191
|
+
add_foreign_key_constraint "child", "parent2_id", "parent2", "id"
|
|
192
|
+
end
|
|
193
|
+
assert_match /add_foreign_key_constraint "child", "parent1_id", "parent1", "id"/, dump
|
|
194
|
+
assert_match /add_foreign_key_constraint "child", "parent2_id", "parent2", "id"/, dump
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def test_multiple_children
|
|
198
|
+
dump = schema do
|
|
199
|
+
create_table "parent", :force => true do end
|
|
200
|
+
create_table "child1", :force => true do |t| t.integer :parent_id end
|
|
201
|
+
create_table "child2", :force => true do |t| t.integer :parent_id end
|
|
202
|
+
add_foreign_key_constraint "child1", "parent_id", "parent", "id"
|
|
203
|
+
add_foreign_key_constraint "child2", "parent_id", "parent", "id"
|
|
204
|
+
end
|
|
205
|
+
assert_match /add_foreign_key_constraint "child1", "parent_id", "parent", "id"/, dump
|
|
206
|
+
assert_match /add_foreign_key_constraint "child2", "parent_id", "parent", "id"/, dump
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def test_remove_only_named
|
|
210
|
+
dump = schema do
|
|
211
|
+
create_table "parent1", :force => true do end
|
|
212
|
+
create_table "parent2", :force => true do end
|
|
213
|
+
create_table "child1", :force => true do |t| t.integer :parent1_id, :parent2_id end
|
|
214
|
+
create_table "child2", :force => true do |t| t.integer :parent1_id, :parent2_id end
|
|
215
|
+
add_foreign_key_constraint "child1", "parent1_id", "parent1", "id", :name => "test_a"
|
|
216
|
+
add_foreign_key_constraint "child1", "parent2_id", "parent2", "id", :name => "test_b"
|
|
217
|
+
add_foreign_key_constraint "child2", "parent1_id", "parent1", "id", :name => "test_c"
|
|
218
|
+
add_foreign_key_constraint "child2", "parent2_id", "parent2", "id", :name => "test_d"
|
|
219
|
+
remove_foreign_key_constraint :child1, :test_b
|
|
220
|
+
end
|
|
221
|
+
assert_match /add_foreign_key_constraint "child1", "parent1_id", "parent1", "id", :name => "test_a"/, dump
|
|
222
|
+
assert_no_match /add_foreign_key_constraint "child1", "parent2_id", "parent2", "id", :name => "test_b"/, dump
|
|
223
|
+
assert_match /add_foreign_key_constraint "child2", "parent1_id", "parent1", "id", :name => "test_c"/, dump
|
|
224
|
+
assert_match /add_foreign_key_constraint "child2", "parent2_id", "parent2", "id", :name => "test_d"/, dump
|
|
225
|
+
end
|
|
226
|
+
end
|
data/test/test_helper.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
RAILS_ROOT = File.expand_path("../../..")
|
|
2
|
+
if File.exist?("#{RAILS_ROOT}/config/boot.rb")
|
|
3
|
+
require "#{RAILS_ROOT}/config/boot.rb"
|
|
4
|
+
else
|
|
5
|
+
require 'rubygems'
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
puts "Rails: #{ENV['RAILS_VERSION'] || 'default'}"
|
|
9
|
+
gem 'activesupport', ENV['RAILS_VERSION']
|
|
10
|
+
gem 'activerecord', ENV['RAILS_VERSION']
|
|
11
|
+
|
|
12
|
+
require 'test/unit'
|
|
13
|
+
require 'active_support'
|
|
14
|
+
require 'active_support/test_case'
|
|
15
|
+
require 'active_record'
|
|
16
|
+
|
|
17
|
+
begin
|
|
18
|
+
require 'ruby-debug'
|
|
19
|
+
Debugger.start
|
|
20
|
+
rescue LoadError
|
|
21
|
+
# ruby-debug not installed, no debugging for you
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
ActiveRecord::Base.configurations = YAML::load(IO.read(File.join(File.dirname(__FILE__), "database.yml")))
|
|
25
|
+
configuration = ActiveRecord::Base.configurations[ENV['RAILS_ENV']]
|
|
26
|
+
raise "use RAILS_ENV=#{ActiveRecord::Base.configurations.keys.sort.join '/'} to test this plugin" unless configuration
|
|
27
|
+
ActiveRecord::Base.establish_connection configuration
|
|
28
|
+
|
|
29
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '../init')) # load foreign_key_constraints
|
metadata
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: foreign_key_saver
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
hash: 15
|
|
5
|
+
prerelease:
|
|
6
|
+
segments:
|
|
7
|
+
- 2
|
|
8
|
+
- 0
|
|
9
|
+
- 0
|
|
10
|
+
version: 2.0.0
|
|
11
|
+
platform: ruby
|
|
12
|
+
authors:
|
|
13
|
+
- Will Bryant
|
|
14
|
+
autorequire:
|
|
15
|
+
bindir: bin
|
|
16
|
+
cert_chain: []
|
|
17
|
+
|
|
18
|
+
date: 2013-04-21 00:00:00 Z
|
|
19
|
+
dependencies:
|
|
20
|
+
- !ruby/object:Gem::Dependency
|
|
21
|
+
name: activerecord
|
|
22
|
+
prerelease: false
|
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
|
24
|
+
none: false
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
hash: 3
|
|
29
|
+
segments:
|
|
30
|
+
- 0
|
|
31
|
+
version: "0"
|
|
32
|
+
type: :runtime
|
|
33
|
+
version_requirements: *id001
|
|
34
|
+
- !ruby/object:Gem::Dependency
|
|
35
|
+
name: rake
|
|
36
|
+
prerelease: false
|
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
|
38
|
+
none: false
|
|
39
|
+
requirements:
|
|
40
|
+
- - ">="
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
hash: 3
|
|
43
|
+
segments:
|
|
44
|
+
- 0
|
|
45
|
+
version: "0"
|
|
46
|
+
type: :development
|
|
47
|
+
version_requirements: *id002
|
|
48
|
+
- !ruby/object:Gem::Dependency
|
|
49
|
+
name: mysql
|
|
50
|
+
prerelease: false
|
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
|
52
|
+
none: false
|
|
53
|
+
requirements:
|
|
54
|
+
- - ">="
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
hash: 3
|
|
57
|
+
segments:
|
|
58
|
+
- 0
|
|
59
|
+
version: "0"
|
|
60
|
+
type: :development
|
|
61
|
+
version_requirements: *id003
|
|
62
|
+
- !ruby/object:Gem::Dependency
|
|
63
|
+
name: mysql2
|
|
64
|
+
prerelease: false
|
|
65
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
|
66
|
+
none: false
|
|
67
|
+
requirements:
|
|
68
|
+
- - ">="
|
|
69
|
+
- !ruby/object:Gem::Version
|
|
70
|
+
hash: 3
|
|
71
|
+
segments:
|
|
72
|
+
- 0
|
|
73
|
+
version: "0"
|
|
74
|
+
type: :development
|
|
75
|
+
version_requirements: *id004
|
|
76
|
+
- !ruby/object:Gem::Dependency
|
|
77
|
+
name: pg
|
|
78
|
+
prerelease: false
|
|
79
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
|
80
|
+
none: false
|
|
81
|
+
requirements:
|
|
82
|
+
- - ">="
|
|
83
|
+
- !ruby/object:Gem::Version
|
|
84
|
+
hash: 3
|
|
85
|
+
segments:
|
|
86
|
+
- 0
|
|
87
|
+
version: "0"
|
|
88
|
+
type: :development
|
|
89
|
+
version_requirements: *id005
|
|
90
|
+
description: |
|
|
91
|
+
Adds a add_foreign_key_constraint schema method, and extends the schema dump code to output these
|
|
92
|
+
foreign key constraints.
|
|
93
|
+
|
|
94
|
+
Only MySQL and PostgreSQL are currently supported.
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
Examples
|
|
98
|
+
========
|
|
99
|
+
|
|
100
|
+
# adds a constraint on projects.customer_id with parent customers.id
|
|
101
|
+
add_foreign_key :projects, :customer_id, :customers, :id
|
|
102
|
+
|
|
103
|
+
# adds a constraint on projects(a, b) with parent(a, b) with the default RESTRICT update/delete actions
|
|
104
|
+
add_foreign_key "child", ["a", "b"], "parent", ["a", "b"]
|
|
105
|
+
|
|
106
|
+
# adds a constraint with the ON UPDATE action set to CASCADE and the ON DELETE action set to SET NULL
|
|
107
|
+
add_foreign_key 'projects', 'customer_id', 'customers', 'id', :on_update => :cascade, :on_delete => :set_null
|
|
108
|
+
|
|
109
|
+
The following actions are defined:
|
|
110
|
+
:restrict
|
|
111
|
+
:no_action
|
|
112
|
+
:cascade
|
|
113
|
+
:set_null (aka :nullify)
|
|
114
|
+
:set_default
|
|
115
|
+
Note that MySQL does not support :set_default, and also treats :no_action as :restrict.
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
Compatibility
|
|
119
|
+
=============
|
|
120
|
+
|
|
121
|
+
Supports mysql, mysql2, postgresql.
|
|
122
|
+
|
|
123
|
+
Currently tested against Rails 3.2.13 on 2.0.0p0 and Rails 3.2.13, 3.1.8, 3.0.17, and 2.3.14 on Ruby 1.8.7.
|
|
124
|
+
|
|
125
|
+
email: will.bryant@gmail.com
|
|
126
|
+
executables: []
|
|
127
|
+
|
|
128
|
+
extensions: []
|
|
129
|
+
|
|
130
|
+
extra_rdoc_files: []
|
|
131
|
+
|
|
132
|
+
files:
|
|
133
|
+
- .gitignore
|
|
134
|
+
- MIT-LICENSE
|
|
135
|
+
- README
|
|
136
|
+
- Rakefile
|
|
137
|
+
- foreign_key_saver.gemspec
|
|
138
|
+
- init.rb
|
|
139
|
+
- lib/foreign_key_saver.rb
|
|
140
|
+
- lib/foreign_key_saver/foreign_key_saver_patches.rb
|
|
141
|
+
- lib/foreign_key_saver/version.rb
|
|
142
|
+
- test/database.yml
|
|
143
|
+
- test/foreign_key_constraints_test.rb
|
|
144
|
+
- test/test_helper.rb
|
|
145
|
+
homepage: http://github.com/willbryant/foreign_key_saver
|
|
146
|
+
licenses: []
|
|
147
|
+
|
|
148
|
+
post_install_message:
|
|
149
|
+
rdoc_options: []
|
|
150
|
+
|
|
151
|
+
require_paths:
|
|
152
|
+
- lib
|
|
153
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
154
|
+
none: false
|
|
155
|
+
requirements:
|
|
156
|
+
- - ">="
|
|
157
|
+
- !ruby/object:Gem::Version
|
|
158
|
+
hash: 3
|
|
159
|
+
segments:
|
|
160
|
+
- 0
|
|
161
|
+
version: "0"
|
|
162
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
163
|
+
none: false
|
|
164
|
+
requirements:
|
|
165
|
+
- - ">="
|
|
166
|
+
- !ruby/object:Gem::Version
|
|
167
|
+
hash: 3
|
|
168
|
+
segments:
|
|
169
|
+
- 0
|
|
170
|
+
version: "0"
|
|
171
|
+
requirements: []
|
|
172
|
+
|
|
173
|
+
rubyforge_project:
|
|
174
|
+
rubygems_version: 1.8.15
|
|
175
|
+
signing_key:
|
|
176
|
+
specification_version: 3
|
|
177
|
+
summary: Adds support for foreign key constraints to ActiveRecord schema operations.
|
|
178
|
+
test_files:
|
|
179
|
+
- test/database.yml
|
|
180
|
+
- test/foreign_key_constraints_test.rb
|
|
181
|
+
- test/test_helper.rb
|