schema_plus 0.1.0.pre1

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 +25 -0
  2. data/Gemfile +3 -0
  3. data/MIT-LICENSE +25 -0
  4. data/README.rdoc +147 -0
  5. data/Rakefile +70 -0
  6. data/init.rb +1 -0
  7. data/lib/schema_plus/active_record/associations.rb +211 -0
  8. data/lib/schema_plus/active_record/base.rb +81 -0
  9. data/lib/schema_plus/active_record/connection_adapters/abstract_adapter.rb +96 -0
  10. data/lib/schema_plus/active_record/connection_adapters/column.rb +55 -0
  11. data/lib/schema_plus/active_record/connection_adapters/foreign_key_definition.rb +115 -0
  12. data/lib/schema_plus/active_record/connection_adapters/index_definition.rb +51 -0
  13. data/lib/schema_plus/active_record/connection_adapters/mysql_adapter.rb +111 -0
  14. data/lib/schema_plus/active_record/connection_adapters/postgresql_adapter.rb +163 -0
  15. data/lib/schema_plus/active_record/connection_adapters/schema_statements.rb +39 -0
  16. data/lib/schema_plus/active_record/connection_adapters/sqlite3_adapter.rb +78 -0
  17. data/lib/schema_plus/active_record/connection_adapters/table_definition.rb +130 -0
  18. data/lib/schema_plus/active_record/migration.rb +220 -0
  19. data/lib/schema_plus/active_record/schema.rb +27 -0
  20. data/lib/schema_plus/active_record/schema_dumper.rb +122 -0
  21. data/lib/schema_plus/active_record/validations.rb +139 -0
  22. data/lib/schema_plus/railtie.rb +12 -0
  23. data/lib/schema_plus/version.rb +3 -0
  24. data/lib/schema_plus.rb +248 -0
  25. data/schema_plus.gemspec +37 -0
  26. data/schema_plus.gemspec.rails3.0 +36 -0
  27. data/schema_plus.gemspec.rails3.1 +36 -0
  28. data/spec/association_spec.rb +529 -0
  29. data/spec/connections/mysql/connection.rb +18 -0
  30. data/spec/connections/mysql2/connection.rb +18 -0
  31. data/spec/connections/postgresql/connection.rb +15 -0
  32. data/spec/connections/sqlite3/connection.rb +14 -0
  33. data/spec/foreign_key_definition_spec.rb +23 -0
  34. data/spec/foreign_key_spec.rb +142 -0
  35. data/spec/index_definition_spec.rb +139 -0
  36. data/spec/index_spec.rb +71 -0
  37. data/spec/migration_spec.rb +405 -0
  38. data/spec/models/comment.rb +2 -0
  39. data/spec/models/post.rb +2 -0
  40. data/spec/models/user.rb +2 -0
  41. data/spec/references_spec.rb +78 -0
  42. data/spec/schema/auto_schema.rb +23 -0
  43. data/spec/schema/core_schema.rb +21 -0
  44. data/spec/schema_dumper_spec.rb +167 -0
  45. data/spec/schema_spec.rb +71 -0
  46. data/spec/spec_helper.rb +59 -0
  47. data/spec/support/extensions/active_model.rb +13 -0
  48. data/spec/support/helpers.rb +16 -0
  49. data/spec/support/matchers/automatic_foreign_key_matchers.rb +2 -0
  50. data/spec/support/matchers/have_index.rb +52 -0
  51. data/spec/support/matchers/reference.rb +66 -0
  52. data/spec/support/reference.rb +66 -0
  53. data/spec/validations_spec.rb +294 -0
  54. data/spec/views_spec.rb +140 -0
  55. metadata +269 -0
@@ -0,0 +1,405 @@
1
+ # encoding: utf-8
2
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
3
+
4
+ describe ActiveRecord::Migration do
5
+
6
+ it "should respond to get_references" do
7
+ ActiveRecord::Migration.should respond_to :get_references
8
+ end
9
+
10
+ end
11
+
12
+ describe ActiveRecord::Migration do
13
+ include SchemaPlusHelpers
14
+
15
+ before(:all) do
16
+ load_auto_schema
17
+ end
18
+
19
+ context "when table is created" do
20
+
21
+ before(:each) do
22
+ @model = Post
23
+ end
24
+
25
+ it "should create foreign keys" do
26
+ create_table(@model, :user_id => {},
27
+ :author_id => { :references => :users },
28
+ :member_id => { :references => nil } )
29
+ @model.should reference(:users, :id).on(:user_id)
30
+ @model.should reference(:users, :id).on(:author_id)
31
+ @model.should_not reference.on(:member_id)
32
+ end
33
+
34
+ it "should create foreign key using t.belongs_to" do
35
+ create_table(@model, :user => {:METHOD => :belongs_to})
36
+ @model.should reference(:users, :id).on(:user_id)
37
+ end
38
+
39
+ it "should create foreign key to the same table on parent_id" do
40
+ create_table(@model, :parent_id => {})
41
+ @model.should reference(@model.table_name, :id).on(:parent_id)
42
+ end
43
+
44
+ it "should create an index if specified on column" do
45
+ create_table(@model, :state => { :index => true })
46
+ @model.should have_index.on(:state)
47
+ end
48
+
49
+ it "should create an index if specified explicitly" do
50
+ create_table_opts(@model, {}, {:state => {}}, {:state => {}})
51
+ @model.should have_index.on(:state)
52
+ end
53
+
54
+ it "should create a unique index if specified explicitly" do
55
+ create_table_opts(@model, {}, {:state => {}}, {:state => {:unique => true}})
56
+ @model.should have_unique_index.on(:state)
57
+ end
58
+
59
+ it "should create a multiple-column index if specified" do
60
+ create_table(@model, :city => {},
61
+ :state => { :index => {:with => :city} } )
62
+ @model.should have_index.on([:state, :city])
63
+ end
64
+
65
+ it "should auto-index foreign keys only" do
66
+ with_fk_config(:auto_index => true) do
67
+ create_table(@model, :user_id => {},
68
+ :application_id => { :references => nil },
69
+ :state => {})
70
+ @model.should have_index.on(:user_id)
71
+ @model.should_not have_index.on(:application_id)
72
+ @model.should_not have_index.on(:state)
73
+ end
74
+ end
75
+
76
+ it "should override foreign key auto_create positively" do
77
+ with_fk_config(:auto_create => false) do
78
+ create_table_opts(@model, {:foreign_keys => {:auto_create => true}}, :user_id => {})
79
+ @model.should reference(:users, :id).on(:user_id)
80
+ end
81
+ end
82
+
83
+ it "should override foreign key auto_create negatively" do
84
+ with_fk_config(:auto_create => true) do
85
+ create_table_opts(@model, {:foreign_keys => {:auto_create => false}}, :user_id => {})
86
+ @model.should_not reference.on(:user_id)
87
+ end
88
+ end
89
+
90
+ it "should override foreign key auto_index positively" do
91
+ with_fk_config(:auto_index => false) do
92
+ create_table_opts(@model, {:foreign_keys => {:auto_index => true}}, :user_id => {})
93
+ @model.should have_index.on(:user_id)
94
+ end
95
+ end
96
+
97
+ actions = [:cascade, :restrict, :set_null, :set_default, :no_action]
98
+
99
+ if SchemaPlusHelpers.mysql?
100
+ actions.delete(:set_default)
101
+ it "should raise a not-implemented error for on_update => :set_default" do
102
+ expect { create_table(@model, :user_id => {:on_update => :set_default}) }.should raise_error(NotImplementedError)
103
+ end
104
+
105
+ it "should raise a not-implemented error for on_delete => :set_default" do
106
+ expect { create_table(@model, :user_id => {:on_delete => :set_default}) }.should raise_error(NotImplementedError)
107
+ end
108
+ end
109
+
110
+ actions.each do |action|
111
+ it "should create and detect on_update #{action.inspect}" do
112
+ create_table(@model, :user_id => {:on_update => action})
113
+ @model.should reference.on(:user_id).on_update(action)
114
+ end
115
+
116
+ it "should create and detect on_delete #{action.inspect}" do
117
+ create_table(@model, :user_id => {:on_delete => action})
118
+ @model.should reference.on(:user_id).on_delete(action)
119
+ end
120
+ end
121
+
122
+ it "should use default on_update action" do
123
+ with_fk_config(:on_update => :cascade) do
124
+ create_table_opts(@model, {:foreign_keys => {}}, :user_id => {})
125
+ @model.should reference.on(:user_id).on_update(:cascade)
126
+ end
127
+ end
128
+
129
+ it "should use default on_delete action" do
130
+ with_fk_config(:on_delete => :cascade) do
131
+ create_table_opts(@model, {:foreign_keys => {}}, :user_id => {})
132
+ @model.should reference.on(:user_id).on_delete(:cascade)
133
+ end
134
+ end
135
+
136
+ it "should override on_update action per table" do
137
+ with_fk_config(:on_update => :cascade) do
138
+ create_table_opts(@model, {:foreign_keys => {:on_update => :restrict}}, :user_id => {})
139
+ @model.should reference.on(:user_id).on_update(:restrict)
140
+ end
141
+ end
142
+
143
+ it "should override on_delete action per table" do
144
+ with_fk_config(:on_delete => :cascade) do
145
+ create_table_opts(@model, {:foreign_keys => {:on_delete => :restrict}}, :user_id => {})
146
+ @model.should reference.on(:user_id).on_delete(:restrict)
147
+ end
148
+ end
149
+
150
+ it "should override on_update action per column" do
151
+ with_fk_config(:on_update => :cascade) do
152
+ create_table_opts(@model, {:foreign_keys => {:on_update => :restruct}}, :user_id => {:on_update => :set_null})
153
+ @model.should reference.on(:user_id).on_update(:set_null)
154
+ end
155
+ end
156
+
157
+ it "should override on_delete action per column" do
158
+ with_fk_config(:on_delete => :cascade) do
159
+ create_table_opts(@model, {:foreign_keys => {:on_delete => :restrict}}, :user_id => {:on_delete => :set_null})
160
+ @model.should reference.on(:user_id).on_delete(:set_null)
161
+ end
162
+ end
163
+
164
+ it "should raise an error for an invalid on_update action" do
165
+ expect { create_table(@model, :user_id => {:on_update => :invalid}) }.should raise_error(ArgumentError)
166
+ end
167
+
168
+ it "should raise an error for an invalid on_delete action" do
169
+ expect { create_table(@model, :user_id => {:on_delete => :invalid}) }.should raise_error(ArgumentError)
170
+ end
171
+
172
+ unless SchemaPlusHelpers.mysql?
173
+ it "should override foreign key auto_index negatively" do
174
+ with_fk_config(:auto_index => true) do
175
+ create_table_opts(@model, {:foreign_keys => {:auto_index => false}}, :user_id => {})
176
+ @model.should_not have_index.on(:user_id)
177
+ end
178
+ end
179
+
180
+ it "should disable auto-index for a column" do
181
+ with_fk_config(:auto_index => true) do
182
+ create_table(@model, :user_id => { :index => false })
183
+ @model.should_not have_index.on(:user_id)
184
+ end
185
+ end
186
+
187
+ end
188
+
189
+ end
190
+
191
+ unless SchemaPlusHelpers.sqlite3?
192
+
193
+ context "when column is added" do
194
+
195
+ before(:each) do
196
+ @model = Comment
197
+ end
198
+
199
+ it "should create an index" do
200
+ add_column(:slug, :string, :index => true) do
201
+ @model.should have_index.on(:slug)
202
+ end
203
+ end
204
+
205
+ it "should create foreign key" do
206
+ add_column(:post_id, :integer) do
207
+ @model.should reference(:posts, :id).on(:post_id)
208
+ end
209
+ end
210
+
211
+ it "should create foreign key to explicity given table" do
212
+ add_column(:author_id, :integer, :references => :users) do
213
+ @model.should reference(:users, :id).on(:author_id)
214
+ end
215
+ end
216
+
217
+ it "should create foreign key to explicity given table and column name" do
218
+ add_column(:author_login, :string, :references => [:users, :login]) do
219
+ @model.should reference(:users, :login).on(:author_login)
220
+ end
221
+ end
222
+
223
+ it "should create foreign key to the same table on parent_id" do
224
+ add_column(:parent_id, :integer) do
225
+ @model.should reference(@model.table_name, :id).on(:parent_id)
226
+ end
227
+ end
228
+
229
+ it "shouldn't create foreign key if column doesn't look like foreign key" do
230
+ add_column(:views_count, :integer) do
231
+ @model.should_not reference.on(:views_count)
232
+ end
233
+ end
234
+
235
+ it "shouldnt't create foreign key if specified explicity" do
236
+ add_column(:post_id, :integer, :references => nil) do
237
+ @model.should_not reference.on(:post_id)
238
+ end
239
+ end
240
+
241
+ it "should create an index if specified" do
242
+ add_column(:post_id, :integer, :index => true) do
243
+ @model.should have_index.on(:post_id)
244
+ end
245
+ end
246
+
247
+ it "should create a unique index if specified" do
248
+ add_column(:post_id, :integer, :index => { :unique => true }) do
249
+ @model.should have_unique_index.on(:post_id)
250
+ end
251
+ end
252
+
253
+ it "should allow custom name for index" do
254
+ index_name = 'comments_post_id_unique_index'
255
+ add_column(:post_id, :integer, :index => { :unique => true, :name => index_name }) do
256
+ @model.should have_unique_index(:name => index_name).on(:post_id)
257
+ end
258
+ end
259
+
260
+ it "should auto-index if specified in global options" do
261
+ SchemaPlus.config.foreign_keys.auto_index = true
262
+ add_column(:post_id, :integer) do
263
+ @model.should have_index.on(:post_id)
264
+ end
265
+ SchemaPlus.config.foreign_keys.auto_index = false
266
+ end
267
+
268
+ it "should auto-index foreign keys only" do
269
+ SchemaPlus.config.foreign_keys.auto_index = true
270
+ add_column(:state, :integer) do
271
+ @model.should_not have_index.on(:state)
272
+ end
273
+ SchemaPlus.config.foreign_keys.auto_index = false
274
+ end
275
+
276
+ it "should allow to overwrite auto_index options in column definition" do
277
+ SchemaPlus.config.foreign_keys.auto_index = true
278
+ add_column(:post_id, :integer, :index => false) do
279
+ # MySQL creates an index on foreign by default
280
+ # and we can do nothing with that
281
+ unless SchemaPlusHelpers.mysql?
282
+ @model.should_not have_index.on(:post_id)
283
+ end
284
+ end
285
+ SchemaPlus.config.foreign_keys.auto_index = false
286
+ end
287
+
288
+ it "should use default on_update action" do
289
+ SchemaPlus.config.foreign_keys.on_update = :cascade
290
+ add_column(:post_id, :integer) do
291
+ @model.should reference.on(:post_id).on_update(:cascade)
292
+ end
293
+ SchemaPlus.config.foreign_keys.on_update = nil
294
+ end
295
+
296
+ it "should use default on_delete action" do
297
+ SchemaPlus.config.foreign_keys.on_delete = :cascade
298
+ add_column(:post_id, :integer) do
299
+ @model.should reference.on(:post_id).on_delete(:cascade)
300
+ end
301
+ SchemaPlus.config.foreign_keys.on_delete = nil
302
+ end
303
+
304
+ it "should allow to overwrite default actions" do
305
+ SchemaPlus.config.foreign_keys.on_delete = :cascade
306
+ SchemaPlus.config.foreign_keys.on_update = :restrict
307
+ add_column(:post_id, :integer, :on_update => :set_null, :on_delete => :set_null) do
308
+ @model.should reference.on(:post_id).on_delete(:set_null).on_update(:set_null)
309
+ end
310
+ SchemaPlus.config.foreign_keys.on_delete = nil
311
+ end
312
+
313
+ protected
314
+ def add_column(column_name, *args)
315
+ table = @model.table_name
316
+ ActiveRecord::Migration.suppress_messages do
317
+ ActiveRecord::Migration.add_column(table, column_name, *args)
318
+ @model.reset_column_information
319
+ yield if block_given?
320
+ ActiveRecord::Migration.remove_column(table, column_name)
321
+ end
322
+ end
323
+
324
+ end
325
+
326
+ context "when column is changed" do
327
+
328
+ before(:each) do
329
+ @model = Comment
330
+ end
331
+
332
+ it "should create foreign key" do
333
+ change_column :user, :string, :references => [:users, :login]
334
+ @model.should reference(:users, :login).on(:user)
335
+ change_column :user, :string, :references => nil
336
+ end
337
+
338
+ context "and initially references to users table" do
339
+
340
+ it "should have foreign key" do
341
+ @model.should reference(:users)
342
+ end
343
+
344
+ it "should drop foreign key afterwards" do
345
+ change_column :user_id, :integer, :references => :members
346
+ @model.should_not reference(:users)
347
+ change_column :user_id, :integer, :references => :users
348
+ end
349
+
350
+ it "should reference pointed table afterwards" do
351
+ change_column :user_id, :integer, :references => :members
352
+ @model.should reference(:members)
353
+ end
354
+
355
+ end
356
+
357
+ protected
358
+ def change_column(column_name, *args)
359
+ table = @model.table_name
360
+ ActiveRecord::Migration.suppress_messages do
361
+ ActiveRecord::Migration.change_column(table, column_name, *args)
362
+ @model.reset_column_information
363
+ end
364
+ end
365
+
366
+ end
367
+ end
368
+
369
+ def foreign_key(model, column)
370
+ columns = Array(column).collect(&:to_s)
371
+ model.foreign_keys.detect { |fk| fk.table_name == model.table_name && fk.column_names == columns }
372
+ end
373
+
374
+ def create_table_opts(model, table_options, columns_with_options, indexes={})
375
+ ActiveRecord::Migration.suppress_messages do
376
+ ActiveRecord::Migration.create_table model.table_name, table_options.merge(:force => true) do |t|
377
+ columns_with_options.each_pair do |column, options|
378
+ method = options.delete(:METHOD) || :integer
379
+ t.send method, column, options
380
+ end
381
+ indexes.each_pair do |column, options|
382
+ t.index column, options
383
+ end
384
+ end
385
+ model.reset_column_information
386
+ end
387
+ end
388
+
389
+ def create_table(model, columns_with_options)
390
+ create_table_opts(model, {}, columns_with_options)
391
+ end
392
+
393
+ def with_fk_config(opts, &block)
394
+ save = Hash[opts.keys.collect{|key| [key, SchemaPlus.config.foreign_keys.send(key)]}]
395
+ begin
396
+ SchemaPlus.config.foreign_keys.update_attributes(opts)
397
+ yield
398
+ ensure
399
+ SchemaPlus.config.foreign_keys.update_attributes(save)
400
+ end
401
+ end
402
+
403
+
404
+ end
405
+
@@ -0,0 +1,2 @@
1
+ class Comment < ::ActiveRecord::Base
2
+ end
@@ -0,0 +1,2 @@
1
+ class Post < ActiveRecord::Base
2
+ end
@@ -0,0 +1,2 @@
1
+ class User < ActiveRecord::Base
2
+ end
@@ -0,0 +1,78 @@
1
+ # encoding: utf-8
2
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
3
+
4
+ describe 'get_references method' do
5
+
6
+ before(:all) do
7
+ @target = ActiveRecord::Migration
8
+ @table_name = 'comments'
9
+ @column_name = 'post_id'
10
+ @destination_table = 'posts'
11
+ @destinantion_column = :id
12
+ end
13
+
14
+ around(:each) do |example|
15
+ with_auto_create do
16
+ example.run
17
+ end
18
+ end
19
+
20
+ it "should accept table name and column name to references" do
21
+ lambda { @target.get_references(@table_name, @column_name) }.should_not raise_error
22
+ end
23
+
24
+ it "should return an array" do
25
+ @target.get_references(@table_name, @column_name).should be_an(Array)
26
+ end
27
+
28
+ it "should return nil if auto_create is disabled" do
29
+ SchemaPlus.config.foreign_keys.auto_create = false
30
+ @target.get_references(@table_name, @column_name).should be_nil
31
+ end
32
+
33
+ it "should split column name to table name and primary key" do
34
+ result = @target.get_references(@table_name, @column_name)
35
+ result[0].should eql @destination_table
36
+ result[1].should eql @destinantion_column
37
+ end
38
+
39
+ it "should not auto create referencs when configured not to" do
40
+ with_auto_create(false) do
41
+ result = @target.get_references(@table_name, @column_name)
42
+ result.should be_nil
43
+ end
44
+ end
45
+
46
+ it "should handle parent_id as belonging to the same table" do
47
+ column_name = 'parent_id'
48
+ result = @target.get_references(@table_name, column_name)
49
+ result[0].should eql @table_name
50
+ result[1].should eql :id
51
+ end
52
+
53
+ it "should accept :references option which overrides default table name" do
54
+ result = @target.get_references(@table_name, @column_name, :references => 'users')
55
+ result[0].should eql 'users'
56
+ result[1].should eql :id
57
+ end
58
+
59
+ it "should accept :references option which overrides default table name and default column name" do
60
+ result = @target.get_references(@table_name, @column_name, :references => ['users', 'uuid'])
61
+ result[0].should eql 'users'
62
+ result[1].should eql 'uuid'
63
+ end
64
+
65
+ protected
66
+
67
+ def with_auto_create(value = true)
68
+ old_value = SchemaPlus.config.foreign_keys.auto_create
69
+ SchemaPlus.config.foreign_keys.auto_create = value
70
+ begin
71
+ yield
72
+ ensure
73
+ SchemaPlus.config.foreign_keys.auto_create = old_value
74
+ end
75
+ end
76
+
77
+ end
78
+