automatic_foreign_key 1.0.5 → 1.1.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/CHANGELOG +3 -0
- data/README.rdoc +27 -0
- data/Rakefile +2 -2
- data/VERSION +1 -1
- data/automatic_foreign_key.gemspec +10 -6
- data/lib/automatic_foreign_key.rb +12 -0
- data/lib/automatic_foreign_key/active_record/migration.rb +28 -3
- data/spec/migration_spec.rb +71 -2
- data/spec/support/matchers/automatic_foreign_key_matchers.rb +2 -52
- data/spec/support/matchers/have_index.rb +52 -0
- data/spec/support/matchers/reference.rb +66 -0
- metadata +9 -5
data/CHANGELOG
CHANGED
data/README.rdoc
CHANGED
@@ -77,6 +77,33 @@ configuration properties:
|
|
77
77
|
* <code>config.active_record.table_name_prefix</code>
|
78
78
|
* <code>config.active_record.table_name_suffix</code>
|
79
79
|
|
80
|
+
=== Auto Indices ===
|
81
|
+
|
82
|
+
It's very common to create an index on foreign key. You can instruct
|
83
|
+
AutomaticForeignKey to add an index after adding foreign key.
|
84
|
+
|
85
|
+
create_table :orders, :id => false do |t|
|
86
|
+
...
|
87
|
+
t.integer :order_id, :index => true
|
88
|
+
end
|
89
|
+
|
90
|
+
If you want to pass some options for index use hash params.
|
91
|
+
|
92
|
+
create_table :bills, :id => false do |t|
|
93
|
+
...
|
94
|
+
t.integer :order_id, :index => { :unique => true, :name => 'foo_index' }
|
95
|
+
end
|
96
|
+
|
97
|
+
That option is useless for MySQL users as their RDBMS adds indices on foreign
|
98
|
+
keys by default. However PostgreSQL users may have fun with that feature.
|
99
|
+
|
100
|
+
=== Configuration ===
|
101
|
+
|
102
|
+
For customization purposes create config/initializers/automatic_foreign_key.rb file:
|
103
|
+
AutomaticForeignKey.auto_index = true # create indices on FKs by default
|
104
|
+
AutomaticForeignKey.on_update = :cascade # cascade as default on_update action
|
105
|
+
AutomaticForeignKey.on_delete = :restrtic # restrict as default on_delete action
|
106
|
+
|
80
107
|
=== Rails 3 compatibility
|
81
108
|
|
82
109
|
Automatic foreign key is fully compatibly with Rails 3.
|
data/Rakefile
CHANGED
@@ -7,8 +7,8 @@ begin
|
|
7
7
|
Jeweler::Tasks.new do |gem|
|
8
8
|
gem.name = "automatic_foreign_key"
|
9
9
|
gem.summary = %Q{Automatically generate foreign-key constraints when creating tables}
|
10
|
-
gem.description = %Q{Automatic Key
|
11
|
-
constraints when creating tables. It uses SQL-92 syntax and as such should be compatible with most databases that support foreign-key constraints.}
|
10
|
+
gem.description = %Q{Automatic Foreign Key automatically generates foreign-key
|
11
|
+
constraints when creating tables or adding columns. It uses SQL-92 syntax and as such should be compatible with most databases that support foreign-key constraints.}
|
12
12
|
gem.email = "michal.lomnicki@gmail.com"
|
13
13
|
gem.homepage = "http://github.com/mlomnicki/automatic_foreign_key"
|
14
14
|
gem.authors = ["Michał Łomnicki"]
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0
|
1
|
+
1.1.0
|
@@ -5,13 +5,13 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{automatic_foreign_key}
|
8
|
-
s.version = "1.0
|
8
|
+
s.version = "1.1.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
-
s.authors = ["
|
12
|
-
s.date = %q{2010-
|
13
|
-
s.description = %q{Automatic Key
|
14
|
-
constraints when creating tables. It uses SQL-92 syntax and as such should be compatible with most databases that support foreign-key constraints.}
|
11
|
+
s.authors = ["Micha\305\202 \305\201omnicki"]
|
12
|
+
s.date = %q{2010-06-12}
|
13
|
+
s.description = %q{Automatic Foreign Key automatically generates foreign-key
|
14
|
+
constraints when creating tables or adding columns. It uses SQL-92 syntax and as such should be compatible with most databases that support foreign-key constraints.}
|
15
15
|
s.email = %q{michal.lomnicki@gmail.com}
|
16
16
|
s.extra_rdoc_files = [
|
17
17
|
"README.rdoc"
|
@@ -43,7 +43,9 @@ constraints when creating tables. It uses SQL-92 syntax and as such should be co
|
|
43
43
|
"spec/references_spec.rb",
|
44
44
|
"spec/schema/schema.rb",
|
45
45
|
"spec/spec_helper.rb",
|
46
|
-
"spec/support/matchers/automatic_foreign_key_matchers.rb"
|
46
|
+
"spec/support/matchers/automatic_foreign_key_matchers.rb",
|
47
|
+
"spec/support/matchers/have_index.rb",
|
48
|
+
"spec/support/matchers/reference.rb"
|
47
49
|
]
|
48
50
|
s.homepage = %q{http://github.com/mlomnicki/automatic_foreign_key}
|
49
51
|
s.rdoc_options = ["--charset=UTF-8"]
|
@@ -55,7 +57,9 @@ constraints when creating tables. It uses SQL-92 syntax and as such should be co
|
|
55
57
|
"spec/connections/postgresql/connection.rb",
|
56
58
|
"spec/connections/mysql/connection.rb",
|
57
59
|
"spec/aaa_create_tables_spec.rb",
|
60
|
+
"spec/support/matchers/have_index.rb",
|
58
61
|
"spec/support/matchers/automatic_foreign_key_matchers.rb",
|
62
|
+
"spec/support/matchers/reference.rb",
|
59
63
|
"spec/spec_helper.rb",
|
60
64
|
"spec/references_spec.rb",
|
61
65
|
"spec/migration_spec.rb",
|
@@ -16,6 +16,18 @@ module AutomaticForeignKey
|
|
16
16
|
end
|
17
17
|
|
18
18
|
end
|
19
|
+
|
20
|
+
# Default FK update action
|
21
|
+
mattr_accessor :on_update
|
22
|
+
|
23
|
+
# Default FK delete action
|
24
|
+
mattr_accessor :on_delete
|
25
|
+
|
26
|
+
# Create an index after creating FK (default false)
|
27
|
+
mattr_accessor :auto_index
|
28
|
+
@@auto_index = nil
|
29
|
+
|
30
|
+
|
19
31
|
end
|
20
32
|
|
21
33
|
ActiveRecord::Base.send(:include, AutomaticForeignKey::ActiveRecord::Base)
|
@@ -11,19 +11,44 @@ module AutomaticForeignKey::ActiveRecord
|
|
11
11
|
# add_column('comments', 'post_id', :integer)
|
12
12
|
# # creates a column and adds foreign key on posts(id)
|
13
13
|
#
|
14
|
+
# add_column('comments', 'post_id', :integer, :on_update => :cascade, :on_delete => :cascade)
|
15
|
+
# # creates a column and adds foreign key on posts(id) with cascade actions on update and on delete
|
16
|
+
#
|
17
|
+
# add_column('comments', 'post_id', :integer, :index => true)
|
18
|
+
# # creates a column and adds foreign key on posts(id)
|
19
|
+
# # additionally adds index on posts(id)
|
20
|
+
#
|
21
|
+
# add_column('comments', 'post_id', :integer, :index => { :unique => true, :name => 'comments_post_id_unique_index' }))
|
22
|
+
# # creates a column and adds foreign key on posts(id)
|
23
|
+
# # additionally adds unique index on posts(id) named comments_post_id_unique_index
|
24
|
+
#
|
14
25
|
# add_column('addresses', 'citizen_id', :integer, :references => :users
|
15
26
|
# # creates a column and adds foreign key on users(id)
|
16
27
|
#
|
17
28
|
# add_column('addresses', 'citizen_id', :integer, :references => [:users, :uuid]
|
18
29
|
# # creates a column and adds foreign key on users(uuid)
|
19
30
|
#
|
20
|
-
# add_column('users', 'verified')
|
21
|
-
# # just creates a column as usually
|
22
31
|
def add_column(table_name, column_name, type, options = {})
|
23
32
|
super
|
33
|
+
index = options.fetch(:index, AutomaticForeignKey.auto_index)
|
24
34
|
references = ActiveRecord::Base.references(table_name, column_name, options)
|
25
|
-
|
35
|
+
if references
|
36
|
+
set_default_update_and_delete_actions!(options)
|
37
|
+
add_foreign_key(table_name, column_name, references.first, references.last, options)
|
38
|
+
end
|
39
|
+
add_index(table_name, column_name, options_for_index(index)) if index
|
26
40
|
end
|
41
|
+
|
42
|
+
private
|
43
|
+
def options_for_index(index)
|
44
|
+
index.is_a?(Hash) ? index : {}
|
45
|
+
end
|
46
|
+
|
47
|
+
def set_default_update_and_delete_actions!(options)
|
48
|
+
options[:on_update] = options.fetch(:on_update, AutomaticForeignKey.on_update)
|
49
|
+
options[:on_delete] = options.fetch(:on_delete, AutomaticForeignKey.on_delete)
|
50
|
+
end
|
51
|
+
|
27
52
|
end
|
28
53
|
end
|
29
54
|
end
|
data/spec/migration_spec.rb
CHANGED
@@ -68,6 +68,71 @@ describe ActiveRecord::Migration do
|
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
71
|
+
it "should create an index if specified" do
|
72
|
+
add_column(:post_id, :integer, :index => true) do
|
73
|
+
@model.should have_index.on(:post_id)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should create a unique index if specified" do
|
78
|
+
add_column(:post_id, :integer, :index => { :unique => true }) do
|
79
|
+
@model.should have_unique_index.on(:post_id)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should allow custom name for index" do
|
84
|
+
index_name = 'comments_post_id_unique_index'
|
85
|
+
add_column(:post_id, :integer, :index => { :unique => true, :name => index_name }) do
|
86
|
+
@model.should have_unique_index(:name => index_name).on(:post_id)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should auto-create index if specified in global options" do
|
91
|
+
AutomaticForeignKey.auto_index = true
|
92
|
+
add_column(:post_id, :integer) do
|
93
|
+
@model.should have_index.on(:post_id)
|
94
|
+
end
|
95
|
+
AutomaticForeignKey.auto_index = false
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should allow to overwrite auto_index options in column definition" do
|
99
|
+
AutomaticForeignKey.auto_index = true
|
100
|
+
add_column(:post_id, :integer, :index => false) do
|
101
|
+
# MySQL creates an index on foreign by default
|
102
|
+
# and we can do nothing with that
|
103
|
+
unless mysql?
|
104
|
+
@model.should_not have_index.on(:post_id)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
AutomaticForeignKey.auto_index = false
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should use default on_update action" do
|
111
|
+
AutomaticForeignKey.on_update = :cascade
|
112
|
+
add_column(:post_id, :integer) do
|
113
|
+
@model.should reference.on(:post_id).on_update(:cascade)
|
114
|
+
end
|
115
|
+
AutomaticForeignKey.on_update = nil
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should use default on_delete action" do
|
119
|
+
AutomaticForeignKey.on_delete = :cascade
|
120
|
+
add_column(:post_id, :integer) do
|
121
|
+
@model.should reference.on(:post_id).on_delete(:cascade)
|
122
|
+
end
|
123
|
+
AutomaticForeignKey.on_delete = nil
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should allow to overwrite default actions" do
|
127
|
+
AutomaticForeignKey.on_delete = :cascade
|
128
|
+
AutomaticForeignKey.on_update = :restrict
|
129
|
+
add_column(:post_id, :integer, :on_update => :set_null, :on_delete => :set_null) do
|
130
|
+
@model.should reference.on(:post_id).on_delete(:set_null).on_update(:set_null)
|
131
|
+
end
|
132
|
+
AutomaticForeignKey.on_delete = nil
|
133
|
+
end
|
134
|
+
|
135
|
+
protected
|
71
136
|
def add_column(column_name, *args)
|
72
137
|
table = @model.table_name
|
73
138
|
ActiveRecord::Migration.suppress_messages do
|
@@ -81,10 +146,14 @@ describe ActiveRecord::Migration do
|
|
81
146
|
end
|
82
147
|
|
83
148
|
def foreign_key(model, column)
|
84
|
-
columns = Array(column)
|
149
|
+
columns = Array(column).collect(&:to_s)
|
85
150
|
model.foreign_keys.detect { |fk| fk.table_name == model.table_name && fk.column_names == columns }
|
86
151
|
end
|
87
|
-
|
152
|
+
|
153
|
+
def mysql?
|
154
|
+
ActiveRecord::Base.connection.adapter_name == 'MySQL'
|
155
|
+
end
|
156
|
+
|
88
157
|
def create_table(model, columns_with_options)
|
89
158
|
ActiveRecord::Migration.suppress_messages do
|
90
159
|
ActiveRecord::Migration.create_table model.table_name, :force => true do |t|
|
@@ -1,52 +1,2 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
class Reference
|
4
|
-
def initialize(expected)
|
5
|
-
@column_names = nil
|
6
|
-
unless expected.empty?
|
7
|
-
@references_column_names = Array(expected).collect(&:to_s)
|
8
|
-
@references_table_name = @references_column_names.shift
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
def matches?(model)
|
13
|
-
@model = model
|
14
|
-
if @references_table_name
|
15
|
-
@result = @model.foreign_keys.select do |fk|
|
16
|
-
fk.references_table_name == @references_table_name &&
|
17
|
-
fk.references_column_names == @references_column_names
|
18
|
-
end
|
19
|
-
else
|
20
|
-
@result = @model.foreign_keys
|
21
|
-
end
|
22
|
-
if @column_names
|
23
|
-
@result.any? { |fk| fk.column_names == @column_names }
|
24
|
-
else
|
25
|
-
!!@result
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def failure_message_for_should(should_not = false)
|
30
|
-
target_column_names = @column_names.present? ? "(#{@column_names.join(', ')})" : ""
|
31
|
-
destinantion_column_names = @references_table_name ? "#{@references_table_name}(#{@references_column_names.join(', ')})" : "anything"
|
32
|
-
invert = should_not ? 'not' : ''
|
33
|
-
"Expected #{@model.table_name}#{target_column_names} #{invert} to reference #{destinantion_column_names}"
|
34
|
-
end
|
35
|
-
|
36
|
-
def failure_message_for_should_not
|
37
|
-
failure_message_for_should(true)
|
38
|
-
end
|
39
|
-
|
40
|
-
def on(*column_names)
|
41
|
-
@column_names = column_names.collect(&:to_s)
|
42
|
-
self
|
43
|
-
end
|
44
|
-
|
45
|
-
end
|
46
|
-
|
47
|
-
def reference(*expect)
|
48
|
-
Reference.new(expect)
|
49
|
-
end
|
50
|
-
|
51
|
-
end
|
52
|
-
|
1
|
+
require 'support/matchers/reference'
|
2
|
+
require 'support/matchers/have_index'
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module AutomaticForeignKeyMatchers
|
2
|
+
|
3
|
+
class HaveIndex
|
4
|
+
|
5
|
+
def initialize(expectation, options = {})
|
6
|
+
set_required_columns(expectation, options)
|
7
|
+
end
|
8
|
+
|
9
|
+
def matches?(model)
|
10
|
+
@model = model
|
11
|
+
@model.indexes.any? do |index|
|
12
|
+
index.columns.to_set == @required_columns &&
|
13
|
+
(@unique ? index.unique : true) &&
|
14
|
+
(@name ? index.name == @name.to_s : true)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def failure_message_for_should(should_not = false)
|
19
|
+
invert = should_not ? "not to" : ""
|
20
|
+
"Expected #{@model.table_name} to #{invert} contain index on #{@required_columns.entries.inspect}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def failure_message_for_should_not
|
24
|
+
failure_message_for_should(true)
|
25
|
+
end
|
26
|
+
|
27
|
+
def on(expectation)
|
28
|
+
set_required_columns(expectation)
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def set_required_columns(expectation, options = {})
|
34
|
+
@required_columns = Array(expectation).collect(&:to_s).to_set
|
35
|
+
@unique = options.delete(:unique)
|
36
|
+
@name = options.delete(:name)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
def have_index(*expectation)
|
42
|
+
options = expectation.extract_options!
|
43
|
+
HaveIndex.new(expectation, options)
|
44
|
+
end
|
45
|
+
|
46
|
+
def have_unique_index(*expectation)
|
47
|
+
options = expectation.extract_options!
|
48
|
+
options[:unique] = true
|
49
|
+
HaveIndex.new(expectation, options)
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module AutomaticForeignKeyMatchers
|
2
|
+
|
3
|
+
class Reference
|
4
|
+
def initialize(expected)
|
5
|
+
@column_names = nil
|
6
|
+
unless expected.empty?
|
7
|
+
@references_column_names = Array(expected).collect(&:to_s)
|
8
|
+
@references_table_name = @references_column_names.shift
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def matches?(model)
|
13
|
+
@model = model
|
14
|
+
if @references_table_name
|
15
|
+
@result = @model.foreign_keys.select do |fk|
|
16
|
+
fk.references_table_name == @references_table_name &&
|
17
|
+
fk.references_column_names == @references_column_names
|
18
|
+
end
|
19
|
+
else
|
20
|
+
@result = @model.foreign_keys
|
21
|
+
end
|
22
|
+
if @column_names
|
23
|
+
@result.any? do |fk|
|
24
|
+
fk.column_names == @column_names &&
|
25
|
+
(@on_update ? fk.on_update == @on_update : true) &&
|
26
|
+
(@on_delete ? fk.on_delete == @on_delete : true)
|
27
|
+
end
|
28
|
+
else
|
29
|
+
!!@result
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def failure_message_for_should(should_not = false)
|
34
|
+
target_column_names = @column_names.present? ? "(#{@column_names.join(', ')})" : ""
|
35
|
+
destinantion_column_names = @references_table_name ? "#{@references_table_name}(#{@references_column_names.join(', ')})" : "anything"
|
36
|
+
invert = should_not ? 'not' : ''
|
37
|
+
"Expected #{@model.table_name}#{target_column_names} #{invert} to reference #{destinantion_column_names}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def failure_message_for_should_not
|
41
|
+
failure_message_for_should(true)
|
42
|
+
end
|
43
|
+
|
44
|
+
def on(*column_names)
|
45
|
+
@column_names = column_names.collect(&:to_s)
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def on_update(action)
|
50
|
+
@on_update = action
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
def on_delete(action)
|
55
|
+
@on_delete = action
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
def reference(*expect)
|
62
|
+
Reference.new(expect)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 1
|
7
|
+
- 1
|
7
8
|
- 0
|
8
|
-
|
9
|
-
version: 1.0.5
|
9
|
+
version: 1.1.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- "Micha\xC5\x82 \xC5\x81omnicki"
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-
|
17
|
+
date: 2010-06-12 00:00:00 +02:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -46,8 +46,8 @@ dependencies:
|
|
46
46
|
type: :runtime
|
47
47
|
version_requirements: *id002
|
48
48
|
description: |-
|
49
|
-
Automatic Key
|
50
|
-
constraints when creating tables. It uses SQL-92 syntax and as such should be compatible with most databases that support foreign-key constraints.
|
49
|
+
Automatic Foreign Key automatically generates foreign-key
|
50
|
+
constraints when creating tables or adding columns. It uses SQL-92 syntax and as such should be compatible with most databases that support foreign-key constraints.
|
51
51
|
email: michal.lomnicki@gmail.com
|
52
52
|
executables: []
|
53
53
|
|
@@ -83,6 +83,8 @@ files:
|
|
83
83
|
- spec/schema/schema.rb
|
84
84
|
- spec/spec_helper.rb
|
85
85
|
- spec/support/matchers/automatic_foreign_key_matchers.rb
|
86
|
+
- spec/support/matchers/have_index.rb
|
87
|
+
- spec/support/matchers/reference.rb
|
86
88
|
has_rdoc: true
|
87
89
|
homepage: http://github.com/mlomnicki/automatic_foreign_key
|
88
90
|
licenses: []
|
@@ -118,7 +120,9 @@ test_files:
|
|
118
120
|
- spec/connections/postgresql/connection.rb
|
119
121
|
- spec/connections/mysql/connection.rb
|
120
122
|
- spec/aaa_create_tables_spec.rb
|
123
|
+
- spec/support/matchers/have_index.rb
|
121
124
|
- spec/support/matchers/automatic_foreign_key_matchers.rb
|
125
|
+
- spec/support/matchers/reference.rb
|
122
126
|
- spec/spec_helper.rb
|
123
127
|
- spec/references_spec.rb
|
124
128
|
- spec/migration_spec.rb
|