automatic_foreign_key 1.0.5 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,6 @@
1
+ 1.1
2
+ * create index within foreign key (auto index)
3
+ * global configuration for: auto_index and on_update/on_delete actions
1
4
  1.0.2
2
5
  * rails 3 generator
3
6
  1.0.1
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 Migrations is a gem that automatically generates foreign-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.5
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.5"
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 = ["Michał Łomnicki"]
12
- s.date = %q{2010-04-06}
13
- s.description = %q{Automatic Key Migrations is a gem that automatically generates foreign-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
- add_foreign_key(table_name, column_name, references.first, references.last, options) if references
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
@@ -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
- 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? { |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
- - 5
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-04-06 00:00:00 +02:00
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 Migrations is a gem that automatically generates foreign-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