schema_plus 0.4.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/.gitignore +3 -1
  2. data/.travis.yml +6 -9
  3. data/Gemfile +0 -4
  4. data/README.rdoc +168 -70
  5. data/Rakefile +58 -47
  6. data/gemfiles/rails-3.2/Gemfile.base +4 -0
  7. data/gemfiles/rails-3.2/Gemfile.mysql +4 -0
  8. data/gemfiles/rails-3.2/Gemfile.mysql2 +4 -0
  9. data/gemfiles/rails-3.2/Gemfile.postgresql +4 -0
  10. data/gemfiles/rails-3.2/Gemfile.sqlite3 +4 -0
  11. data/lib/schema_plus.rb +2 -0
  12. data/lib/schema_plus/active_record/column_options_handler.rb +73 -32
  13. data/lib/schema_plus/active_record/connection_adapters/abstract_adapter.rb +60 -31
  14. data/lib/schema_plus/active_record/connection_adapters/foreign_key_definition.rb +7 -2
  15. data/lib/schema_plus/active_record/connection_adapters/index_definition.rb +2 -1
  16. data/lib/schema_plus/active_record/connection_adapters/mysql_adapter.rb +19 -1
  17. data/lib/schema_plus/active_record/connection_adapters/postgresql_adapter.rb +68 -17
  18. data/lib/schema_plus/active_record/connection_adapters/sqlite3_adapter.rb +28 -3
  19. data/lib/schema_plus/active_record/connection_adapters/table_definition.rb +27 -1
  20. data/lib/schema_plus/active_record/db_default.rb +19 -0
  21. data/lib/schema_plus/active_record/foreign_keys.rb +40 -32
  22. data/lib/schema_plus/active_record/schema_dumper.rb +7 -3
  23. data/lib/schema_plus/version.rb +1 -1
  24. data/runspecs +5 -8
  25. data/schema_plus.gemspec +2 -5
  26. data/spec/column_definition_spec.rb +18 -1
  27. data/spec/column_spec.rb +39 -2
  28. data/spec/connection_spec.rb +1 -1
  29. data/spec/connections/mysql/connection.rb +1 -1
  30. data/spec/connections/mysql2/connection.rb +1 -1
  31. data/spec/connections/postgresql/connection.rb +1 -1
  32. data/spec/foreign_key_definition_spec.rb +0 -4
  33. data/spec/foreign_key_spec.rb +37 -13
  34. data/spec/index_definition_spec.rb +54 -2
  35. data/spec/index_spec.rb +59 -15
  36. data/spec/migration_spec.rb +336 -85
  37. data/spec/multiple_schemas_spec.rb +127 -0
  38. data/spec/schema_dumper_spec.rb +65 -25
  39. data/spec/schema_spec.rb +16 -18
  40. data/spec/spec_helper.rb +19 -18
  41. data/spec/support/matchers/reference.rb +7 -1
  42. data/spec/views_spec.rb +5 -2
  43. metadata +43 -54
  44. data/gemfiles/Gemfile.rails-2.3 +0 -6
  45. data/gemfiles/Gemfile.rails-2.3.lock +0 -65
  46. data/gemfiles/Gemfile.rails-3.0 +0 -5
  47. data/gemfiles/Gemfile.rails-3.0.lock +0 -113
  48. data/gemfiles/Gemfile.rails-3.1 +0 -5
  49. data/gemfiles/Gemfile.rails-3.1.lock +0 -123
  50. data/gemfiles/Gemfile.rails-3.2 +0 -5
  51. data/gemfiles/Gemfile.rails-3.2.lock +0 -121
  52. data/spec/models/comment.rb +0 -2
  53. data/spec/models/post.rb +0 -2
  54. data/spec/models/user.rb +0 -2
  55. data/spec/rails3_migration_spec.rb +0 -144
@@ -2,10 +2,6 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
3
  describe "Foreign Key definition" do
4
4
 
5
- before(:all) do
6
- load_core_schema
7
- end
8
-
9
5
  let(:definition) { SchemaPlus::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new("posts_user_fkey", :posts, :user, :users, :id) }
10
6
 
11
7
  it "it is dumped to sql with quoted values" do
@@ -1,16 +1,22 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
- require 'models/user'
4
- require 'models/post'
5
- require 'models/comment'
6
-
7
3
  describe "Foreign Key" do
8
-
4
+
9
5
  let(:migration) { ::ActiveRecord::Migration }
10
6
 
11
7
  context "created with table" do
12
8
  before(:all) do
13
- load_auto_schema
9
+ define_schema(:auto_create => true) do
10
+ create_table :users, :force => true do |t|
11
+ t.string :login
12
+ end
13
+ create_table :comments, :force => true do |t|
14
+ t.integer :user_id
15
+ t.foreign_key :user_id, :users, :id
16
+ end
17
+ end
18
+ class User < ::ActiveRecord::Base ; end
19
+ class Comment < ::ActiveRecord::Base ; end
14
20
  end
15
21
 
16
22
  it "should report foreign key constraints" do
@@ -26,7 +32,27 @@ describe "Foreign Key" do
26
32
  context "modification" do
27
33
 
28
34
  before(:all) do
29
- load_core_schema
35
+ define_schema(:auto_create => false) do
36
+ create_table :users, :force => true do |t|
37
+ t.string :login
38
+ t.datetime :deleted_at
39
+ end
40
+
41
+ create_table :posts, :force => true do |t|
42
+ t.text :body
43
+ t.integer :user_id
44
+ t.integer :author_id
45
+ end
46
+
47
+ create_table :comments, :force => true do |t|
48
+ t.text :body
49
+ t.integer :post_id
50
+ t.foreign_key :post_id, :posts, :id
51
+ end
52
+ end
53
+ class User < ::ActiveRecord::Base ; end
54
+ class Post < ::ActiveRecord::Base ; end
55
+ class Comment < ::ActiveRecord::Base ; end
30
56
  end
31
57
 
32
58
  if SchemaPlusHelpers.sqlite3?
@@ -34,13 +60,13 @@ describe "Foreign Key" do
34
60
  it "raises an exception when attempting to add" do
35
61
  expect {
36
62
  add_foreign_key(:posts, :author_id, :users, :id, :on_update => :cascade, :on_delete => :restrict)
37
- }.should raise_error(NotImplementedError)
63
+ }.to raise_error(NotImplementedError)
38
64
  end
39
65
 
40
66
  it "raises an exception when attempting to remove" do
41
67
  expect {
42
68
  remove_foreign_key(:posts, "dummy")
43
- }.should raise_error(NotImplementedError)
69
+ }.to raise_error(NotImplementedError)
44
70
  end
45
71
 
46
72
  else
@@ -102,8 +128,6 @@ describe "Foreign Key" do
102
128
  Post.reverse_foreign_keys.collect(&:column_names).should_not include(%w[post_id])
103
129
  end
104
130
 
105
- it "removes auto-generated index"
106
-
107
131
  end
108
132
 
109
133
  context "when referencing column and column is removed" do
@@ -117,7 +141,7 @@ describe "Foreign Key" do
117
141
 
118
142
  end
119
143
 
120
- context "when table name is a rerved word" do
144
+ context "when table name is a reserved word" do
121
145
  before(:each) do
122
146
  migration.suppress_messages do
123
147
  migration.create_table :references, :force => true do |t|
@@ -132,7 +156,7 @@ describe "Foreign Key" do
132
156
  migration.add_foreign_key(:references, :post_id, :posts, :id)
133
157
  foreign_key = migration.foreign_keys(:references).detect{|definition| definition.column_names == ["post_id"]}
134
158
  migration.remove_foreign_key(:references, foreign_key.name)
135
- }.should_not raise_error
159
+ }.to_not raise_error
136
160
  end
137
161
  end
138
162
  end
@@ -6,7 +6,27 @@ describe "Index definition" do
6
6
  let(:migration) { ::ActiveRecord::Migration }
7
7
 
8
8
  before(:all) do
9
- load_core_schema
9
+ define_schema(:auto_create => false) do
10
+ create_table :users, :force => true do |t|
11
+ t.string :login
12
+ t.datetime :deleted_at
13
+ end
14
+
15
+ create_table :posts, :force => true do |t|
16
+ t.text :body
17
+ t.integer :user_id
18
+ t.integer :author_id
19
+ end
20
+
21
+ create_table :comments, :force => true do |t|
22
+ t.text :body
23
+ t.integer :post_id
24
+ t.foreign_key :post_id, :posts, :id
25
+ end
26
+ end
27
+ class User < ::ActiveRecord::Base ; end
28
+ class Post < ::ActiveRecord::Base ; end
29
+ class Comment < ::ActiveRecord::Base ; end
10
30
  end
11
31
 
12
32
  around(:each) do |example|
@@ -27,7 +47,7 @@ describe "Index definition" do
27
47
  end
28
48
 
29
49
  it "is included in User.indexes" do
30
- User.indexes.select { |index| index.columns == %w[login deleted_at] }.should have(1).item
50
+ @index.should_not be_nil
31
51
  end
32
52
 
33
53
  end
@@ -41,6 +61,38 @@ describe "Index definition" do
41
61
  end
42
62
  end
43
63
 
64
+ it "should not crash on equality test with nil" do
65
+ index = ActiveRecord::ConnectionAdapters::IndexDefinition.new(:table, :column)
66
+ expect{index == nil}.to_not raise_error
67
+ (index == nil).should be_false
68
+ end
69
+
70
+
71
+ unless SchemaPlusHelpers.mysql?
72
+ context "when index is ordered" do
73
+
74
+ quotes = [
75
+ ["unquoted", ''],
76
+ ["double-quoted", '"'],
77
+ ]
78
+ quotes += [
79
+ ["single-quoted", "'"],
80
+ ["back-quoted", '`']
81
+ ] if SchemaPlusHelpers.sqlite3?
82
+
83
+ quotes.each do |quotename, quote|
84
+ it "index definition includes orders for #{quotename} columns" do
85
+ migration.execute "CREATE INDEX users_login_index ON users (#{quote}login#{quote} DESC, #{quote}deleted_at#{quote} ASC)"
86
+ User.reset_column_information
87
+ index = index_definition(%w[login deleted_at])
88
+ index.orders.should == {"login" => :desc, "deleted_at" => :asc}
89
+ end
90
+
91
+ end
92
+ end
93
+ end
94
+
95
+
44
96
  if SchemaPlusHelpers.postgresql?
45
97
 
46
98
  context "when case insensitive is added" do
data/spec/index_spec.rb CHANGED
@@ -1,11 +1,29 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
- require 'models/user'
4
-
5
3
  describe "add_index" do
6
-
4
+
7
5
  before(:all) do
8
- load_core_schema
6
+ define_schema(:auto_create => false) do
7
+ create_table :users, :force => true do |t|
8
+ t.string :login
9
+ t.datetime :deleted_at
10
+ end
11
+
12
+ create_table :posts, :force => true do |t|
13
+ t.text :body
14
+ t.integer :user_id
15
+ t.integer :author_id
16
+ end
17
+
18
+ create_table :comments, :force => true do |t|
19
+ t.text :body
20
+ t.integer :post_id
21
+ t.foreign_key :post_id, :posts, :id
22
+ end
23
+ end
24
+ class User < ::ActiveRecord::Base ; end
25
+ class Post < ::ActiveRecord::Base ; end
26
+ class Comment < ::ActiveRecord::Base ; end
9
27
  end
10
28
 
11
29
  let(:migration) { ::ActiveRecord::Migration }
@@ -31,21 +49,27 @@ describe "add_index" do
31
49
  index_for(:login).name.should == 'users_login_index'
32
50
  end
33
51
 
52
+ unless SchemaPlusHelpers.mysql?
53
+ it "should assign order" do
54
+ add_index(:users, [:login, :deleted_at], :order => {:login => :desc, :deleted_at => :asc})
55
+ index_for([:login, :deleted_at]).orders.should == {"login" => :desc, "deleted_at" => :asc}
56
+ end
57
+ end
58
+
59
+
34
60
  context "for duplicate index" do
35
61
  it "should not complain if the index is the same" do
36
62
  add_index(:users, :login)
37
63
  index_for(:login).should_not be_nil
38
64
  ActiveRecord::Base.logger.should_receive(:warn).with(/login.*Skipping/)
39
- expect { add_index(:users, :login) }.should_not raise_error
65
+ expect { add_index(:users, :login) }.to_not raise_error
40
66
  index_for(:login).should_not be_nil
41
67
  end
42
- if ActiveRecord::VERSION::STRING >= "3.0"
43
- it "should complain if the index is different" do
44
- add_index(:users, :login, :unique => true)
45
- index_for(:login).should_not be_nil
46
- expect { add_index(:users, :login) }.should raise_error
47
- index_for(:login).should_not be_nil
48
- end
68
+ it "should complain if the index is different" do
69
+ add_index(:users, :login, :unique => true)
70
+ index_for(:login).should_not be_nil
71
+ expect { add_index(:users, :login) }.to raise_error
72
+ index_for(:login).should_not be_nil
49
73
  end
50
74
  end
51
75
 
@@ -56,19 +80,39 @@ describe "add_index" do
56
80
  index_for(:login).conditions.should == '(deleted_at IS NULL)'
57
81
  end
58
82
 
59
- it "should assign expression" do
83
+ it "should assign expression, conditions and kind" do
60
84
  add_index(:users, :expression => "USING hash (upper(login)) WHERE deleted_at IS NULL", :name => 'users_login_index')
61
85
  @index = User.indexes.detect { |i| i.expression.present? }
62
86
  @index.expression.should == "upper((login)::text)"
63
87
  @index.conditions.should == "(deleted_at IS NULL)"
88
+ @index.kind.should == "hash"
89
+ end
90
+
91
+ it "should allow to specify expression, conditions and kind separately" do
92
+ add_index(:users, :kind => "hash", :expression => "upper(login)", :conditions => "deleted_at IS NULL", :name => 'users_login_index')
93
+ @index = User.indexes.detect { |i| i.expression.present? }
94
+ @index.expression.should == "upper((login)::text)"
95
+ @index.conditions.should == "(deleted_at IS NULL)"
96
+ @index.kind.should == "hash"
97
+ end
98
+
99
+ it "should allow to specify kind" do
100
+ add_index(:users, :login, :kind => "hash")
101
+ index_for(:login).kind.should == 'hash'
102
+ end
103
+
104
+ it "should allow to specify actual expression only" do
105
+ add_index(:users, :expression => "upper(login)", :name => 'users_login_index')
106
+ @index = User.indexes.detect { |i| i.expression.present? }
107
+ @index.expression.should == "upper((login)::text)"
64
108
  end
65
109
 
66
110
  it "should raise if no column given and expression is missing" do
67
- expect { add_index(:users, :name => 'users_login_index') }.should raise_error(ArgumentError)
111
+ expect { add_index(:users, :name => 'users_login_index') }.to raise_error(ArgumentError)
68
112
  end
69
113
 
70
114
  it "should raise if expression without name is given" do
71
- expect { add_index(:users, :expression => "USING btree (login)") }.should raise_error(ArgumentError)
115
+ expect { add_index(:users, :expression => "USING btree (login)") }.to raise_error(ArgumentError)
72
116
  end
73
117
 
74
118
  end # of postgresql specific examples
@@ -5,7 +5,34 @@ describe ActiveRecord::Migration do
5
5
  include SchemaPlusHelpers
6
6
 
7
7
  before(:all) do
8
- load_auto_schema
8
+ define_schema(:auto_create => true) do
9
+
10
+ create_table :users, :force => true do |t|
11
+ t.string :login, :index => { :unique => true }
12
+ end
13
+
14
+ create_table :members, :force => true do |t|
15
+ t.string :login
16
+ end
17
+
18
+ create_table :comments, :force => true do |t|
19
+ t.string :content
20
+ t.integer :user
21
+ t.integer :user_id
22
+ t.foreign_key :user_id, :users, :id
23
+ end
24
+
25
+ create_table :posts, :force => true do |t|
26
+ t.string :content
27
+ end
28
+ end
29
+ class User < ::ActiveRecord::Base ; end
30
+ class Post < ::ActiveRecord::Base ; end
31
+ class Comment < ::ActiveRecord::Base ; end
32
+ end
33
+
34
+ around(:each) do |example|
35
+ with_fk_config(:auto_create => true, :auto_index => true) { example.run }
9
36
  end
10
37
 
11
38
  context "when table is created" do
@@ -15,107 +42,184 @@ describe ActiveRecord::Migration do
15
42
  end
16
43
 
17
44
  it "should properly handle default values for booleans" do
18
- expect { create_table(@model, :bool => { :METHOD => :boolean, :default => true }) }.should_not raise_error
45
+ expect {
46
+ recreate_table(@model) do |t|
47
+ t.boolean :bool, :default => true
48
+ end
49
+ }.to_not raise_error
19
50
  @model.create.reload.bool.should be_true
20
51
  end
21
52
 
22
- it "should create foreign keys" do
23
- create_table(@model, :user_id => {},
24
- :author_id => { :references => :users },
25
- :member_id => { :references => nil } )
53
+ it "should create auto foreign keys" do
54
+ recreate_table(@model) do |t|
55
+ t.integer :user_id
56
+ end
26
57
  @model.should reference(:users, :id).on(:user_id)
58
+ end
59
+
60
+ it "should create explicit foreign key with default reference" do
61
+ recreate_table(@model) do |t|
62
+ t.integer :user, :foreign_key => true
63
+ end
64
+ @model.should reference(:users, :id).on(:user)
65
+ end
66
+
67
+ it "should create foreign key with different reference" do
68
+ recreate_table(@model) do |t|
69
+ t.integer :author_id, :foreign_key => { :references => :users }
70
+ end
71
+ @model.should reference(:users, :id).on(:author_id)
72
+ end
73
+
74
+ it "should create foreign key with different reference using shortcut" do
75
+ recreate_table(@model) do |t|
76
+ t.integer :author_id, :references => :users
77
+ end
27
78
  @model.should reference(:users, :id).on(:author_id)
79
+ end
80
+
81
+ it "should create foreign key with specified name" do
82
+ recreate_table @model do |t|
83
+ t.integer :user_id, :foreign_key => { :name => "wugga" }
84
+ end
85
+ @model.should reference(:users, :id).with_name("wugga")
86
+ end
87
+
88
+ it "should suppress foreign key" do
89
+ recreate_table(@model) do |t|
90
+ t.integer :member_id, :foreign_key => false
91
+ end
92
+ @model.should_not reference.on(:member_id)
93
+ end
94
+
95
+ it "should suppress foreign key using shortcut" do
96
+ recreate_table(@model) do |t|
97
+ t.integer :member_id, :references => nil
98
+ end
28
99
  @model.should_not reference.on(:member_id)
29
100
  end
30
101
 
31
102
  it "should create foreign key using t.belongs_to" do
32
- create_table(@model, :user => {:METHOD => :belongs_to})
103
+ recreate_table(@model) do |t|
104
+ t.belongs_to :user
105
+ end
33
106
  @model.should reference(:users, :id).on(:user_id)
34
107
  end
35
108
 
36
109
  it "should not create foreign key using t.belongs_to with :polymorphic => true" do
37
- create_table(@model, :user => {:METHOD => :belongs_to, :polymorphic => true})
110
+ recreate_table(@model) do |t|
111
+ t.belongs_to :user, :polymorphic => true
112
+ end
38
113
  @model.should_not reference(:users, :id).on(:user_id)
39
114
  end
40
115
 
41
116
  it "should create foreign key using t.references" do
42
- create_table(@model, :user => {:METHOD => :references})
117
+ recreate_table(@model) do |t|
118
+ t.references :user
119
+ end
43
120
  @model.should reference(:users, :id).on(:user_id)
44
121
  end
45
122
 
46
- it "should not create foreign key using t.references with :references => nil" do
47
- create_table(@model, :user => {:METHOD => :references, :references => nil})
123
+ it "should not create foreign key using t.references with :foreign_key => false" do
124
+ recreate_table(@model) do |t|
125
+ t.references :user, :foreign_key => false
126
+ end
48
127
  @model.should_not reference(:users, :id).on(:user_id)
49
128
  end
50
129
 
51
130
  it "should not create foreign key using t.references with :polymorphic => true" do
52
- create_table(@model, :user => {:METHOD => :references, :polymorphic => true})
131
+ recreate_table(@model) do |t|
132
+ t.references :user, :polymorphic => true
133
+ end
53
134
  @model.should_not reference(:users, :id).on(:user_id)
54
135
  end
55
136
 
56
137
  it "should create foreign key to the same table on parent_id" do
57
- create_table(@model, :parent_id => {})
138
+ recreate_table(@model) do |t|
139
+ t.integer :parent_id
140
+ end
58
141
  @model.should reference(@model.table_name, :id).on(:parent_id)
59
142
  end
60
143
 
61
144
  it "should create an index if specified on column" do
62
- create_table(@model, :state => { :index => true })
145
+ recreate_table(@model) do |t|
146
+ t.integer :state, :index => true
147
+ end
63
148
  @model.should have_index.on(:state)
64
149
  end
65
150
 
66
151
  it "should create a unique index if specified on column" do
67
- create_table(@model, :state => { :index => {:unique => true} })
152
+ recreate_table(@model) do |t|
153
+ t.integer :state, :index => { :unique => true }
154
+ end
68
155
  @model.should have_unique_index.on(:state)
69
156
  end
157
+
70
158
  it "should create a unique index if specified on column using shorthand" do
71
- create_table(@model, :state => { :index => :unique })
159
+ recreate_table(@model) do |t|
160
+ t.integer :state, :index => :unique
161
+ end
72
162
  @model.should have_unique_index.on(:state)
73
163
  end
74
164
 
75
165
  it "should create an index if specified explicitly" do
76
- create_table_opts(@model, {}, {:state => {}}, {:state => {}})
166
+ recreate_table(@model) do |t|
167
+ t.integer :state
168
+ t.index :state
169
+ end
77
170
  @model.should have_index.on(:state)
78
171
  end
79
172
 
80
173
  it "should create a unique index if specified explicitly" do
81
- create_table_opts(@model, {}, {:state => {}}, {:state => {:unique => true}})
174
+ recreate_table(@model) do |t|
175
+ t.integer :state
176
+ t.index :state, :unique => true
177
+ end
82
178
  @model.should have_unique_index.on(:state)
83
179
  end
84
180
 
85
181
  it "should create a multiple-column index if specified" do
86
- create_table(@model, :city => {},
87
- :state => { :index => {:with => :city} } )
182
+ recreate_table(@model) do |t|
183
+ t.integer :city
184
+ t.integer :state, :index => { :with => :city }
185
+ end
88
186
  @model.should have_index.on([:state, :city])
89
187
  end
90
188
 
91
189
  it "should auto-index foreign keys only" do
92
- with_fk_config(:auto_index => true) do
93
- create_table(@model, :user_id => {},
94
- :application_id => { :references => nil },
95
- :state => {})
96
- @model.should have_index.on(:user_id)
97
- @model.should_not have_index.on(:application_id)
98
- @model.should_not have_index.on(:state)
190
+ recreate_table(@model) do |t|
191
+ t.integer :user_id
192
+ t.integer :application_id, :references => nil
193
+ t.integer :state
99
194
  end
195
+ @model.should have_index.on(:user_id)
196
+ @model.should_not have_index.on(:application_id)
197
+ @model.should_not have_index.on(:state)
100
198
  end
101
199
 
102
200
  it "should override foreign key auto_create positively" do
103
201
  with_fk_config(:auto_create => false) do
104
- create_table_opts(@model, {:foreign_keys => {:auto_create => true}}, :user_id => {})
202
+ recreate_table @model, :foreign_keys => {:auto_create => true} do |t|
203
+ t.integer :user_id
204
+ end
105
205
  @model.should reference(:users, :id).on(:user_id)
106
206
  end
107
207
  end
108
208
 
109
209
  it "should override foreign key auto_create negatively" do
110
210
  with_fk_config(:auto_create => true) do
111
- create_table_opts(@model, {:foreign_keys => {:auto_create => false}}, :user_id => {})
211
+ recreate_table @model, :foreign_keys => {:auto_create => false} do |t|
212
+ t.integer :user_id
213
+ end
112
214
  @model.should_not reference.on(:user_id)
113
215
  end
114
216
  end
115
217
 
116
218
  it "should override foreign key auto_index positively" do
117
219
  with_fk_config(:auto_index => false) do
118
- create_table_opts(@model, {:foreign_keys => {:auto_index => true}}, :user_id => {})
220
+ recreate_table @model, :foreign_keys => {:auto_index => true} do |t|
221
+ t.integer :user_id
222
+ end
119
223
  @model.should have_index.on(:user_id)
120
224
  end
121
225
  end
@@ -125,87 +229,137 @@ describe ActiveRecord::Migration do
125
229
  if SchemaPlusHelpers.mysql?
126
230
  actions.delete(:set_default)
127
231
  it "should raise a not-implemented error for on_update => :set_default" do
128
- expect { create_table(@model, :user_id => {:on_update => :set_default}) }.should raise_error(NotImplementedError)
232
+ expect {
233
+ recreate_table @model do |t|
234
+ t.integer :user_id, :foreign_key => { :on_update => :set_default }
235
+ end
236
+ }.to raise_error(NotImplementedError)
129
237
  end
130
238
 
131
239
  it "should raise a not-implemented error for on_delete => :set_default" do
132
- expect { create_table(@model, :user_id => {:on_delete => :set_default}) }.should raise_error(NotImplementedError)
240
+ expect {
241
+ recreate_table @model do |t|
242
+ t.integer :user_id, :foreign_key => { :on_delete => :set_default }
243
+ end
244
+ }.to raise_error(NotImplementedError)
133
245
  end
134
246
  end
135
247
 
136
248
  actions.each do |action|
137
249
  it "should create and detect on_update #{action.inspect}" do
138
- create_table(@model, :user_id => {:on_update => action})
250
+ recreate_table @model do |t|
251
+ t.integer :user_id, :foreign_key => { :on_update => action }
252
+ end
253
+ @model.should reference.on(:user_id).on_update(action)
254
+ end
255
+
256
+ it "should create and detect on_update #{action.inspect} using shortcut" do
257
+ recreate_table @model do |t|
258
+ t.integer :user_id, :on_update => action
259
+ end
139
260
  @model.should reference.on(:user_id).on_update(action)
140
261
  end
141
262
 
142
263
  it "should create and detect on_delete #{action.inspect}" do
143
- create_table(@model, :user_id => {:on_delete => action})
264
+ recreate_table @model do |t|
265
+ t.integer :user_id, :foreign_key => { :on_delete => action }
266
+ end
267
+ @model.should reference.on(:user_id).on_delete(action)
268
+ end
269
+
270
+ it "should create and detect on_delete #{action.inspect} using shortcut" do
271
+ recreate_table @model do |t|
272
+ t.integer :user_id, :on_delete => action
273
+ end
144
274
  @model.should reference.on(:user_id).on_delete(action)
145
275
  end
146
276
  end
147
277
 
148
278
  it "should use default on_update action" do
149
279
  with_fk_config(:on_update => :cascade) do
150
- create_table_opts(@model, {:foreign_keys => {}}, :user_id => {})
280
+ recreate_table @model do |t|
281
+ t.integer :user_id
282
+ end
151
283
  @model.should reference.on(:user_id).on_update(:cascade)
152
284
  end
153
285
  end
154
286
 
155
287
  it "should use default on_delete action" do
156
288
  with_fk_config(:on_delete => :cascade) do
157
- create_table_opts(@model, {:foreign_keys => {}}, :user_id => {})
289
+ recreate_table @model do |t|
290
+ t.integer :user_id
291
+ end
158
292
  @model.should reference.on(:user_id).on_delete(:cascade)
159
293
  end
160
294
  end
161
295
 
162
296
  it "should override on_update action per table" do
163
297
  with_fk_config(:on_update => :cascade) do
164
- create_table_opts(@model, {:foreign_keys => {:on_update => :restrict}}, :user_id => {})
298
+ recreate_table @model, :foreign_keys => {:on_update => :restrict} do |t|
299
+ t.integer :user_id
300
+ end
165
301
  @model.should reference.on(:user_id).on_update(:restrict)
166
302
  end
167
303
  end
168
304
 
169
305
  it "should override on_delete action per table" do
170
306
  with_fk_config(:on_delete => :cascade) do
171
- create_table_opts(@model, {:foreign_keys => {:on_delete => :restrict}}, :user_id => {})
307
+ recreate_table @model, :foreign_keys => {:on_delete => :restrict} do |t|
308
+ t.integer :user_id
309
+ end
172
310
  @model.should reference.on(:user_id).on_delete(:restrict)
173
311
  end
174
312
  end
175
313
 
176
314
  it "should override on_update action per column" do
177
315
  with_fk_config(:on_update => :cascade) do
178
- create_table_opts(@model, {:foreign_keys => {:on_update => :restruct}}, :user_id => {:on_update => :set_null})
316
+ recreate_table @model, :foreign_keys => {:on_update => :restrict} do |t|
317
+ t.integer :user_id, :foreign_key => { :on_update => :set_null }
318
+ end
179
319
  @model.should reference.on(:user_id).on_update(:set_null)
180
320
  end
181
321
  end
182
322
 
183
323
  it "should override on_delete action per column" do
184
324
  with_fk_config(:on_delete => :cascade) do
185
- create_table_opts(@model, {:foreign_keys => {:on_delete => :restrict}}, :user_id => {:on_delete => :set_null})
325
+ recreate_table @model, :foreign_keys => {:on_delete => :restrict} do |t|
326
+ t.integer :user_id, :foreign_key => { :on_delete => :set_null }
327
+ end
186
328
  @model.should reference.on(:user_id).on_delete(:set_null)
187
329
  end
188
330
  end
189
331
 
190
332
  it "should raise an error for an invalid on_update action" do
191
- expect { create_table(@model, :user_id => {:on_update => :invalid}) }.should raise_error(ArgumentError)
333
+ expect {
334
+ recreate_table @model do |t|
335
+ t.integer :user_id, :foreign_key => { :on_update => :invalid }
336
+ end
337
+ }.to raise_error(ArgumentError)
192
338
  end
193
339
 
194
340
  it "should raise an error for an invalid on_delete action" do
195
- expect { create_table(@model, :user_id => {:on_delete => :invalid}) }.should raise_error(ArgumentError)
341
+ expect {
342
+ recreate_table @model do |t|
343
+ t.integer :user_id, :foreign_key => { :on_delete => :invalid }
344
+ end
345
+ }.to raise_error(ArgumentError)
196
346
  end
197
347
 
198
348
  unless SchemaPlusHelpers.mysql?
199
349
  it "should override foreign key auto_index negatively" do
200
350
  with_fk_config(:auto_index => true) do
201
- create_table_opts(@model, {:foreign_keys => {:auto_index => false}}, :user_id => {})
351
+ recreate_table @model, :foreign_keys => {:auto_index => false} do |t|
352
+ t.integer :user_id
353
+ end
202
354
  @model.should_not have_index.on(:user_id)
203
355
  end
204
356
  end
205
357
 
206
358
  it "should disable auto-index for a column" do
207
359
  with_fk_config(:auto_index => true) do
208
- create_table(@model, :user_id => { :index => false })
360
+ recreate_table @model do |t|
361
+ t.integer :user_id, :index => false
362
+ end
209
363
  @model.should_not have_index.on(:user_id)
210
364
  end
211
365
  end
@@ -234,14 +388,20 @@ describe ActiveRecord::Migration do
234
388
  end
235
389
  end
236
390
 
237
- it "should create foreign key to explicity given table" do
391
+ it "should create foreign key to explicitly given table" do
392
+ add_column(:author_id, :integer, :foreign_key => { :references => :users }) do
393
+ @model.should reference(:users, :id).on(:author_id)
394
+ end
395
+ end
396
+
397
+ it "should create foreign key to explicitly given table using shortcut" do
238
398
  add_column(:author_id, :integer, :references => :users) do
239
399
  @model.should reference(:users, :id).on(:author_id)
240
400
  end
241
401
  end
242
402
 
243
- it "should create foreign key to explicity given table and column name" do
244
- add_column(:author_login, :string, :references => [:users, :login]) do
403
+ it "should create foreign key to explicitly given table and column name" do
404
+ add_column(:author_login, :string, :foreign_key => { :references => [:users, :login]}) do
245
405
  @model.should reference(:users, :login).on(:author_login)
246
406
  end
247
407
  end
@@ -258,7 +418,13 @@ describe ActiveRecord::Migration do
258
418
  end
259
419
  end
260
420
 
261
- it "shouldnt't create foreign key if specified explicity" do
421
+ it "shouldn't create foreign key if specified explicitly" do
422
+ add_column(:post_id, :integer, :foreign_key => false) do
423
+ @model.should_not reference.on(:post_id)
424
+ end
425
+ end
426
+
427
+ it "shouldn't create foreign key if specified explicitly by shorthand" do
262
428
  add_column(:post_id, :integer, :references => nil) do
263
429
  @model.should_not reference.on(:post_id)
264
430
  end
@@ -317,10 +483,6 @@ describe ActiveRecord::Migration do
317
483
  SchemaPlus.config.foreign_keys.auto_index = false
318
484
  end
319
485
 
320
- it "should not auto-index if column already has an index"
321
-
322
- it "should remove auto-created index when removing foreign key"
323
-
324
486
  it "should use default on_update action" do
325
487
  SchemaPlus.config.foreign_keys.on_update = :cascade
326
488
  add_column(:post_id, :integer) do
@@ -340,7 +502,7 @@ describe ActiveRecord::Migration do
340
502
  it "should allow to overwrite default actions" do
341
503
  SchemaPlus.config.foreign_keys.on_delete = :cascade
342
504
  SchemaPlus.config.foreign_keys.on_update = :restrict
343
- add_column(:post_id, :integer, :on_update => :set_null, :on_delete => :set_null) do
505
+ add_column(:post_id, :integer, :foreign_key => { :on_update => :set_null, :on_delete => :set_null}) do
344
506
  @model.should reference.on(:post_id).on_delete(:set_null).on_update(:set_null)
345
507
  end
346
508
  SchemaPlus.config.foreign_keys.on_delete = nil
@@ -366,28 +528,71 @@ describe ActiveRecord::Migration do
366
528
  end
367
529
 
368
530
  it "should create foreign key" do
369
- change_column :user, :string, :references => [:users, :login]
531
+ change_column :user, :string, :foreign_key => { :references => [:users, :login] }
370
532
  @model.should reference(:users, :login).on(:user)
371
- change_column :user, :string, :references => nil
372
533
  end
373
534
 
374
535
  context "and initially references to users table" do
375
536
 
537
+ before(:each) do
538
+ recreate_table @model do |t|
539
+ t.integer :user_id
540
+ end
541
+ end
542
+
376
543
  it "should have foreign key" do
377
544
  @model.should reference(:users)
378
545
  end
379
546
 
380
- it "should drop foreign key afterwards" do
381
- change_column :user_id, :integer, :references => :members
547
+ it "should drop foreign key if it is no longer valid" do
548
+ change_column :user_id, :integer, :foreign_key => { :references => :members }
382
549
  @model.should_not reference(:users)
383
- change_column :user_id, :integer, :references => :users
384
550
  end
385
551
 
386
- it "should reference pointed table afterwards" do
387
- change_column :user_id, :integer, :references => :members
552
+ it "should drop foreign key if requested to do so" do
553
+ change_column :user_id, :integer, :foreign_key => { :references => nil }
554
+ @model.should_not reference(:users)
555
+ end
556
+
557
+ it "should remove auto-created index if foreign key is removed" do
558
+ @model.should have_index.on(:user_id) # sanity check that index was auto-created
559
+ change_column :user_id, :integer, :foreign_key => { :references => nil }
560
+ @model.should_not have_index.on(:user_id)
561
+ end
562
+
563
+ it "should reference pointed table afterwards if new one is created" do
564
+ change_column :user_id, :integer, :foreign_key => { :references => :members }
388
565
  @model.should reference(:members)
389
566
  end
390
567
 
568
+ it "should maintain foreign key if it's unaffected by change" do
569
+ change_column :user_id, :integer, :default => 0
570
+ @model.should reference(:users)
571
+ end
572
+
573
+ it "should maintain foreign key if it's unaffected by change, even if auto_index is off" do
574
+ with_fk_config(:auto_create => false) do
575
+ change_column :user_id, :integer, :default => 0
576
+ @model.should reference(:users)
577
+ end
578
+ end
579
+
580
+ end
581
+
582
+ context "if column defined without foreign key but with index" do
583
+ before(:each) do
584
+ recreate_table @model do |t|
585
+ t.integer :user_id, :foreign_key => false, :index => true
586
+ end
587
+ end
588
+
589
+ it "should create the index" do
590
+ @model.should have_index.on(:user_id)
591
+ end
592
+
593
+ it "adding foreign key should not fail due to attempt to auto-create existing index" do
594
+ expect { change_column :user_id, :integer, :foreign_key => true }.to_not raise_error
595
+ end
391
596
  end
392
597
 
393
598
  protected
@@ -400,41 +605,87 @@ describe ActiveRecord::Migration do
400
605
  end
401
606
 
402
607
  end
403
- end
404
-
405
- def foreign_key(model, column)
406
- columns = Array(column).collect(&:to_s)
407
- model.foreign_keys.detect { |fk| fk.table_name == model.table_name && fk.column_names == columns }
408
- end
409
608
 
410
- def create_table_opts(model, table_options, columns_with_options, indexes={})
411
- ActiveRecord::Migration.suppress_messages do
412
- ActiveRecord::Migration.create_table model.table_name, table_options.merge(:force => true) do |t|
413
- columns_with_options.each_pair do |column, options|
414
- method = options.delete(:METHOD) || :integer
415
- t.send method, column, options
609
+ context "when column is removed" do
610
+ before(:each) do
611
+ @model = Comment
612
+ recreate_table @model do |t|
613
+ t.integer :post_id
416
614
  end
417
- indexes.each_pair do |column, options|
418
- t.index column, options
615
+ end
616
+
617
+ it "should remove a foreign key" do
618
+ @model.should reference(:posts)
619
+ remove_column(:post_id)
620
+ @model.should_not reference(:posts)
621
+ end
622
+
623
+ it "should remove an index" do
624
+ @model.should have_index.on(:post_id)
625
+ remove_column(:post_id)
626
+ @model.should_not have_index.on(:post_id)
627
+ end
628
+
629
+ protected
630
+ def remove_column(column_name)
631
+ table = @model.table_name
632
+ ActiveRecord::Migration.suppress_messages do
633
+ ActiveRecord::Migration.remove_column(table, column_name)
634
+ @model.reset_column_information
419
635
  end
420
636
  end
421
- model.reset_column_information
422
637
  end
423
- end
424
638
 
425
- def create_table(model, columns_with_options)
426
- create_table_opts(model, {}, columns_with_options)
427
639
  end
428
640
 
429
- def with_fk_config(opts, &block)
430
- save = Hash[opts.keys.collect{|key| [key, SchemaPlus.config.foreign_keys.send(key)]}]
431
- begin
432
- SchemaPlus.config.foreign_keys.update_attributes(opts)
433
- yield
434
- ensure
435
- SchemaPlus.config.foreign_keys.update_attributes(save)
641
+ context "when table is renamed" do
642
+
643
+ before(:each) do
644
+ @model = Comment
645
+ recreate_table @model do |t|
646
+ t.integer :user_id
647
+ t.integer :xyz, :index => true
648
+ end
649
+ ActiveRecord::Migration.suppress_messages do
650
+ ActiveRecord::Migration.rename_table @model.table_name, :newname
651
+ end
652
+ end
653
+
654
+ around(:each) do |example|
655
+ begin
656
+ example.run
657
+ ensure
658
+ ActiveRecord::Migration.suppress_messages do
659
+ ActiveRecord::Migration.rename_table :newname, :comments
660
+ end
661
+ end
662
+ end
663
+
664
+ it "should rename rails-named indexes" do
665
+ index = ActiveRecord::Base.connection.indexes(:newname).find{|index| index.columns == ['xyz']}
666
+ index.name.should =~ /^index_newname_on/
667
+ end
668
+
669
+ it "should rename fk indexes" do
670
+ index = ActiveRecord::Base.connection.indexes(:newname).find{|index| index.columns == ['user_id']}
671
+ index.name.should =~ /^fk__newname_/
436
672
  end
673
+
674
+ unless SchemaPlusHelpers.sqlite3?
675
+ it "should rename foreign key definitions" do
676
+ ActiveRecord::Base.connection.foreign_keys(:newname).first.name.should =~ /newname/
677
+ end
678
+ end
679
+
437
680
  end
681
+
682
+ def recreate_table(model, opts={}, &block)
683
+ ActiveRecord::Migration.suppress_messages do
684
+ ActiveRecord::Migration.create_table model.table_name, opts.merge(:force => true), &block
685
+ end
686
+ model.reset_column_information
687
+ end
688
+
438
689
 
439
690
 
440
691
  end