schema_plus 0.1.0.pre1
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 +25 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +25 -0
- data/README.rdoc +147 -0
- data/Rakefile +70 -0
- data/init.rb +1 -0
- data/lib/schema_plus/active_record/associations.rb +211 -0
- data/lib/schema_plus/active_record/base.rb +81 -0
- data/lib/schema_plus/active_record/connection_adapters/abstract_adapter.rb +96 -0
- data/lib/schema_plus/active_record/connection_adapters/column.rb +55 -0
- data/lib/schema_plus/active_record/connection_adapters/foreign_key_definition.rb +115 -0
- data/lib/schema_plus/active_record/connection_adapters/index_definition.rb +51 -0
- data/lib/schema_plus/active_record/connection_adapters/mysql_adapter.rb +111 -0
- data/lib/schema_plus/active_record/connection_adapters/postgresql_adapter.rb +163 -0
- data/lib/schema_plus/active_record/connection_adapters/schema_statements.rb +39 -0
- data/lib/schema_plus/active_record/connection_adapters/sqlite3_adapter.rb +78 -0
- data/lib/schema_plus/active_record/connection_adapters/table_definition.rb +130 -0
- data/lib/schema_plus/active_record/migration.rb +220 -0
- data/lib/schema_plus/active_record/schema.rb +27 -0
- data/lib/schema_plus/active_record/schema_dumper.rb +122 -0
- data/lib/schema_plus/active_record/validations.rb +139 -0
- data/lib/schema_plus/railtie.rb +12 -0
- data/lib/schema_plus/version.rb +3 -0
- data/lib/schema_plus.rb +248 -0
- data/schema_plus.gemspec +37 -0
- data/schema_plus.gemspec.rails3.0 +36 -0
- data/schema_plus.gemspec.rails3.1 +36 -0
- data/spec/association_spec.rb +529 -0
- data/spec/connections/mysql/connection.rb +18 -0
- data/spec/connections/mysql2/connection.rb +18 -0
- data/spec/connections/postgresql/connection.rb +15 -0
- data/spec/connections/sqlite3/connection.rb +14 -0
- data/spec/foreign_key_definition_spec.rb +23 -0
- data/spec/foreign_key_spec.rb +142 -0
- data/spec/index_definition_spec.rb +139 -0
- data/spec/index_spec.rb +71 -0
- data/spec/migration_spec.rb +405 -0
- data/spec/models/comment.rb +2 -0
- data/spec/models/post.rb +2 -0
- data/spec/models/user.rb +2 -0
- data/spec/references_spec.rb +78 -0
- data/spec/schema/auto_schema.rb +23 -0
- data/spec/schema/core_schema.rb +21 -0
- data/spec/schema_dumper_spec.rb +167 -0
- data/spec/schema_spec.rb +71 -0
- data/spec/spec_helper.rb +59 -0
- data/spec/support/extensions/active_model.rb +13 -0
- data/spec/support/helpers.rb +16 -0
- data/spec/support/matchers/automatic_foreign_key_matchers.rb +2 -0
- data/spec/support/matchers/have_index.rb +52 -0
- data/spec/support/matchers/reference.rb +66 -0
- data/spec/support/reference.rb +66 -0
- data/spec/validations_spec.rb +294 -0
- data/spec/views_spec.rb +140 -0
- metadata +269 -0
@@ -0,0 +1,81 @@
|
|
1
|
+
module SchemaPlus
|
2
|
+
module ActiveRecord
|
3
|
+
|
4
|
+
#
|
5
|
+
# SchemaPlus adds several methods to ActiveRecord::Base
|
6
|
+
#
|
7
|
+
module Base
|
8
|
+
def self.included(base) #:nodoc:
|
9
|
+
base.extend(ClassMethods)
|
10
|
+
base.extend(SchemaPlus::ActiveRecord::Associations)
|
11
|
+
base.extend(SchemaPlus::ActiveRecord::Validations)
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods #:nodoc:
|
15
|
+
def self.extended(base) #:nodoc:
|
16
|
+
class << base
|
17
|
+
alias_method_chain :columns, :schema_plus
|
18
|
+
alias_method_chain :abstract_class?, :schema_plus
|
19
|
+
alias_method_chain :reset_column_information, :schema_plus
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
public
|
24
|
+
|
25
|
+
# Per-model override of Config options. Use via, e.g.
|
26
|
+
# class MyModel < ActiveRecord::Base
|
27
|
+
# schema_plus :associations => { :auto_create => false }
|
28
|
+
# end
|
29
|
+
def schema_plus(opts)
|
30
|
+
@schema_plus_config = SchemaPlus.config.merge(opts)
|
31
|
+
end
|
32
|
+
|
33
|
+
def abstract_class_with_schema_plus? #:nodoc:
|
34
|
+
abstract_class_without_schema_plus? || !(name =~ /^Abstract/).nil?
|
35
|
+
end
|
36
|
+
|
37
|
+
def columns_with_schema_plus #:nodoc:
|
38
|
+
unless @schema_plus_extended_columns
|
39
|
+
@schema_plus_extended_columns = true
|
40
|
+
cols = columns_hash
|
41
|
+
indexes.each do |index|
|
42
|
+
index.columns.each do |name|
|
43
|
+
cols[name].indexes << index
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
columns_without_schema_plus
|
48
|
+
end
|
49
|
+
|
50
|
+
def reset_column_information_with_schema_plus #:nodoc:
|
51
|
+
reset_column_information_without_schema_plus
|
52
|
+
@indexes = @foreign_keys = @schema_plus_extended_columns = nil
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns a list of IndexDefinition objects, for each index
|
56
|
+
# defind on this model's table.
|
57
|
+
def indexes
|
58
|
+
@indexes ||= connection.indexes(table_name, "#{name} Indexes")
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns a list of ForeignKeyDefinition objects, for each foreign
|
62
|
+
# key constraint defined in this model's table
|
63
|
+
def foreign_keys
|
64
|
+
@foreign_keys ||= connection.foreign_keys(table_name, "#{name} Foreign Keys")
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns a list of ForeignKeyDefinition objects, for each foreign
|
68
|
+
# key constraint of other tables that refer to this model's table
|
69
|
+
def reverse_foreign_keys
|
70
|
+
connection.reverse_foreign_keys(table_name, "#{name} Reverse Foreign Keys")
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def schema_plus_config # :nodoc:
|
76
|
+
@schema_plus_config ||= SchemaPlus.config.dup
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module SchemaPlus
|
2
|
+
module ActiveRecord
|
3
|
+
# SchemaPlus adds several methods to the connection adapter (as returned by ActiveRecordBase#connection). See AbstractAdapter for details.
|
4
|
+
module ConnectionAdapters
|
5
|
+
|
6
|
+
#
|
7
|
+
# SchemaPlus adds several methods to
|
8
|
+
# ActiveRecord::ConnectionAdapters::AbstractAdapter. In most cases
|
9
|
+
# you don't call these directly, but rather the methods that define
|
10
|
+
# things are called by schema statements, and methods that query
|
11
|
+
# things are called by ActiveRecord::Base.
|
12
|
+
#
|
13
|
+
module AbstractAdapter
|
14
|
+
def self.included(base) #:nodoc:
|
15
|
+
base.alias_method_chain :initialize, :schema_plus
|
16
|
+
base.alias_method_chain :drop_table, :schema_plus
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize_with_schema_plus(*args) #:nodoc:
|
20
|
+
initialize_without_schema_plus(*args)
|
21
|
+
adapter = nil
|
22
|
+
case adapter_name
|
23
|
+
# name of MySQL adapter depends on mysql gem
|
24
|
+
# * with mysql gem adapter is named MySQL
|
25
|
+
# * with mysql2 gem adapter is named Mysql2
|
26
|
+
# Here we handle this and hopefully futher adapter names
|
27
|
+
when /^MySQL/i
|
28
|
+
adapter = 'MysqlAdapter'
|
29
|
+
when 'PostgreSQL'
|
30
|
+
adapter = 'PostgresqlAdapter'
|
31
|
+
when 'SQLite'
|
32
|
+
adapter = 'Sqlite3Adapter'
|
33
|
+
end
|
34
|
+
if adapter
|
35
|
+
adapter_module = SchemaPlus::ActiveRecord::ConnectionAdapters.const_get(adapter)
|
36
|
+
self.class.send(:include, adapter_module) unless self.class.include?(adapter_module)
|
37
|
+
self.post_initialize if self.respond_to? :post_initialize
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Create a view given the SQL definition
|
42
|
+
def create_view(view_name, definition)
|
43
|
+
execute "CREATE VIEW #{quote_table_name(view_name)} AS #{definition}"
|
44
|
+
end
|
45
|
+
|
46
|
+
# Drop the named view
|
47
|
+
def drop_view(view_name)
|
48
|
+
execute "DROP VIEW #{quote_table_name(view_name)}"
|
49
|
+
end
|
50
|
+
|
51
|
+
#--
|
52
|
+
# these are all expected to be defined by subclasses, listing them
|
53
|
+
# here only as templates.
|
54
|
+
#++
|
55
|
+
# Returns a list of all views (abstract)
|
56
|
+
def views(name = nil) [] end
|
57
|
+
# Returns the SQL definition of a given view (abstract)
|
58
|
+
def view_definition(view_name, name = nil) end
|
59
|
+
# Return the ForeignKeyDefinition objects for foreign key
|
60
|
+
# constraints defined on this table (abstract)
|
61
|
+
def foreign_keys(table_name, name = nil) [] end
|
62
|
+
# Return the ForeignKeyDefinition objects for foreign key
|
63
|
+
# constraints defined on other tables that reference this table
|
64
|
+
# (abstract)
|
65
|
+
def reverse_foreign_keys(table_name, name = nil) [] end
|
66
|
+
|
67
|
+
# Define a foreign key constraint. Valid options are :on_update,
|
68
|
+
# :on_delete, and :deferrable, with values as described at
|
69
|
+
# ForeignKeyDefinition
|
70
|
+
def add_foreign_key(table_name, column_names, references_table_name, references_column_names, options = {})
|
71
|
+
foreign_key = ForeignKeyDefinition.new(options[:name], table_name, column_names, ::ActiveRecord::Migrator.proper_table_name(references_table_name), references_column_names, options[:on_update], options[:on_delete], options[:deferrable])
|
72
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} ADD #{foreign_key.to_sql}"
|
73
|
+
end
|
74
|
+
|
75
|
+
# Remove a foreign key constraint
|
76
|
+
def remove_foreign_key(table_name, foreign_key_name)
|
77
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{foreign_key_name}"
|
78
|
+
end
|
79
|
+
|
80
|
+
def drop_table_with_schema_plus(name, options = {}) #:nodoc:
|
81
|
+
unless ::ActiveRecord::Base.connection.class.include?(SchemaPlus::ActiveRecord::ConnectionAdapters::Sqlite3Adapter)
|
82
|
+
reverse_foreign_keys(name).each { |foreign_key| remove_foreign_key(foreign_key.table_name, foreign_key.name) }
|
83
|
+
end
|
84
|
+
drop_table_without_schema_plus(name, options)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns true if the database supports parital indexes (abstract; only
|
88
|
+
# Postgresql returns true)
|
89
|
+
def supports_partial_indexes?
|
90
|
+
false
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module SchemaPlus
|
2
|
+
module ActiveRecord
|
3
|
+
module ConnectionAdapters
|
4
|
+
|
5
|
+
#
|
6
|
+
# SchemaPlus adds several methods to Column
|
7
|
+
#
|
8
|
+
module Column
|
9
|
+
|
10
|
+
# Returns the list of IndexDefinition instances for each index that
|
11
|
+
# refers to this column. Returns an empty list if there are no
|
12
|
+
# such indexes.
|
13
|
+
def indexes
|
14
|
+
# list get filled by SchemaPlus::ActiveRecord::Base::columns_with_schema_plus
|
15
|
+
@indexes ||= []
|
16
|
+
end
|
17
|
+
|
18
|
+
# If the column is in a unique index, returns a list of names of other columns in
|
19
|
+
# the index. Returns an empty list if it's a single-column index.
|
20
|
+
# Returns nil if the column is not in a unique index.
|
21
|
+
def unique_scope
|
22
|
+
if index = indexes.select{|i| i.unique}.sort_by{|i| i.columns.size}.first
|
23
|
+
index.columns.reject{|name| name == self.name}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns true if the column is in a unique index. See also
|
28
|
+
# unique_scope
|
29
|
+
def unique?
|
30
|
+
indexes.any?{|i| i.unique}
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns true if the column is in one or more indexes that are
|
34
|
+
# case sensitive
|
35
|
+
def case_sensitive?
|
36
|
+
indexes.any?{|i| i.case_sensitive?}
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns the circumstance in which the column must have a value:
|
40
|
+
# nil if the column may be null
|
41
|
+
# :save if the column has no default value
|
42
|
+
# :update otherwise
|
43
|
+
def required_on
|
44
|
+
if null
|
45
|
+
nil
|
46
|
+
elsif default.nil?
|
47
|
+
:save
|
48
|
+
else
|
49
|
+
:update
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module SchemaPlus
|
2
|
+
module ActiveRecord
|
3
|
+
module ConnectionAdapters
|
4
|
+
# Instances of this class are returned by the queries ActiveRecord::Base#foreign_keys and ActiveRecord::Base#reverse_foreign_keys (via AbstractAdapter#foreign_keys and AbstractAdapter#reverse_foreign_keys)
|
5
|
+
#
|
6
|
+
# The on_update and on_delete attributes can take on the following values:
|
7
|
+
# :cascade
|
8
|
+
# :restrict
|
9
|
+
# :set_null
|
10
|
+
# :set_default
|
11
|
+
# :no_action
|
12
|
+
class ForeignKeyDefinition
|
13
|
+
|
14
|
+
# The name of the foreign key constraint
|
15
|
+
attr_reader :name
|
16
|
+
|
17
|
+
# The name of the table the constraint is defined on
|
18
|
+
attr_reader :table_name
|
19
|
+
|
20
|
+
# The list of column names that are constrained (as strings).
|
21
|
+
attr_reader :column_names
|
22
|
+
|
23
|
+
# The foreign table that is referenced by the constraint
|
24
|
+
attr_reader :references_table_name
|
25
|
+
|
26
|
+
# The list of column names (as strings) of the foreign table that are referenced
|
27
|
+
# by the constraint
|
28
|
+
attr_reader :references_column_names
|
29
|
+
|
30
|
+
# The ON_UPDATE behavior for the constraint. See above for the
|
31
|
+
# possible values.
|
32
|
+
attr_reader :on_update
|
33
|
+
|
34
|
+
# The ON_UPDATE behavior for the constraint. See above for the
|
35
|
+
# possible values.
|
36
|
+
attr_reader :on_delete
|
37
|
+
|
38
|
+
# True if the constraint is deferrable
|
39
|
+
attr_reader :deferrable
|
40
|
+
|
41
|
+
# :enddoc:
|
42
|
+
|
43
|
+
ACTIONS = { :cascade => "CASCADE", :restrict => "RESTRICT", :set_null => "SET NULL", :set_default => "SET DEFAULT", :no_action => "NO ACTION" }.freeze
|
44
|
+
|
45
|
+
def initialize(name, table_name, column_names, references_table_name, references_column_names, on_update = nil, on_delete = nil, deferrable = nil)
|
46
|
+
@name = name
|
47
|
+
@table_name = unquote(table_name)
|
48
|
+
@column_names = unquote(column_names)
|
49
|
+
@references_table_name = unquote(references_table_name)
|
50
|
+
@references_column_names = unquote(references_column_names)
|
51
|
+
@on_update = on_update
|
52
|
+
@on_delete = on_delete
|
53
|
+
@deferrable = deferrable
|
54
|
+
|
55
|
+
ACTIONS.has_key?(on_update) or raise(ArgumentError, "invalid :on_update action: #{on_update.inspect}") if on_update
|
56
|
+
ACTIONS.has_key?(on_delete) or raise(ArgumentError, "invalid :on_delete action: #{on_delete.inspect}") if on_delete
|
57
|
+
if ::ActiveRecord::Base.connection.adapter_name =~ /^mysql/i
|
58
|
+
raise(NotImplementedError, "MySQL does not support ON UPDATE SET DEFAULT") if on_update == :set_default
|
59
|
+
raise(NotImplementedError, "MySQL does not support ON DELETE SET DEFAULT") if on_delete == :set_default
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Dumps a definition of foreign key.
|
64
|
+
# Must be invoked inside create_table block.
|
65
|
+
#
|
66
|
+
# It was introduced to satisfy sqlite which requires foreign key definitions
|
67
|
+
# to be declared when creating a table. That approach is fine for MySQL and
|
68
|
+
# PostgreSQL too.
|
69
|
+
def to_dump
|
70
|
+
dump = " t.foreign_key"
|
71
|
+
dump << " [#{Array(column_names).collect{ |name| name.inspect }.join(', ')}]"
|
72
|
+
dump << ", #{references_table_name.inspect}, [#{Array(references_column_names).collect{ |name| name.inspect }.join(', ')}]"
|
73
|
+
dump << ", :on_update => :#{on_update}" if on_update
|
74
|
+
dump << ", :on_delete => :#{on_delete}" if on_delete
|
75
|
+
dump << ", :deferrable => #{deferrable}" if deferrable
|
76
|
+
dump << ", :name => #{name.inspect}" if name
|
77
|
+
dump
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_sql
|
81
|
+
sql = name ? "CONSTRAINT #{name} " : ""
|
82
|
+
sql << "FOREIGN KEY (#{quoted_column_names.join(", ")}) REFERENCES #{quoted_references_table_name} (#{quoted_references_column_names.join(", ")})"
|
83
|
+
sql << " ON UPDATE #{ACTIONS[on_update]}" if on_update
|
84
|
+
sql << " ON DELETE #{ACTIONS[on_delete]}" if on_delete
|
85
|
+
sql << " DEFERRABLE" if deferrable
|
86
|
+
sql
|
87
|
+
end
|
88
|
+
|
89
|
+
def quoted_column_names
|
90
|
+
Array(column_names).collect { |name| ::ActiveRecord::Base.connection.quote_column_name(name) }
|
91
|
+
end
|
92
|
+
|
93
|
+
def quoted_references_column_names
|
94
|
+
Array(references_column_names).collect { |name| ::ActiveRecord::Base.connection.quote_column_name(name) }
|
95
|
+
end
|
96
|
+
|
97
|
+
def quoted_references_table_name
|
98
|
+
::ActiveRecord::Base.connection.quote_table_name(references_table_name)
|
99
|
+
end
|
100
|
+
|
101
|
+
def unquote(names)
|
102
|
+
if names.is_a?(Array)
|
103
|
+
names.collect { |name| __unquote(name) }
|
104
|
+
else
|
105
|
+
__unquote(names)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def __unquote(value)
|
110
|
+
value.to_s.sub(/^["`](.*)["`]$/, '\1')
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module SchemaPlus
|
2
|
+
module ActiveRecord
|
3
|
+
module ConnectionAdapters
|
4
|
+
#
|
5
|
+
# SchemaPlus extends the IndexDefinition object to return information
|
6
|
+
# about partial indexes and case sensitivity (i.e. Postgresql
|
7
|
+
# support).
|
8
|
+
module IndexDefinition
|
9
|
+
def self.included(base) #:nodoc:
|
10
|
+
base.alias_method_chain :initialize, :schema_plus
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :conditions
|
14
|
+
attr_reader :expression
|
15
|
+
attr_reader :kind
|
16
|
+
|
17
|
+
def case_sensitive?
|
18
|
+
@case_sensitive
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize_with_schema_plus(*args) #:nodoc:
|
22
|
+
# same args as add_index(table_name, column_names, options={})
|
23
|
+
if args.length == 2 or (args.length == 3 && Hash === args.last)
|
24
|
+
table_name, column_names, options = args + [{}]
|
25
|
+
initialize_without_schema_plus(table_name, options[:name], options[:unique], column_names, options[:lengths])
|
26
|
+
@conditions = options[:conditions]
|
27
|
+
@expression = options[:expression]
|
28
|
+
@kind = options[:kind]
|
29
|
+
@case_sensitive = options.include?(:case_sensitive) ? options[:case_sensitive] : true
|
30
|
+
else # backwards compatibility
|
31
|
+
initialize_without_schema_plus(*args)
|
32
|
+
@case_sensitive = true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# returns the options as a hash suitable for add_index
|
37
|
+
def opts #:nodoc:
|
38
|
+
opts = {}
|
39
|
+
opts[:name] = name unless name.nil?
|
40
|
+
opts[:unique] = unique unless unique.nil?
|
41
|
+
opts[:lengths] = lengths unless lengths.nil?
|
42
|
+
opts[:conditions] = conditions unless conditions.nil?
|
43
|
+
opts[:expression] = expression unless expression.nil?
|
44
|
+
opts[:kind] = kind unless kind.nil?
|
45
|
+
opts[:case_sensitive] = case_sensitive? unless @case_sensitive.nil?
|
46
|
+
opts
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module SchemaPlus
|
2
|
+
module ActiveRecord
|
3
|
+
module ConnectionAdapters
|
4
|
+
# SchemaPlus includes a MySQL implementation of the AbstractAdapater
|
5
|
+
# extensions. (This works with both the <tt>mysql</t> and
|
6
|
+
# <tt>mysql2</tt> gems.)
|
7
|
+
module MysqlAdapter
|
8
|
+
|
9
|
+
#:enddoc:
|
10
|
+
|
11
|
+
def self.included(base)
|
12
|
+
base.class_eval do
|
13
|
+
alias_method_chain :tables, :schema_plus
|
14
|
+
alias_method_chain :remove_column, :schema_plus
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def tables_with_schema_plus(name=nil, *args)
|
19
|
+
tables_without_schema_plus(name, *args) - views(name)
|
20
|
+
end
|
21
|
+
|
22
|
+
def remove_column_with_schema_plus(table_name, column_name)
|
23
|
+
foreign_keys(table_name).select { |foreign_key| foreign_key.column_names.include?(column_name.to_s) }.each do |foreign_key|
|
24
|
+
remove_foreign_key(table_name, foreign_key.name)
|
25
|
+
end
|
26
|
+
remove_column_without_schema_plus(table_name, column_name)
|
27
|
+
end
|
28
|
+
|
29
|
+
def remove_foreign_key(table_name, foreign_key_name, options = {})
|
30
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} DROP FOREIGN KEY #{foreign_key_name}"
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def foreign_keys(table_name, name = nil)
|
35
|
+
results = execute("SHOW CREATE TABLE #{quote_table_name(table_name)}", name)
|
36
|
+
|
37
|
+
foreign_keys = []
|
38
|
+
|
39
|
+
results.each do |row|
|
40
|
+
row[1].lines.each do |line|
|
41
|
+
if line =~ /^ CONSTRAINT [`"](.+?)[`"] FOREIGN KEY \([`"](.+?)[`"]\) REFERENCES [`"](.+?)[`"] \((.+?)\)( ON DELETE (.+?))?( ON UPDATE (.+?))?,?$/
|
42
|
+
name = $1
|
43
|
+
column_names = $2
|
44
|
+
references_table_name = $3
|
45
|
+
references_column_names = $4
|
46
|
+
on_update = $8
|
47
|
+
on_delete = $6
|
48
|
+
on_update = on_update ? on_update.downcase.gsub(' ', '_').to_sym : :restrict
|
49
|
+
on_delete = on_delete ? on_delete.downcase.gsub(' ', '_').to_sym : :restrict
|
50
|
+
|
51
|
+
foreign_keys << ForeignKeyDefinition.new(name,
|
52
|
+
table_name, column_names.gsub('`', '').split(', '),
|
53
|
+
references_table_name, references_column_names.gsub('`', '').split(', '),
|
54
|
+
on_update, on_delete)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
foreign_keys
|
60
|
+
end
|
61
|
+
|
62
|
+
def reverse_foreign_keys(table_name, name = nil)
|
63
|
+
results = execute(<<-SQL, name)
|
64
|
+
SELECT constraint_name, table_name, column_name, referenced_table_name, referenced_column_name
|
65
|
+
FROM information_schema.key_column_usage
|
66
|
+
WHERE table_schema = SCHEMA()
|
67
|
+
AND referenced_table_schema = table_schema
|
68
|
+
ORDER BY constraint_name, ordinal_position;
|
69
|
+
SQL
|
70
|
+
current_foreign_key = nil
|
71
|
+
foreign_keys = []
|
72
|
+
|
73
|
+
results.each do |row|
|
74
|
+
next unless table_name.casecmp(row[3]) == 0
|
75
|
+
if current_foreign_key != row[0]
|
76
|
+
foreign_keys << ForeignKeyDefinition.new(row[0], row[1], [], row[3], [])
|
77
|
+
current_foreign_key = row[0]
|
78
|
+
end
|
79
|
+
|
80
|
+
foreign_keys.last.column_names << row[2]
|
81
|
+
foreign_keys.last.references_column_names << row[4]
|
82
|
+
end
|
83
|
+
|
84
|
+
foreign_keys
|
85
|
+
end
|
86
|
+
|
87
|
+
def views(name = nil)
|
88
|
+
views = []
|
89
|
+
execute("SELECT table_name FROM information_schema.views WHERE table_schema = SCHEMA()", name).each do |row|
|
90
|
+
views << row[0]
|
91
|
+
end
|
92
|
+
views
|
93
|
+
end
|
94
|
+
|
95
|
+
def view_definition(view_name, name = nil)
|
96
|
+
result = execute("SELECT view_definition, check_option FROM information_schema.views WHERE table_schema = SCHEMA() AND table_name = #{quote(view_name)}", name)
|
97
|
+
return nil unless (result.respond_to?(:num_rows) ? result.num_rows : result.to_a.size) > 0 # mysql vs mysql2
|
98
|
+
row = result.respond_to?(:fetch_row) ? result.fetch_row : result.first
|
99
|
+
sql = row[0]
|
100
|
+
sql.gsub!(%r{#{quote_table_name(current_database)}[.]}, '')
|
101
|
+
case row[1]
|
102
|
+
when "CASCADED" then sql += " WITH CASCADED CHECK OPTION"
|
103
|
+
when "LOCAL" then sql += " WITH LOCAL CHECK OPTION"
|
104
|
+
end
|
105
|
+
sql
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|