schema_plus 0.4.1 → 1.0.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.
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