brianjlandau-foreigner 0.7.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/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Matthew Higgins, Brian Landau
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,103 @@
1
+ Foreigner
2
+ =========
3
+
4
+ Rails does not come with methods to add foreign keys. Foreigner introduces a few
5
+ methods to your migrations for adding and removing foreign key constraints.
6
+
7
+ Since each adapter implements the API, migrations using Foreigner will continue to
8
+ work on databases that do not support foreign keys, such as sqlite3.
9
+
10
+ Installation
11
+ ------------
12
+
13
+ Install as a plugin:
14
+
15
+ ruby script/plugin install git://github.com/brianjlandau/foreigner.git
16
+
17
+ In Rails 2, install as a gem by adding the following to config/environment.rb:
18
+
19
+ config.gem "brianjlandau-foreigner", :lib => "foreigner"
20
+
21
+ In Rails 3, install as a gem by adding the following to your Gemfile:
22
+
23
+ gem 'brianjlandau-foreigner', :require => 'foreigner'
24
+
25
+ API
26
+ ---
27
+
28
+ An adapter implementing the Foreigner API implements three methods.
29
+ (Options are documented in connection_adapters/abstract/schema_definitions.rb):
30
+
31
+ add_foreign_key(from_table, to_table, options)
32
+ remove_foreign_key(from_table, options)
33
+ foreign_keys(table_name)
34
+
35
+ Example
36
+ -------
37
+
38
+ The most common use of foreign keys is to reference a table that a model belongs to.
39
+ For example, given the following model:
40
+
41
+ class Comment < ActiveRecord::Base
42
+ belongs_to :post
43
+ end
44
+
45
+ class Post < ActiveRecord::Base
46
+ has_many :comments, :dependent => :delete_all
47
+ end
48
+
49
+ You should add a foreign key in your migration:
50
+
51
+ add_foreign_key(:comments, :posts)
52
+
53
+ The :dependent option can be moved from the has_many definition to the foreign key:
54
+
55
+ add_foreign_key(:comments, :posts, :dependent => :delete)
56
+
57
+ If the column is named article_id instead of post_id, use the :column option:
58
+
59
+ add_foreign_key(:comments, :posts, :column => 'article_id')
60
+
61
+ Lastly, a name can be specified for the foreign key constraint:
62
+
63
+ add_foreign_key(:comments, :posts, :name => 'comment_article_foreign_key')
64
+
65
+ Create/Change Table Shorthand
66
+ -----------------------------
67
+
68
+ Foreigner adds extra behavior to change_table, which lets you define foreign keys using shorthand.
69
+
70
+ Add a missing foreign key to comments:
71
+
72
+ change_table :comments do |t|
73
+ t.foreign_key :posts, :dependent => :delete
74
+ end
75
+
76
+ t.foreign_key accepts the same options as add_foreign_key.
77
+
78
+
79
+ Additional t.references option
80
+ ------------------------------
81
+
82
+ Foreigner extends table.references with the :foreign_key option. Pass true, and the default
83
+ foreign key options are used:
84
+
85
+ change_table :comments do |t|
86
+ t.references :post, :foreign_key => true
87
+ end
88
+
89
+ An options hash can also be passed. It accepts the same options as add_foreign_key:
90
+
91
+ change_table :comments do |t|
92
+ t.references :author, :foreign_key => {:dependent => :destroy}
93
+ end
94
+
95
+ By default, t.references will not generate a foreign key.
96
+
97
+ schema.rb
98
+ ---------
99
+
100
+ Similar to indexes, the foreign keys in your database are automatically dumped to schema.rb.
101
+ This allows you to use foreign keys without switching to the :sql schema.
102
+
103
+ Copyright (c) 2009 Matthew Higgins, Brian Landau, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,39 @@
1
+ begin
2
+ require 'jeweler'
3
+
4
+ Jeweler::Tasks.new do |gemspec|
5
+ gemspec.name = "brianjlandau-foreigner"
6
+ gemspec.summary = "Foreign keys for Rails"
7
+ gemspec.description = "Adds helpers to migrations and correctly dumps foreign keys to schema.rb"
8
+ gemspec.email = "brianjlandau@gmail.com"
9
+ gemspec.homepage = "http://github.com/brianjlandau/foreigner"
10
+ gemspec.authors = ["Matthew Higgins", "Brian Landau"]
11
+ end
12
+ Jeweler::GemcutterTasks.new
13
+ rescue LoadError
14
+ puts "Jeweler not available."
15
+ puts "Install it with: gem install jeweler"
16
+ end
17
+
18
+ require 'rake/testtask'
19
+ require 'rake/rdoctask'
20
+
21
+ desc 'Default: run unit tests.'
22
+ task :default => :test
23
+
24
+ desc 'Test the foreigner plugin.'
25
+ Rake::TestTask.new(:test) do |t|
26
+ t.libs << 'lib'
27
+ t.libs << 'test'
28
+ t.pattern = 'test/**/*_test.rb'
29
+ t.verbose = true
30
+ end
31
+
32
+ desc 'Generate documentation for the foreigner plugin.'
33
+ Rake::RDocTask.new(:rdoc) do |rdoc|
34
+ rdoc.rdoc_dir = 'rdoc'
35
+ rdoc.title = 'Foreigner'
36
+ rdoc.options << '--line-numbers' << '--inline-source'
37
+ rdoc.rdoc_files.include('README')
38
+ rdoc.rdoc_files.include('lib/**/*.rb')
39
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.7.0
@@ -0,0 +1,58 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{brianjlandau-foreigner}
8
+ s.version = "0.7.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Matthew Higgins", "Brian Landau"]
12
+ s.date = %q{2010-06-23}
13
+ s.description = %q{Adds helpers to migrations and correctly dumps foreign keys to schema.rb}
14
+ s.email = %q{brianjlandau@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "README"
17
+ ]
18
+ s.files = [
19
+ "MIT-LICENSE",
20
+ "README",
21
+ "Rakefile",
22
+ "VERSION",
23
+ "brianjlandau-foreigner.gemspec",
24
+ "init.rb",
25
+ "install.rb",
26
+ "lib/foreigner.rb",
27
+ "lib/foreigner/connection_adapters/abstract/schema_definitions.rb",
28
+ "lib/foreigner/connection_adapters/abstract/schema_statements.rb",
29
+ "lib/foreigner/connection_adapters/mysql_adapter.rb",
30
+ "lib/foreigner/connection_adapters/postgresql_adapter.rb",
31
+ "lib/foreigner/connection_adapters/sql_2003.rb",
32
+ "lib/foreigner/schema_dumper.rb",
33
+ "tasks/foreigner_tasks.rake",
34
+ "test/helper.rb",
35
+ "test/mysql_adapter_test.rb",
36
+ "uninstall.rb"
37
+ ]
38
+ s.homepage = %q{http://github.com/brianjlandau/foreigner}
39
+ s.rdoc_options = ["--charset=UTF-8"]
40
+ s.require_paths = ["lib"]
41
+ s.rubygems_version = %q{1.3.7}
42
+ s.summary = %q{Foreign keys for Rails}
43
+ s.test_files = [
44
+ "test/helper.rb",
45
+ "test/mysql_adapter_test.rb"
46
+ ]
47
+
48
+ if s.respond_to? :specification_version then
49
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
50
+ s.specification_version = 3
51
+
52
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
53
+ else
54
+ end
55
+ else
56
+ end
57
+ end
58
+
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'foreigner'
data/install.rb ADDED
@@ -0,0 +1 @@
1
+ # Install hook code here
@@ -0,0 +1,112 @@
1
+ module Foreigner
2
+ module ConnectionAdapters
3
+ class ForeignKeyDefinition < Struct.new(:from_table, :to_table, :options) #:nodoc:
4
+ end
5
+
6
+ module SchemaDefinitions
7
+ def self.included(base)
8
+ base::TableDefinition.class_eval do
9
+ include Foreigner::ConnectionAdapters::TableDefinition
10
+ end
11
+
12
+ base::Table.class_eval do
13
+ include Foreigner::ConnectionAdapters::Table
14
+ end
15
+ end
16
+ end
17
+
18
+ module TableDefinition
19
+ def self.included(base)
20
+ base.class_eval do
21
+ include InstanceMethods
22
+ alias_method_chain :references, :foreign_keys
23
+ end
24
+ end
25
+
26
+ module InstanceMethods
27
+ def references_with_foreign_keys(*args)
28
+ options = args.extract_options!
29
+ if options[:foreign_key].present?
30
+ ActiveSupport::Deprecation.warn(
31
+ ':foreign_key option is deprecated inside create_table. ' +
32
+ 'to add a foreign key, use add_foreign_key', caller[0,10]
33
+ )
34
+ end
35
+
36
+ references_without_foreign_keys(*(args << options))
37
+ end
38
+
39
+ def foreign_key(to_table, options = {})
40
+ ActiveSupport::Deprecation.warn(
41
+ 'adding a foreign key inside create_table is deprecated. ' +
42
+ 'to add a foreign key, use add_foreign_key', caller[0,10]
43
+ )
44
+ end
45
+ end
46
+ end
47
+
48
+ module Table
49
+ def self.included(base)
50
+ base.class_eval do
51
+ include InstanceMethods
52
+ alias_method_chain :references, :foreign_keys
53
+ end
54
+ end
55
+
56
+ module InstanceMethods
57
+ # Adds a new foreign key to the table. +to_table+ can be a single Symbol, or
58
+ # an Array of Symbols. See SchemaStatements#add_foreign_key
59
+ #
60
+ # ===== Examples
61
+ # ====== Creating a simple foreign key
62
+ # t.foreign_key(:people)
63
+ # ====== Defining the column
64
+ # t.foreign_key(:people, :column => :sender_id)
65
+ # ====== Creating a named foreign key
66
+ # t.foreign_key(:people, :column => :sender_id, :name => 'sender_foreign_key')
67
+ # ====== Defining the column of the +to_table+.
68
+ # t.foreign_key(:people, :column => :sender_id, :primary_key => :person_id)
69
+ def foreign_key(to_table, options = {})
70
+ @base.add_foreign_key(@table_name, to_table, options)
71
+ end
72
+
73
+ # Remove the given foreign key from the table.
74
+ #
75
+ # ===== Examples
76
+ # ====== Remove the suppliers_company_id_fk in the suppliers table.
77
+ # t.remove_foreign_key :companies
78
+ # ====== Remove the foreign key named accounts_branch_id_fk in the accounts table.
79
+ # remove_foreign_key :column => :branch_id
80
+ # ====== Remove the foreign key named party_foreign_key in the accounts table.
81
+ # remove_index :name => :party_foreign_key
82
+ def remove_foreign_key(options = {})
83
+ @base.remove_foreign_key(@table_name, options)
84
+ end
85
+
86
+ # Adds a :foreign_key option to TableDefinition.references.
87
+ # If :foreign_key is true, a foreign key constraint is added to the table.
88
+ # You can also specify a hash, which is passed as foreign key options.
89
+ #
90
+ # ===== Examples
91
+ # ====== Add goat_id column and a foreign key to the goats table.
92
+ # t.references(:goat, :foreign_key => true)
93
+ # ====== Add goat_id column and a cascading foreign key to the goats table.
94
+ # t.references(:goat, :foreign_key => {:dependent => :delete})
95
+ #
96
+ # Note: No foreign key is created if :polymorphic => true is used.
97
+ def references_with_foreign_keys(*args)
98
+ options = args.extract_options!
99
+ polymorphic = options[:polymorphic]
100
+ fk_options = options.delete(:foreign_key)
101
+
102
+ references_without_foreign_keys(*(args.dup << options))
103
+
104
+ if fk_options && !polymorphic
105
+ fk_options = {} if fk_options == true
106
+ args.each { |to_table| foreign_key(to_table, fk_options) }
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,75 @@
1
+ module Foreigner
2
+ module ConnectionAdapters
3
+ module SchemaStatements
4
+ def self.included(base)
5
+ base::AbstractAdapter.class_eval do
6
+ include Foreigner::ConnectionAdapters::AbstractAdapter
7
+ end
8
+ end
9
+ end
10
+
11
+ module AbstractAdapter
12
+ def supports_foreign_keys?
13
+ false
14
+ end
15
+
16
+ # Adds a new foreign key to the +from_table+, referencing the primary key of +to_table+
17
+ #
18
+ # The foreign key will be named after the from and to tables unless you pass
19
+ # <tt>:name</tt> as an option.
20
+ #
21
+ # ===== Examples
22
+ # ====== Creating a foreign key
23
+ # add_foreign_key(:comments, :posts)
24
+ # generates
25
+ # ALTER TABLE `comments` ADD CONSTRAINT
26
+ # `comments_post_id_fk` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`)
27
+ #
28
+ # ====== Creating a named foreign key
29
+ # add_foreign_key(:comments, :posts, :name => 'comments_belongs_to_posts')
30
+ # generates
31
+ # ALTER TABLE `comments` ADD CONSTRAINT
32
+ # `comments_belongs_to_posts` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`)
33
+ #
34
+ # ====== Creating a cascading foreign_key on a custom column
35
+ # add_foreign_key(:people, :people, :column => 'best_friend_id', :dependent => :nullify)
36
+ # generates
37
+ # ALTER TABLE `people` ADD CONSTRAINT
38
+ # `people_best_friend_id_fk` FOREIGN KEY (`best_friend_id`) REFERENCES `people` (`id`)
39
+ # ON DELETE SET NULL
40
+ #
41
+ # === Supported options
42
+ # [:column]
43
+ # Specify the column name on the from_table that references the to_table. By default this is guessed
44
+ # to be the singular name of the to_table with "_id" suffixed. So a to_table of :posts will use "post_id"
45
+ # as the default <tt>:column</tt>.
46
+ # [:primary_key]
47
+ # Specify the column name on the to_table that is referenced by this foreign key. By default this is
48
+ # assumed to be "id".
49
+ # [:name]
50
+ # Specify the name of the foreign key constraint. This defaults to use from_table and foreign key column.
51
+ # [:dependent]
52
+ # If set to <tt>:delete</tt>, the associated records in from_table are deleted when records in to_table table are deleted.
53
+ # If set to <tt>:nullify</tt>, the foreign key column is set to +NULL+.
54
+ def add_foreign_key(from_table, to_table, options = {})
55
+ end
56
+
57
+ # Remove the given foreign key from the table.
58
+ #
59
+ # ===== Examples
60
+ # ====== Remove the suppliers_company_id_fk in the suppliers table.
61
+ # remove_foreign_key :suppliers, :companies
62
+ # ====== Remove the foreign key named accounts_branch_id_fk in the accounts table.
63
+ # remove_foreign_key :accounts, :column => :branch_id
64
+ # ====== Remove the foreign key named party_foreign_key in the accounts table.
65
+ # remove_foreign_key :accounts, :name => :party_foreign_key
66
+ def remove_foreign_key(from_table, options)
67
+ end
68
+
69
+ # Return the foreign keys for the schema_dumper
70
+ def foreign_keys(table_name)
71
+ []
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,67 @@
1
+ module Foreigner
2
+ module ConnectionAdapters
3
+ module MysqlAdapter
4
+ include Foreigner::ConnectionAdapters::Sql2003
5
+
6
+ def remove_foreign_key(table, options)
7
+ if Hash === options
8
+ foreign_key_name = foreign_key_name(table, options[:column], options)
9
+ else
10
+ foreign_key_name = foreign_key_name(table, "#{options.to_s.gsub(/^\w+\./, '').singularize}_id")
11
+ end
12
+
13
+ execute "ALTER TABLE #{quote_table_name(table)} DROP FOREIGN KEY #{quote_column_name(foreign_key_name)}"
14
+ end
15
+
16
+ def foreign_keys(table_name)
17
+ fk_info = select_all %{
18
+ SELECT fk.referenced_table_name as 'to_table'
19
+ ,fk.referenced_table_schema as 'to_database'
20
+ ,fk.referenced_column_name as 'primary_key'
21
+ ,fk.column_name as 'column'
22
+ ,fk.constraint_name as 'name'
23
+ FROM information_schema.key_column_usage fk
24
+ WHERE fk.referenced_column_name is not null
25
+ AND fk.table_schema = '#{@config[:database]}'
26
+ AND fk.table_name = '#{table_name}'
27
+ }
28
+
29
+ create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
30
+
31
+ fk_info.map do |row|
32
+ options = {:column => row['column'], :name => row['name'], :primary_key => row['primary_key']}
33
+
34
+ if create_table_info =~ /CONSTRAINT #{quote_column_name(row['name'])} FOREIGN KEY .* REFERENCES .* ON DELETE (CASCADE|SET NULL)/
35
+ if $1 == 'CASCADE'
36
+ options[:dependent] = :delete
37
+ elsif $1 == 'SET NULL'
38
+ options[:dependent] = :nullify
39
+ end
40
+ end
41
+
42
+ if row['to_database'] != @config[:database]
43
+ options[:database] = row['to_database']
44
+ end
45
+
46
+ ForeignKeyDefinition.new(table_name, row['to_table'], options)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ module ActiveRecord
54
+ module ConnectionAdapters
55
+ if defined? MysqlAdapter
56
+ MysqlAdapter.class_eval do
57
+ include Foreigner::ConnectionAdapters::MysqlAdapter
58
+ end
59
+ end
60
+
61
+ if defined? JdbcAdapter
62
+ JdbcAdapter.class_eval do
63
+ include Foreigner::ConnectionAdapters::MysqlAdapter
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,64 @@
1
+ module Foreigner
2
+ module ConnectionAdapters
3
+ module PostgreSQLAdapter
4
+ include Foreigner::ConnectionAdapters::Sql2003
5
+
6
+ def remove_foreign_key(table, options)
7
+ if Hash === options
8
+ foreign_key_name = foreign_key_name(table, options[:column], options)
9
+ else
10
+ foreign_key_name = foreign_key_name(table, "#{options.to_s.gsub(/^\w+\./, '').singularize}_id")
11
+ end
12
+
13
+ execute "ALTER TABLE #{quote_table_name(table)} DROP CONSTRAINT #{quote_column_name(foreign_key_name)}"
14
+ end
15
+
16
+ def foreign_keys(table_name)
17
+ fk_info = select_all %{
18
+ SELECT tc.constraint_name as name
19
+ ,ccu.table_name as to_table
20
+ ,ccu.column_name as primary_key
21
+ ,kcu.column_name as column
22
+ ,rc.delete_rule as dependency
23
+ FROM information_schema.table_constraints tc
24
+ JOIN information_schema.key_column_usage kcu
25
+ USING (constraint_catalog, constraint_schema, constraint_name)
26
+ JOIN information_schema.referential_constraints rc
27
+ USING (constraint_catalog, constraint_schema, constraint_name)
28
+ JOIN information_schema.constraint_column_usage ccu
29
+ USING (constraint_catalog, constraint_schema, constraint_name)
30
+ WHERE tc.constraint_type = 'FOREIGN KEY'
31
+ AND tc.constraint_catalog = '#{@config[:database]}'
32
+ AND tc.table_name = '#{table_name}'
33
+ }
34
+
35
+ fk_info.map do |row|
36
+ options = {:column => row['column'], :name => row['name'], :primary_key => row['primary_key']}
37
+
38
+ if row['dependency'] == 'CASCADE'
39
+ options[:dependent] = :delete
40
+ elsif row['dependency'] == 'SET NULL'
41
+ options[:dependent] = :nullify
42
+ end
43
+ ForeignKeyDefinition.new(table_name, row['to_table'], options)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ module ActiveRecord
51
+ module ConnectionAdapters
52
+ if defined? PostgreSQLAdapter
53
+ PostgreSQLAdapter.class_eval do
54
+ include Foreigner::ConnectionAdapters::PostgreSQLAdapter
55
+ end
56
+ end
57
+
58
+ if defined? JdbcAdapter
59
+ JdbcAdapter.class_eval do
60
+ include Foreigner::ConnectionAdapters::PostgreSQLAdapter
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,42 @@
1
+ module Foreigner
2
+ module ConnectionAdapters
3
+ module Sql2003
4
+ def supports_foreign_keys?
5
+ true
6
+ end
7
+
8
+ def add_foreign_key(from_table, to_table, options = {})
9
+ column = options[:column] || "#{to_table.to_s.singularize}_id"
10
+ foreign_key_name = foreign_key_name(from_table, column, options)
11
+ primary_key = options[:primary_key] || "id"
12
+ dependency = dependency_sql(options[:dependent])
13
+
14
+ sql =
15
+ "ALTER TABLE #{quote_table_name(from_table)} " +
16
+ "ADD CONSTRAINT #{quote_column_name(foreign_key_name)} " +
17
+ "FOREIGN KEY (#{quote_column_name(column)}) " +
18
+ "REFERENCES #{quote_table_name(ActiveRecord::Migrator.proper_table_name(to_table))}(#{primary_key})"
19
+ sql << " #{dependency}" if dependency.present?
20
+
21
+ execute(sql)
22
+ end
23
+
24
+ private
25
+ def foreign_key_name(table, column, options = {})
26
+ if options[:name]
27
+ options[:name]
28
+ else
29
+ "#{table}_#{column}_fk"
30
+ end
31
+ end
32
+
33
+ def dependency_sql(dependency)
34
+ case dependency
35
+ when :nullify then "ON DELETE SET NULL"
36
+ when :delete then "ON DELETE CASCADE"
37
+ else ""
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,49 @@
1
+ module Foreigner
2
+ module SchemaDumper
3
+ def self.included(base)
4
+ base.class_eval do
5
+ include InstanceMethods
6
+ alias_method_chain :tables, :foreign_keys
7
+ end
8
+ end
9
+
10
+ module InstanceMethods
11
+ def tables_with_foreign_keys(stream)
12
+ tables_without_foreign_keys(stream)
13
+ @connection.tables.sort.each do |table|
14
+ foreign_keys(table, stream)
15
+ end
16
+ end
17
+
18
+ private
19
+ def foreign_keys(table_name, stream)
20
+ if (foreign_keys = @connection.foreign_keys(table_name)).any?
21
+ add_foreign_key_statements = foreign_keys.map do |foreign_key|
22
+ statement_parts = [ ('add_foreign_key ' + foreign_key.from_table.inspect) ]
23
+ if options[:database].present?
24
+ statement_parts << %Q["#{options[:database]}.#{foreign_key.to_table.to_s}"]
25
+ else
26
+ statement_parts << foreign_key.to_table.inspect
27
+ end
28
+ statement_parts << (':name => ' + foreign_key.options[:name].inspect)
29
+
30
+ if foreign_key.options[:column] != "#{foreign_key.to_table.singularize}_id"
31
+ statement_parts << (':column => ' + foreign_key.options[:column].inspect)
32
+ end
33
+ if foreign_key.options[:primary_key] != 'id'
34
+ statement_parts << (':primary_key => ' + foreign_key.options[:primary_key].inspect)
35
+ end
36
+ if foreign_key.options[:dependent].present?
37
+ statement_parts << (':dependent => ' + foreign_key.options[:dependent].inspect)
38
+ end
39
+
40
+ ' ' + statement_parts.join(', ')
41
+ end
42
+
43
+ stream.puts add_foreign_key_statements.sort.join("\n")
44
+ stream.puts
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
data/lib/foreigner.rb ADDED
@@ -0,0 +1,57 @@
1
+ require 'foreigner/connection_adapters/abstract/schema_statements'
2
+ require 'foreigner/connection_adapters/abstract/schema_definitions'
3
+ require 'foreigner/connection_adapters/sql_2003'
4
+ require 'foreigner/schema_dumper'
5
+
6
+ module Foreigner
7
+ mattr_accessor :adapters
8
+ self.adapters = {}
9
+
10
+ class << self
11
+ def register(adapter_name, file_name)
12
+ adapters[adapter_name] = file_name
13
+ end
14
+
15
+ def load_adapter!
16
+ if adapters.key?(configured_adapter)
17
+ require adapters[configured_adapter]
18
+ end
19
+ end
20
+
21
+ def configured_adapter
22
+ ActiveRecord::Base.connection.adapter_name.downcase
23
+ end
24
+
25
+ def on_load(&block)
26
+ if defined?(Rails) && Rails.version >= '3.0'
27
+ ActiveSupport.on_load :active_record do
28
+ unless ActiveRecord::Base.connected?
29
+ ActiveRecord::Base.configurations = Rails.application.config.database_configuration
30
+ ActiveRecord::Base.establish_connection
31
+ end
32
+ block.call
33
+ end
34
+ else
35
+ yield
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ Foreigner.register 'mysql', 'foreigner/connection_adapters/mysql_adapter'
42
+ Foreigner.register 'postgresql', 'foreigner/connection_adapters/postgresql_adapter'
43
+
44
+ Foreigner.on_load do
45
+ module ActiveRecord
46
+ module ConnectionAdapters
47
+ include Foreigner::ConnectionAdapters::SchemaStatements
48
+ include Foreigner::ConnectionAdapters::SchemaDefinitions
49
+ end
50
+
51
+ SchemaDumper.class_eval do
52
+ include Foreigner::SchemaDumper
53
+ end
54
+ end
55
+
56
+ Foreigner.load_adapter!
57
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :foreigner do
3
+ # # Task goes here
4
+ # end
data/test/helper.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+ require 'active_support'
4
+ require 'active_support/test_case'
5
+ require 'active_record'
6
+ require 'active_record/test_case'
7
+ require 'active_record/connection_adapters/mysql_adapter'
8
+ require 'foreigner'
@@ -0,0 +1,97 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class MysqlAdapterTest < ActiveRecord::TestCase
4
+ include Foreigner::MysqlAdapter
5
+
6
+ def test_add_without_options
7
+ assert_equal(
8
+ "ALTER TABLE `employees` ADD CONSTRAINT `employees_company_id_fk` FOREIGN KEY (`company_id`) REFERENCES `companies`(id)",
9
+ add_foreign_key(:employees, :companies)
10
+ )
11
+ end
12
+
13
+ def test_add_without_options_with_database
14
+ assert_equal(
15
+ "ALTER TABLE `employees` ADD CONSTRAINT `employees_company_id_fk` FOREIGN KEY (`company_id`) REFERENCES `other_db`.`companies`(id)",
16
+ add_foreign_key(:employees, 'other_db.companies')
17
+ )
18
+ end
19
+
20
+ def test_add_with_name
21
+ assert_equal(
22
+ "ALTER TABLE `employees` ADD CONSTRAINT `favorite_company_fk` FOREIGN KEY (`company_id`) REFERENCES `companies`(id)",
23
+ add_foreign_key(:employees, :companies, :name => 'favorite_company_fk')
24
+ )
25
+ end
26
+
27
+ def test_add_with_column
28
+ assert_equal(
29
+ "ALTER TABLE `employees` ADD CONSTRAINT `employees_last_employer_id_fk` FOREIGN KEY (`last_employer_id`) REFERENCES `companies`(id)",
30
+ add_foreign_key(:employees, :companies, :column => 'last_employer_id')
31
+ )
32
+ end
33
+
34
+ def test_add_with_column_and_name
35
+ assert_equal(
36
+ "ALTER TABLE `employees` ADD CONSTRAINT `favorite_company_fk` FOREIGN KEY (`last_employer_id`) REFERENCES `companies`(id)",
37
+ add_foreign_key(:employees, :companies, :column => 'last_employer_id', :name => 'favorite_company_fk')
38
+ )
39
+ end
40
+
41
+ def test_add_with_delete_dependency
42
+ assert_equal(
43
+ "ALTER TABLE `employees` ADD CONSTRAINT `employees_company_id_fk` FOREIGN KEY (`company_id`) REFERENCES `companies`(id) " +
44
+ "ON DELETE CASCADE",
45
+ add_foreign_key(:employees, :companies, :dependent => :delete)
46
+ )
47
+ end
48
+
49
+ def test_add_with_nullify_dependency
50
+ assert_equal(
51
+ "ALTER TABLE `employees` ADD CONSTRAINT `employees_company_id_fk` FOREIGN KEY (`company_id`) REFERENCES `companies`(id) " +
52
+ "ON DELETE SET NULL",
53
+ add_foreign_key(:employees, :companies, :dependent => :nullify)
54
+ )
55
+ end
56
+
57
+ def test_remove_by_table
58
+ assert_equal(
59
+ "ALTER TABLE `suppliers` DROP FOREIGN KEY `suppliers_company_id_fk`",
60
+ remove_foreign_key(:suppliers, :companies)
61
+ )
62
+ end
63
+
64
+ def test_remove_by_table_with_database
65
+ assert_equal(
66
+ "ALTER TABLE `suppliers` DROP FOREIGN KEY `suppliers_company_id_fk`",
67
+ remove_foreign_key(:suppliers, 'other_db.companies')
68
+ )
69
+ end
70
+
71
+ def test_remove_by_name
72
+ assert_equal(
73
+ "ALTER TABLE `suppliers` DROP FOREIGN KEY `belongs_to_supplier`",
74
+ remove_foreign_key(:suppliers, :name => "belongs_to_supplier")
75
+ )
76
+ end
77
+
78
+ def test_remove_by_column
79
+ assert_equal(
80
+ "ALTER TABLE `suppliers` DROP FOREIGN KEY `suppliers_ship_to_id_fk`",
81
+ remove_foreign_key(:suppliers, :column => "ship_to_id")
82
+ )
83
+ end
84
+
85
+ private
86
+ def execute(sql, name = nil)
87
+ sql
88
+ end
89
+
90
+ def quote_column_name(name)
91
+ "`#{name}`"
92
+ end
93
+
94
+ def quote_table_name(name)
95
+ quote_column_name(name).gsub('.', '`.`')
96
+ end
97
+ end
data/uninstall.rb ADDED
@@ -0,0 +1 @@
1
+ # Uninstall hook code here
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: brianjlandau-foreigner
3
+ version: !ruby/object:Gem::Version
4
+ hash: 3
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 7
9
+ - 0
10
+ version: 0.7.0
11
+ platform: ruby
12
+ authors:
13
+ - Matthew Higgins
14
+ - Brian Landau
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2010-06-23 00:00:00 -04:00
20
+ default_executable:
21
+ dependencies: []
22
+
23
+ description: Adds helpers to migrations and correctly dumps foreign keys to schema.rb
24
+ email: brianjlandau@gmail.com
25
+ executables: []
26
+
27
+ extensions: []
28
+
29
+ extra_rdoc_files:
30
+ - README
31
+ files:
32
+ - MIT-LICENSE
33
+ - README
34
+ - Rakefile
35
+ - VERSION
36
+ - brianjlandau-foreigner.gemspec
37
+ - init.rb
38
+ - install.rb
39
+ - lib/foreigner.rb
40
+ - lib/foreigner/connection_adapters/abstract/schema_definitions.rb
41
+ - lib/foreigner/connection_adapters/abstract/schema_statements.rb
42
+ - lib/foreigner/connection_adapters/mysql_adapter.rb
43
+ - lib/foreigner/connection_adapters/postgresql_adapter.rb
44
+ - lib/foreigner/connection_adapters/sql_2003.rb
45
+ - lib/foreigner/schema_dumper.rb
46
+ - tasks/foreigner_tasks.rake
47
+ - test/helper.rb
48
+ - test/mysql_adapter_test.rb
49
+ - uninstall.rb
50
+ has_rdoc: true
51
+ homepage: http://github.com/brianjlandau/foreigner
52
+ licenses: []
53
+
54
+ post_install_message:
55
+ rdoc_options:
56
+ - --charset=UTF-8
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ hash: 3
65
+ segments:
66
+ - 0
67
+ version: "0"
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ hash: 3
74
+ segments:
75
+ - 0
76
+ version: "0"
77
+ requirements: []
78
+
79
+ rubyforge_project:
80
+ rubygems_version: 1.3.7
81
+ signing_key:
82
+ specification_version: 3
83
+ summary: Foreign keys for Rails
84
+ test_files:
85
+ - test/helper.rb
86
+ - test/mysql_adapter_test.rb