schema_plus 0.1.0.pre1

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 +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
+