foreign_keys 1.2.0

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