schema_plus 2.0.0.pre15 → 2.0.0.pre16
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.
- checksums.yaml +4 -4
- data/README.md +6 -78
- data/lib/schema_plus.rb +1 -0
- data/lib/schema_plus/auto_foreign_keys.rb +31 -0
- data/lib/schema_plus/auto_foreign_keys/active_record/connection_adapters/sqlite3_adapter.rb +22 -0
- data/lib/schema_plus/auto_foreign_keys/active_record/migration/command_recorder.rb +14 -0
- data/lib/schema_plus/auto_foreign_keys/middleware/migration.rb +66 -0
- data/lib/schema_plus/{foreign_keys → auto_foreign_keys}/middleware/schema.rb +1 -1
- data/lib/schema_plus/version.rb +1 -1
- data/schema_plus.gemspec +2 -1
- data/spec/{schema_plus_foreign_keys → schema_auto_foreign_keys}/foreign_key_spec.rb +0 -0
- data/spec/{schema_plus_foreign_keys → schema_auto_foreign_keys}/migration_spec.rb +3 -2
- data/spec/{schema_plus_foreign_keys → schema_auto_foreign_keys}/schema_dumper_spec.rb +0 -0
- data/spec/{schema_plus_foreign_keys → schema_auto_foreign_keys}/schema_spec.rb +0 -0
- metadata +31 -32
- data/lib/schema_plus/foreign_keys.rb +0 -93
- data/lib/schema_plus/foreign_keys/active_record/base.rb +0 -33
- data/lib/schema_plus/foreign_keys/active_record/connection_adapters/abstract_adapter.rb +0 -168
- data/lib/schema_plus/foreign_keys/active_record/connection_adapters/foreign_key_definition.rb +0 -138
- data/lib/schema_plus/foreign_keys/active_record/connection_adapters/mysql2_adapter.rb +0 -126
- data/lib/schema_plus/foreign_keys/active_record/connection_adapters/postgresql_adapter.rb +0 -89
- data/lib/schema_plus/foreign_keys/active_record/connection_adapters/sqlite3_adapter.rb +0 -98
- data/lib/schema_plus/foreign_keys/active_record/connection_adapters/table_definition.rb +0 -108
- data/lib/schema_plus/foreign_keys/active_record/migration/command_recorder.rb +0 -35
- data/lib/schema_plus/foreign_keys/middleware/dumper.rb +0 -79
- data/lib/schema_plus/foreign_keys/middleware/migration.rb +0 -184
- data/lib/schema_plus/foreign_keys/middleware/model.rb +0 -15
- data/lib/schema_plus/foreign_keys/middleware/mysql.rb +0 -20
- data/lib/schema_plus/foreign_keys/middleware/sql.rb +0 -20
- data/lib/schema_plus/foreign_keys/version.rb +0 -3
- data/spec/schema_plus_foreign_keys/foreign_key_definition_spec.rb +0 -34
- data/spec/schema_plus_foreign_keys/named_schemas_spec.rb +0 -136
@@ -1,89 +0,0 @@
|
|
1
|
-
module SchemaPlus::ForeignKeys
|
2
|
-
module ActiveRecord
|
3
|
-
module ConnectionAdapters
|
4
|
-
|
5
|
-
# The Postgresql adapter implements the SchemaPlus::ForeignKeys extensions and
|
6
|
-
# enhancements
|
7
|
-
module PostgresqlAdapter
|
8
|
-
|
9
|
-
def rename_table(oldname, newname) #:nodoc:
|
10
|
-
super
|
11
|
-
rename_foreign_keys(oldname, newname)
|
12
|
-
end
|
13
|
-
|
14
|
-
def foreign_keys(table_name, name = nil) #:nodoc:
|
15
|
-
load_foreign_keys(<<-SQL, name)
|
16
|
-
SELECT f.conname, pg_get_constraintdef(f.oid), t.relname
|
17
|
-
FROM pg_class t, pg_constraint f
|
18
|
-
WHERE f.conrelid = t.oid
|
19
|
-
AND f.contype = 'f'
|
20
|
-
AND t.relname = '#{table_name_without_namespace(table_name)}'
|
21
|
-
AND t.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = #{namespace_sql(table_name)} )
|
22
|
-
SQL
|
23
|
-
end
|
24
|
-
|
25
|
-
def reverse_foreign_keys(table_name, name = nil) #:nodoc:
|
26
|
-
load_foreign_keys(<<-SQL, name)
|
27
|
-
SELECT f.conname, pg_get_constraintdef(f.oid), t2.relname
|
28
|
-
FROM pg_class t, pg_class t2, pg_constraint f
|
29
|
-
WHERE f.confrelid = t.oid
|
30
|
-
AND f.conrelid = t2.oid
|
31
|
-
AND f.contype = 'f'
|
32
|
-
AND t.relname = '#{table_name_without_namespace(table_name)}'
|
33
|
-
AND t.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = #{namespace_sql(table_name)} )
|
34
|
-
SQL
|
35
|
-
end
|
36
|
-
|
37
|
-
private
|
38
|
-
|
39
|
-
def unquote(name)
|
40
|
-
return name.map { |name| unquote(name) } if name.is_a?(Array)
|
41
|
-
name.sub(/^["`](.*)["`]$/, '\1')
|
42
|
-
end
|
43
|
-
|
44
|
-
def namespace_sql(table_name)
|
45
|
-
(table_name.to_s =~ /(.*)[.]/) ? "'#{$1}'" : "ANY (current_schemas(false))"
|
46
|
-
end
|
47
|
-
|
48
|
-
def table_name_without_namespace(table_name)
|
49
|
-
table_name.to_s.sub /.*[.]/, ''
|
50
|
-
end
|
51
|
-
|
52
|
-
def load_foreign_keys(sql, name = nil) #:nodoc:
|
53
|
-
foreign_keys = []
|
54
|
-
|
55
|
-
query(sql, name).each do |row|
|
56
|
-
if row[1] =~ /^FOREIGN KEY \((.+?)\) REFERENCES (.+?)\((.+?)\)( ON UPDATE (.+?))?( ON DELETE (.+?))?( (DEFERRABLE|NOT DEFERRABLE)( (INITIALLY DEFERRED|INITIALLY IMMEDIATE))?)?$/
|
57
|
-
name = row[0]
|
58
|
-
from_table = unquote(row[2])
|
59
|
-
columns = unquote($1.split(', '))
|
60
|
-
to_table = unquote($2)
|
61
|
-
primary_keys = unquote($3.split(', '))
|
62
|
-
on_update = $5
|
63
|
-
on_delete = $7
|
64
|
-
deferrable = $9 == "DEFERRABLE"
|
65
|
-
deferrable = :initially_deferred if ($11 == "INITIALLY DEFERRED" )
|
66
|
-
on_update = ForeignKeyDefinition::ACTION_LOOKUP[on_update] || :no_action
|
67
|
-
on_delete = ForeignKeyDefinition::ACTION_LOOKUP[on_delete] || :no_action
|
68
|
-
|
69
|
-
options = { :name => name,
|
70
|
-
:on_delete => on_delete,
|
71
|
-
:on_update => on_update,
|
72
|
-
:column => columns,
|
73
|
-
:primary_key => primary_keys,
|
74
|
-
:deferrable => deferrable }
|
75
|
-
|
76
|
-
foreign_keys << ::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(
|
77
|
-
from_table,
|
78
|
-
to_table.sub(/^"(.*)"$/, '\1'),
|
79
|
-
options)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
foreign_keys
|
84
|
-
end
|
85
|
-
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
@@ -1,98 +0,0 @@
|
|
1
|
-
module SchemaPlus::ForeignKeys
|
2
|
-
module ActiveRecord
|
3
|
-
module ConnectionAdapters
|
4
|
-
|
5
|
-
# SchemaPlus::ForeignKeys includes an Sqlite3 implementation of the AbstractAdapter
|
6
|
-
# extensions.
|
7
|
-
module Sqlite3Adapter
|
8
|
-
|
9
|
-
# :enddoc:
|
10
|
-
|
11
|
-
def initialize(*args)
|
12
|
-
super
|
13
|
-
execute('PRAGMA FOREIGN_KEYS = ON')
|
14
|
-
end
|
15
|
-
|
16
|
-
def rename_table(oldname, newname) #:nodoc:
|
17
|
-
super
|
18
|
-
rename_foreign_keys(oldname, newname)
|
19
|
-
end
|
20
|
-
|
21
|
-
def copy_table(*args, &block)
|
22
|
-
fk_override = { :auto_create => false, :auto_index => false }
|
23
|
-
save = Hash[fk_override.keys.collect{|key| [key, SchemaPlus::ForeignKeys.config.send(key)]}]
|
24
|
-
begin
|
25
|
-
SchemaPlus::ForeignKeys.config.update_attributes(fk_override)
|
26
|
-
super
|
27
|
-
ensure
|
28
|
-
SchemaPlus::ForeignKeys.config.update_attributes(save)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def add_foreign_key(table_name, to_table, options = {})
|
33
|
-
raise NotImplementedError, "Sqlite3 does not support altering a table to add foreign key constraints (table #{table_name.inspect} to #{to_table.inspect})"
|
34
|
-
end
|
35
|
-
|
36
|
-
def remove_foreign_key(table_name, *args)
|
37
|
-
raise NotImplementedError, "Sqlite3 does not support altering a table to remove foreign key constraints (table #{table_name.inspect} constraint #{args.inspect})"
|
38
|
-
end
|
39
|
-
|
40
|
-
def foreign_keys(table_name, name = nil)
|
41
|
-
get_foreign_keys(table_name, name)
|
42
|
-
end
|
43
|
-
|
44
|
-
def reverse_foreign_keys(table_name, name = nil)
|
45
|
-
get_foreign_keys(nil, name).select{|definition| definition.to_table == table_name}
|
46
|
-
end
|
47
|
-
|
48
|
-
protected
|
49
|
-
|
50
|
-
def get_foreign_keys(table_name = nil, name = nil)
|
51
|
-
results = execute(<<-SQL, name)
|
52
|
-
SELECT name, sql FROM sqlite_master
|
53
|
-
WHERE type='table' #{table_name && %" AND name='#{table_name}' "}
|
54
|
-
SQL
|
55
|
-
|
56
|
-
re = %r[
|
57
|
-
\b(CONSTRAINT\s+(\S+)\s+)?
|
58
|
-
FOREIGN\s+KEY\s* \(\s*[`"](.+?)[`"]\s*\)
|
59
|
-
\s*REFERENCES\s*[`"](.+?)[`"]\s*\((.+?)\)
|
60
|
-
(\s+ON\s+UPDATE\s+(.+?))?
|
61
|
-
(\s*ON\s+DELETE\s+(.+?))?
|
62
|
-
(\s*DEFERRABLE(\s+INITIALLY\s+DEFERRED)?)?
|
63
|
-
\s*[,)]
|
64
|
-
]x
|
65
|
-
|
66
|
-
foreign_keys = []
|
67
|
-
results.each do |row|
|
68
|
-
from_table = row["name"]
|
69
|
-
row["sql"].scan(re).each do |d0, name, columns, to_table, primary_keys, d1, on_update, d2, on_delete, deferrable, initially_deferred|
|
70
|
-
columns = columns.gsub(/`/, '').split(', ')
|
71
|
-
|
72
|
-
primary_keys = primary_keys.gsub(/[`"]/, '').split(', ')
|
73
|
-
on_update = ForeignKeyDefinition::ACTION_LOOKUP[on_update] || :no_action
|
74
|
-
on_delete = ForeignKeyDefinition::ACTION_LOOKUP[on_delete] || :no_action
|
75
|
-
deferrable = deferrable ? (initially_deferred ? :initially_deferred : true) : false
|
76
|
-
|
77
|
-
options = { :name => name,
|
78
|
-
:on_update => on_update,
|
79
|
-
:on_delete => on_delete,
|
80
|
-
:column => columns,
|
81
|
-
:primary_key => primary_keys,
|
82
|
-
:deferrable => deferrable }
|
83
|
-
|
84
|
-
foreign_keys << ::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(
|
85
|
-
from_table,
|
86
|
-
to_table,
|
87
|
-
options)
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
foreign_keys
|
92
|
-
end
|
93
|
-
|
94
|
-
end
|
95
|
-
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
@@ -1,108 +0,0 @@
|
|
1
|
-
module SchemaPlus::ForeignKeys::ActiveRecord::ConnectionAdapters
|
2
|
-
|
3
|
-
#
|
4
|
-
# SchemaPlus::ForeignKeys adds several methods to TableDefinition, allowing indexes
|
5
|
-
# and foreign key constraints to be defined within a
|
6
|
-
# <tt>create_table</tt> block of a migration, allowing for better
|
7
|
-
# encapsulation and more DRY definitions.
|
8
|
-
#
|
9
|
-
# For example, without SchemaPlus::ForeignKeys you might define a table like this:
|
10
|
-
#
|
11
|
-
# create_table :widgets do |t|
|
12
|
-
# t.string :name
|
13
|
-
# end
|
14
|
-
# add_index :widgets, :name
|
15
|
-
#
|
16
|
-
# But with SchemaPlus::ForeignKeys, the index can be defined within the create_table
|
17
|
-
# block, so you don't need to repeat the table name:
|
18
|
-
#
|
19
|
-
# create_table :widgets do |t|
|
20
|
-
# t.string :name
|
21
|
-
# t.index :name
|
22
|
-
# end
|
23
|
-
#
|
24
|
-
# Even more DRY, you can define the index as part of the column
|
25
|
-
# definition, via:
|
26
|
-
#
|
27
|
-
# create_table :widgets do |t|
|
28
|
-
# t.string :name, :index => true
|
29
|
-
# end
|
30
|
-
#
|
31
|
-
# For details about the :index option (including unique and multi-column indexes), see the
|
32
|
-
# documentation for Migration::ClassMethods#add_column
|
33
|
-
#
|
34
|
-
# SchemaPlus::ForeignKeys also supports creation of foreign key constraints analogously, using Migration::ClassMethods#add_foreign_key or TableDefinition#foreign_key or as part of the column definition, for example:
|
35
|
-
#
|
36
|
-
# create_table :posts do |t| # not DRY
|
37
|
-
# t.integer :author_id
|
38
|
-
# end
|
39
|
-
# add_foreign_key :posts, :author_id, :references => :authors
|
40
|
-
#
|
41
|
-
# create_table :posts do |t| # DRYer
|
42
|
-
# t.integer :author_id
|
43
|
-
# t.foreign_key :author_id, :references => :authors
|
44
|
-
# end
|
45
|
-
#
|
46
|
-
# create_table :posts do |t| # Dryest
|
47
|
-
# t.integer :author_id, :foreign_key => true
|
48
|
-
# end
|
49
|
-
#
|
50
|
-
# <b>NOTE:</b> In the standard configuration, SchemaPlus::ForeignKeys automatically
|
51
|
-
# creates foreign key constraints for columns whose names end in
|
52
|
-
# <tt>_id</tt>. So the above examples are redundant, unless automatic
|
53
|
-
# creation was disabled at initialization in the global Config.
|
54
|
-
#
|
55
|
-
# SchemaPlus::ForeignKeys likewise by default automatically creates foreign key constraints for
|
56
|
-
# columns defined via <tt>t.references</tt>. However, SchemaPlus::ForeignKeys does not create
|
57
|
-
# foreign key constraints if the <tt>:polymorphic</tt> option is true
|
58
|
-
#
|
59
|
-
# Finally, the configuration for foreign keys can be overriden on a per-table
|
60
|
-
# basis by passing Config options to Migration::ClassMethods#create_table, such as
|
61
|
-
#
|
62
|
-
# create_table :students, :foreign_keys => {:auto_create => false} do
|
63
|
-
# t.integer :student_id
|
64
|
-
# end
|
65
|
-
#
|
66
|
-
module TableDefinition
|
67
|
-
|
68
|
-
attr_accessor :schema_plus_config #:nodoc:
|
69
|
-
|
70
|
-
if ActiveRecord.version == Gem::Version.new("4.2.0")
|
71
|
-
def foreign_keys
|
72
|
-
@foreign_keys ||= []
|
73
|
-
end
|
74
|
-
|
75
|
-
def foreign_keys_for_table(*)
|
76
|
-
foreign_keys
|
77
|
-
end
|
78
|
-
else
|
79
|
-
def foreign_keys_for_table(table)
|
80
|
-
foreign_keys[table] ||= []
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
def foreign_key(*args) # (column_names, to_table, primary_key=nil, options=nil)
|
85
|
-
options = args.extract_options!
|
86
|
-
case args.length
|
87
|
-
when 1
|
88
|
-
to_table = args[0]
|
89
|
-
column_names = "#{to_table.to_s.singularize}_id"
|
90
|
-
when 2
|
91
|
-
column_names, to_table = args
|
92
|
-
when 3
|
93
|
-
ActiveSupport::Deprecation.warn "positional arg for foreign primary key is deprecated, use :primary_key option instead"
|
94
|
-
column_names, to_table, primary_key = args
|
95
|
-
options.merge(:primary_key => primary_key)
|
96
|
-
else
|
97
|
-
raise ArgumentError, "wrong number of arguments (#{args.length}) for foreign_key(column_names, table_name, options)"
|
98
|
-
end
|
99
|
-
|
100
|
-
options.merge!(:column => column_names)
|
101
|
-
options.reverse_merge!(:name => ForeignKeyDefinition.default_name(self.name, column_names))
|
102
|
-
fk = ::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(self.name, AbstractAdapter.proper_table_name(to_table), options)
|
103
|
-
foreign_keys_for_table(fk.to_table) << fk
|
104
|
-
self
|
105
|
-
end
|
106
|
-
|
107
|
-
end
|
108
|
-
end
|
@@ -1,35 +0,0 @@
|
|
1
|
-
module SchemaPlus::ForeignKeys
|
2
|
-
module ActiveRecord
|
3
|
-
module Migration
|
4
|
-
module CommandRecorder
|
5
|
-
|
6
|
-
attr_accessor :schema_plus_config #:nodoc:
|
7
|
-
|
8
|
-
# seems like this is fixing a rails bug:
|
9
|
-
# change_table foo, :bulk => true { |t| t.references :bar }
|
10
|
-
# results in an 'unknown method :add_reference_sql' (with mysql2)
|
11
|
-
#
|
12
|
-
# should track it down separately and submit a patch/fix to rails
|
13
|
-
#
|
14
|
-
def add_reference(table_name, ref_name, options = {}) #:nodoc:
|
15
|
-
polymorphic = options.delete(:polymorphic)
|
16
|
-
options[:references] = nil if polymorphic
|
17
|
-
# ugh. copying and pasting code from ::ActiveRecord::ConnectionAdapters::SchemaStatements#add_reference
|
18
|
-
index_options = options.delete(:index)
|
19
|
-
add_column(table_name, "#{ref_name}_id", :integer, options)
|
20
|
-
add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
|
21
|
-
add_index(table_name, polymorphic ? %w[id type].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
|
22
|
-
|
23
|
-
self
|
24
|
-
end
|
25
|
-
|
26
|
-
# overrides to add if_exists option
|
27
|
-
def invert_add_index(args)
|
28
|
-
table, columns, options = *args
|
29
|
-
[:remove_index, [table, (options||{}).merge(column: columns, if_exists: true)]]
|
30
|
-
end
|
31
|
-
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
@@ -1,79 +0,0 @@
|
|
1
|
-
module SchemaPlus::ForeignKeys
|
2
|
-
module Middleware
|
3
|
-
module Dumper
|
4
|
-
|
5
|
-
# index and foreign key constraint definitions are dumped
|
6
|
-
# inline in the create_table block. (This is done for elegance, but
|
7
|
-
# also because Sqlite3 does not allow foreign key constraints to be
|
8
|
-
# added to a table after it has been defined.)
|
9
|
-
|
10
|
-
module Tables
|
11
|
-
|
12
|
-
def before(env)
|
13
|
-
|
14
|
-
@inline_fks = Hash.new{ |h, k| h[k] = [] }
|
15
|
-
@backref_fks = Hash.new{ |h, k| h[k] = [] }
|
16
|
-
|
17
|
-
env.connection.tables.each do |table|
|
18
|
-
@inline_fks[table] = env.connection.foreign_keys(table)
|
19
|
-
env.dump.depends(table, @inline_fks[table].collect(&:to_table))
|
20
|
-
end
|
21
|
-
|
22
|
-
# Normally we dump foreign key constraints inline in the table
|
23
|
-
# definitions, both for visual cleanliness and because sqlite3
|
24
|
-
# doesn't allow foreign key constraints to be added afterwards.
|
25
|
-
# But in case there's a cycle in the constraint references, some
|
26
|
-
# constraints will need to be broken out then added later. (Adding
|
27
|
-
# constraints later won't work with sqlite3, but that means sqlite3
|
28
|
-
# won't let you create cycles in the first place.)
|
29
|
-
break_fk_cycles(env) while env.dump.strongly_connected_components.any?{|component| component.size > 1}
|
30
|
-
|
31
|
-
env.dump.data.inline_fks = @inline_fks
|
32
|
-
env.dump.data.backref_fks = @backref_fks
|
33
|
-
end
|
34
|
-
|
35
|
-
# Ignore the foreign key dumps at the end of the schema; we'll put them in/near their tables
|
36
|
-
def after(env)
|
37
|
-
env.dump.final.reject!(&it =~/foreign_key/)
|
38
|
-
end
|
39
|
-
|
40
|
-
private
|
41
|
-
|
42
|
-
def break_fk_cycles(env) #:nodoc:
|
43
|
-
env.dump.strongly_connected_components.select{|component| component.size > 1}.each do |tables|
|
44
|
-
table = tables.sort.last
|
45
|
-
backref_fks = @inline_fks[table].select{|fk| tables.include?(fk.to_table)}
|
46
|
-
@inline_fks[table] -= backref_fks
|
47
|
-
env.dump.dependencies[table] -= backref_fks.collect(&:to_table)
|
48
|
-
backref_fks.each do |fk|
|
49
|
-
@backref_fks[fk.to_table] << fk
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
end
|
55
|
-
|
56
|
-
module Table
|
57
|
-
def after(env)
|
58
|
-
dumped = {}
|
59
|
-
env.table.columns.each do |column|
|
60
|
-
if (foreign_key = env.dump.data.inline_fks[env.table.name].find(&its.column.to_s == column.name))
|
61
|
-
column.add_option foreign_key.to_dump(column: true)
|
62
|
-
dumped[foreign_key] = true
|
63
|
-
end
|
64
|
-
if (foreign_key = env.dump.data.backref_fks.values.flatten.find{|fk| fk.from_table.to_s == env.table.name && fk.column.to_s == column.name})
|
65
|
-
column.add_comment "foreign key references #{foreign_key.to_table.inspect} (below)"
|
66
|
-
end
|
67
|
-
end
|
68
|
-
env.table.statements += env.dump.data.inline_fks[env.table.name].map { |foreign_key|
|
69
|
-
foreign_key.to_dump(inline: true) unless dumped[foreign_key]
|
70
|
-
}.compact.sort
|
71
|
-
env.table.trailer += env.dump.data.backref_fks[env.table.name].map { |foreign_key|
|
72
|
-
foreign_key.to_dump
|
73
|
-
}.sort
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
end
|
79
|
-
end
|
@@ -1,184 +0,0 @@
|
|
1
|
-
module SchemaPlus::ForeignKeys
|
2
|
-
module Middleware
|
3
|
-
module Migration
|
4
|
-
|
5
|
-
module CreateTable
|
6
|
-
def around(env)
|
7
|
-
if (original_block = env.block)
|
8
|
-
config_options = env.options.delete(:foreign_keys) || {}
|
9
|
-
env.block = -> (table_definition) {
|
10
|
-
table_definition.schema_plus_config = SchemaPlus::ForeignKeys.config.merge(config_options)
|
11
|
-
original_block.call table_definition
|
12
|
-
}
|
13
|
-
end
|
14
|
-
yield env
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
module Column
|
19
|
-
|
20
|
-
#
|
21
|
-
# Column option shortcuts
|
22
|
-
#
|
23
|
-
def before(env)
|
24
|
-
fk_options = env.options[:foreign_key]
|
25
|
-
|
26
|
-
case fk_options
|
27
|
-
when false then ;
|
28
|
-
when true then fk_options = {}
|
29
|
-
end
|
30
|
-
|
31
|
-
if fk_options != false # may be nil
|
32
|
-
[:references, :on_update, :on_delete, :deferrable].each do |key|
|
33
|
-
(fk_options||={}).reverse_merge!(key => env.options[key]) if env.options.has_key? key
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
if fk_options and fk_options.has_key?(:references)
|
38
|
-
case fk_options[:references]
|
39
|
-
when nil, false
|
40
|
-
fk_options = false
|
41
|
-
when Array then
|
42
|
-
table, primary_key = fk_options[:references]
|
43
|
-
fk_options[:references] = table
|
44
|
-
fk_options[:primary_key] ||= primary_key
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
|
49
|
-
fk_options = false if fk_options and fk_options.has_key?(:references) and not fk_options[:references]
|
50
|
-
|
51
|
-
env.options[:foreign_key] = fk_options
|
52
|
-
end
|
53
|
-
|
54
|
-
#
|
55
|
-
# Add the foreign keys
|
56
|
-
#
|
57
|
-
def around(env)
|
58
|
-
options = env.options
|
59
|
-
original_options = options.dup
|
60
|
-
|
61
|
-
is_reference = (env.type == :reference)
|
62
|
-
is_polymorphic = is_reference && options[:polymorphic]
|
63
|
-
|
64
|
-
# usurp index creation from AR. That's necessary to make
|
65
|
-
# auto_index work properly
|
66
|
-
index = options.delete(:index) unless is_polymorphic
|
67
|
-
if is_reference
|
68
|
-
options[:foreign_key] = false
|
69
|
-
options[:_is_reference] = true
|
70
|
-
end
|
71
|
-
|
72
|
-
yield env
|
73
|
-
|
74
|
-
return if is_polymorphic
|
75
|
-
|
76
|
-
env.options = original_options
|
77
|
-
|
78
|
-
add_foreign_keys_and_auto_index(env)
|
79
|
-
|
80
|
-
end
|
81
|
-
|
82
|
-
private
|
83
|
-
|
84
|
-
def add_foreign_keys_and_auto_index(env)
|
85
|
-
|
86
|
-
if (reverting = env.caller.is_a?(::ActiveRecord::Migration::CommandRecorder) && env.caller.reverting)
|
87
|
-
commands_length = env.caller.commands.length
|
88
|
-
end
|
89
|
-
|
90
|
-
config = (env.caller.try(:schema_plus_config) || SchemaPlus::ForeignKeys.config)
|
91
|
-
fk_args = get_fk_args(env, config)
|
92
|
-
|
93
|
-
# remove existing fk and auto-generated index in case of change of fk on existing column
|
94
|
-
if env.operation == :change and fk_args # includes :none for explicitly off
|
95
|
-
remove_foreign_key_if_exists(env)
|
96
|
-
remove_auto_index_if_exists(env)
|
97
|
-
end
|
98
|
-
|
99
|
-
fk_args = nil if fk_args == :none
|
100
|
-
|
101
|
-
create_index(env, fk_args, config)
|
102
|
-
create_fk(env, fk_args) if fk_args
|
103
|
-
|
104
|
-
if reverting
|
105
|
-
rev = []
|
106
|
-
while env.caller.commands.length > commands_length
|
107
|
-
cmd = env.caller.commands.pop
|
108
|
-
rev.unshift cmd unless cmd[0].to_s =~ /^add_/
|
109
|
-
end
|
110
|
-
env.caller.commands.concat rev
|
111
|
-
end
|
112
|
-
|
113
|
-
end
|
114
|
-
|
115
|
-
def auto_index_name(env)
|
116
|
-
ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.auto_index_name(env.table_name, env.column_name)
|
117
|
-
end
|
118
|
-
|
119
|
-
def create_index(env, fk_args, config)
|
120
|
-
# create index if requested explicity or implicitly due to auto_index
|
121
|
-
index = env.options[:index]
|
122
|
-
index = { :name => auto_index_name(env) } if index.nil? and fk_args && config.auto_index?
|
123
|
-
return unless index
|
124
|
-
case env.caller
|
125
|
-
when ::ActiveRecord::ConnectionAdapters::TableDefinition
|
126
|
-
env.caller.index(env.column_name, index)
|
127
|
-
else
|
128
|
-
env.caller.add_index(env.table_name, env.column_name, index)
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
def create_fk(env, fk_args)
|
133
|
-
references = fk_args.delete(:references)
|
134
|
-
case env.caller
|
135
|
-
when ::ActiveRecord::ConnectionAdapters::TableDefinition
|
136
|
-
env.caller.foreign_key(env.column_name, references, fk_args)
|
137
|
-
else
|
138
|
-
env.caller.add_foreign_key(env.table_name, references, fk_args.merge(:column => env.column_name))
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
|
143
|
-
def get_fk_args(env, config)
|
144
|
-
args = nil
|
145
|
-
column_name = env.column_name.to_s
|
146
|
-
options = env.options
|
147
|
-
|
148
|
-
return :none if options[:foreign_key] == false
|
149
|
-
|
150
|
-
args = options[:foreign_key]
|
151
|
-
auto = config.auto_create?
|
152
|
-
auto = false if options[:_is_reference] and env.type != :reference # this is a nested call to column() from with reference(); suppress auto-fk
|
153
|
-
args ||= {} if auto and column_name =~ /_id$/
|
154
|
-
|
155
|
-
return nil if args.nil?
|
156
|
-
|
157
|
-
args[:references] ||= env.table_name if column_name == 'parent_id'
|
158
|
-
|
159
|
-
args[:references] ||= begin
|
160
|
-
table_name = column_name.sub(/_id$/, '')
|
161
|
-
table_name = table_name.pluralize if ::ActiveRecord::Base.pluralize_table_names
|
162
|
-
table_name
|
163
|
-
end
|
164
|
-
|
165
|
-
args[:on_update] ||= config.on_update
|
166
|
-
args[:on_delete] ||= config.on_delete
|
167
|
-
|
168
|
-
args
|
169
|
-
end
|
170
|
-
|
171
|
-
def remove_foreign_key_if_exists(env)
|
172
|
-
env.caller.remove_foreign_key(env.table_name.to_s, column: env.column_name.to_s, :if_exists => true)
|
173
|
-
end
|
174
|
-
|
175
|
-
def remove_auto_index_if_exists(env)
|
176
|
-
env.caller.remove_index(env.table_name, :name => auto_index_name(env), :column => env.column_name, :if_exists => true)
|
177
|
-
end
|
178
|
-
|
179
|
-
end
|
180
|
-
|
181
|
-
end
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|