foreign_keys 1.2.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 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
+