schema_associations 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.
@@ -0,0 +1,543 @@
1
+
2
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
3
+
4
+ describe ActiveRecord::Base do
5
+
6
+ after(:each) do
7
+ remove_all_models
8
+ end
9
+
10
+ context "in basic case" do
11
+ before(:each) do
12
+ create_tables(
13
+ "posts", {}, {},
14
+ "comments", {}, { :post_id => {} }
15
+ )
16
+ class Post < ActiveRecord::Base ; end
17
+ class Comment < ActiveRecord::Base ; end
18
+ end
19
+
20
+ after(:each) do
21
+ Comment.destroy_all
22
+ Post.destroy_all
23
+ end
24
+
25
+ it "should create belongs_to association when reflecting on it" do
26
+ reflection = Comment.reflect_on_association(:post)
27
+ reflection.should_not be_nil
28
+ reflection.macro.should == :belongs_to
29
+ reflection.options[:class_name].should == "Post"
30
+ reflection.options[:foreign_key].should == "post_id"
31
+ end
32
+
33
+ it "should create association when reflecting on all associations" do
34
+ reflection = Comment.reflect_on_all_associations.first
35
+ reflection.should_not be_nil
36
+ reflection.macro.should == :belongs_to
37
+ reflection.options[:class_name].should == "Post"
38
+ reflection.options[:foreign_key].should == "post_id"
39
+ end
40
+
41
+ it "should create association when accessing it" do
42
+ post = Post.create
43
+ comment = Comment.create(:post_id => post.id)
44
+ comment.post.id.should == post.id
45
+ end
46
+
47
+ it "should create association when creating record" do
48
+ post = Post.create
49
+ comment = Comment.create(:post => post)
50
+ comment.reload.post.id.should == post.id
51
+ end
52
+
53
+ it "should create has_many association" do
54
+ reflection = Post.reflect_on_association(:comments)
55
+ reflection.should_not be_nil
56
+ reflection.macro.should == :has_many
57
+ reflection.options[:class_name].should == "Comment"
58
+ reflection.options[:foreign_key].should == "post_id"
59
+ end
60
+ it "shouldn't raise an exception when model is instantiated" do
61
+ expect { Post.new }.should_not raise_error
62
+ end
63
+ end
64
+
65
+ it "should override auto_create negatively" do
66
+ with_associations_auto_create(true) do
67
+ create_tables(
68
+ "posts", {}, {},
69
+ "comments", {}, { :post_id => {} }
70
+ )
71
+ class Post < ActiveRecord::Base
72
+ schema_associations :auto_create => false
73
+ end
74
+ class Comment < ActiveRecord::Base ; end
75
+ Post.reflect_on_association(:comments).should be_nil
76
+ Comment.reflect_on_association(:post).should_not be_nil
77
+ end
78
+ end
79
+
80
+ context "with multiple associations of all types" do
81
+ before(:each) do
82
+ create_tables(
83
+ "owners", {}, {},
84
+ "colors", {}, {},
85
+ "widgets", {}, {
86
+ :owner_id => {},
87
+ },
88
+ "parts", {}, { :widget_id => {} },
89
+ "manifests", {}, { :widget_id => { :index => {:unique => true}} },
90
+ "colors_widgets", {:id => false}, { :widget_id => {}, :color_id => {} }
91
+ )
92
+ end
93
+
94
+ def check_reflections(hash)
95
+ hash.each do |key, val|
96
+ reflection = Widget.reflect_on_association(key)
97
+ case val
98
+ when true then reflection.should_not be_nil
99
+ else reflection.should be_nil
100
+ end
101
+ end
102
+ end
103
+
104
+ it "should default as expected" do
105
+ class Widget < ActiveRecord::Base ; end
106
+ check_reflections(:owner => true, :colors => true, :parts => true, :manifest => true)
107
+ end
108
+
109
+ it "should respect :only" do
110
+ class Widget < ActiveRecord::Base
111
+ schema_associations :only => :owner
112
+ end
113
+ check_reflections(:owner => true, :colors => false, :parts => false, :manifest => false)
114
+ end
115
+
116
+ it "should respect :except" do
117
+ class Widget < ActiveRecord::Base
118
+ schema_associations :except => :owner
119
+ end
120
+ check_reflections(:owner => false, :colors => true, :parts => true, :manifest => true)
121
+ end
122
+
123
+ it "should respect :only_type :belongs_to" do
124
+ class Widget < ActiveRecord::Base
125
+ schema_associations :only_type => :belongs_to
126
+ end
127
+ check_reflections(:owner => true, :colors => false, :parts => false, :manifest => false)
128
+ end
129
+
130
+ it "should respect :except_type :belongs_to" do
131
+ class Widget < ActiveRecord::Base
132
+ schema_associations :except_type => :belongs_to
133
+ end
134
+ check_reflections(:owner => false, :colors => true, :parts => true, :manifest => true)
135
+ end
136
+
137
+ it "should respect :only_type :has_many" do
138
+ class Widget < ActiveRecord::Base
139
+ schema_associations :only_type => :has_many
140
+ end
141
+ check_reflections(:owner => false, :colors => false, :parts => true, :manifest => false)
142
+ end
143
+
144
+ it "should respect :except_type :has_many" do
145
+ class Widget < ActiveRecord::Base
146
+ schema_associations :except_type => :has_many
147
+ end
148
+ check_reflections(:owner => true, :colors => true, :parts => false, :manifest => true)
149
+ end
150
+
151
+ it "should respect :only_type :has_one" do
152
+ class Widget < ActiveRecord::Base
153
+ schema_associations :only_type => :has_one
154
+ end
155
+ check_reflections(:owner => false, :colors => false, :parts => false, :manifest => true)
156
+ end
157
+
158
+ it "should respect :except_type :has_one" do
159
+ class Widget < ActiveRecord::Base
160
+ schema_associations :except_type => :has_one
161
+ end
162
+ check_reflections(:owner => true, :colors => true, :parts => true, :manifest => false)
163
+ end
164
+
165
+ it "should respect :only_type :has_and_belongs_to_many" do
166
+ class Widget < ActiveRecord::Base
167
+ schema_associations :only_type => :has_and_belongs_to_many
168
+ end
169
+ check_reflections(:owner => false, :colors => true, :parts => false, :manifest => false)
170
+ end
171
+
172
+ it "should respect :except_type :has_and_belongs_to_many" do
173
+ class Widget < ActiveRecord::Base
174
+ schema_associations :except_type => :has_and_belongs_to_many
175
+ end
176
+ check_reflections(:owner => true, :colors => false, :parts => true, :manifest => true)
177
+ end
178
+
179
+ end
180
+
181
+ it "should override auto_create positively" do
182
+ with_associations_auto_create(false) do
183
+ create_tables(
184
+ "posts", {}, {},
185
+ "comments", {}, { :post_id => {} }
186
+ )
187
+ class Post < ActiveRecord::Base
188
+ schema_associations :auto_create => true
189
+ end
190
+ class Comment < ActiveRecord::Base ; end
191
+ Post.reflect_on_association(:comments).should_not be_nil
192
+ Comment.reflect_on_association(:post).should be_nil
193
+ end
194
+ end
195
+
196
+
197
+ context "with unique index" do
198
+ before(:each) do
199
+ create_tables(
200
+ "posts", {}, {},
201
+ "comments", {}, { :post_id => {:index => { :unique => true} } }
202
+ )
203
+ class Post < ActiveRecord::Base ; end
204
+ class Comment < ActiveRecord::Base ; end
205
+ end
206
+ it "should create has_one association" do
207
+ reflection = Post.reflect_on_association(:comment)
208
+ reflection.should_not be_nil
209
+ reflection.macro.should == :has_one
210
+ reflection.options[:class_name].should == "Comment"
211
+ reflection.options[:foreign_key].should == "post_id"
212
+ end
213
+ end
214
+
215
+ context "with prefixed column names" do
216
+ before(:each) do
217
+ create_tables(
218
+ "posts", {}, {},
219
+ "comments", {}, { :subject_post_id => { :references => :posts} }
220
+ )
221
+ class Post < ActiveRecord::Base ; end
222
+ class Comment < ActiveRecord::Base ; end
223
+ end
224
+ it "should name belongs_to according to column" do
225
+ reflection = Comment.reflect_on_association(:subject_post)
226
+ reflection.should_not be_nil
227
+ reflection.macro.should == :belongs_to
228
+ reflection.options[:class_name].should == "Post"
229
+ reflection.options[:foreign_key].should == "subject_post_id"
230
+ end
231
+
232
+ it "should name has_many using 'as column'" do
233
+ reflection = Post.reflect_on_association(:comments_as_subject)
234
+ reflection.should_not be_nil
235
+ reflection.macro.should == :has_many
236
+ reflection.options[:class_name].should == "Comment"
237
+ reflection.options[:foreign_key].should == "subject_post_id"
238
+ end
239
+ end
240
+
241
+ context "with suffixed column names" do
242
+ before(:each) do
243
+ create_tables(
244
+ "posts", {}, {},
245
+ "comments", {}, { :post_cited => { :references => :posts} }
246
+ )
247
+ class Post < ActiveRecord::Base ; end
248
+ class Comment < ActiveRecord::Base ; end
249
+ end
250
+ it "should name belongs_to according to column" do
251
+ reflection = Comment.reflect_on_association(:post_cited)
252
+ reflection.should_not be_nil
253
+ reflection.macro.should == :belongs_to
254
+ reflection.options[:class_name].should == "Post"
255
+ reflection.options[:foreign_key].should == "post_cited"
256
+ end
257
+
258
+ it "should name has_many using 'as column'" do
259
+ reflection = Post.reflect_on_association(:comments_as_cited)
260
+ reflection.should_not be_nil
261
+ reflection.macro.should == :has_many
262
+ reflection.options[:class_name].should == "Comment"
263
+ reflection.options[:foreign_key].should == "post_cited"
264
+ end
265
+ end
266
+
267
+ context "with arbitrary column names" do
268
+ before(:each) do
269
+ create_tables(
270
+ "posts", {}, {},
271
+ "comments", {}, { :subject => {:references => :posts} }
272
+ )
273
+ class Post < ActiveRecord::Base ; end
274
+ class Comment < ActiveRecord::Base ; end
275
+ end
276
+ it "should name belongs_to according to column" do
277
+ reflection = Comment.reflect_on_association(:subject)
278
+ reflection.should_not be_nil
279
+ reflection.macro.should == :belongs_to
280
+ reflection.options[:class_name].should == "Post"
281
+ reflection.options[:foreign_key].should == "subject"
282
+ end
283
+
284
+ it "should name has_many using 'as column'" do
285
+ reflection = Post.reflect_on_association(:comments_as_subject)
286
+ reflection.should_not be_nil
287
+ reflection.macro.should == :has_many
288
+ reflection.options[:class_name].should == "Comment"
289
+ reflection.options[:foreign_key].should == "subject"
290
+ end
291
+ end
292
+
293
+
294
+ context "with position" do
295
+ before(:each) do
296
+ create_tables(
297
+ "posts", {}, {},
298
+ "comments", {}, { :post_id => {}, :position => {} }
299
+ )
300
+ class Post < ActiveRecord::Base ; end
301
+ class Comment < ActiveRecord::Base ; end
302
+ end
303
+ it "should create ordered has_many association" do
304
+ reflection = Post.reflect_on_association(:comments)
305
+ reflection.should_not be_nil
306
+ reflection.macro.should == :has_many
307
+ reflection.options[:class_name].should == "Comment"
308
+ reflection.options[:foreign_key].should == "post_id"
309
+ reflection.options[:order].to_s.should == "position"
310
+ end
311
+ end
312
+
313
+ context "regarding parent-child relationships" do
314
+
315
+ let (:migration) {ActiveRecord::Migration}
316
+
317
+ before(:each) do
318
+ create_tables(
319
+ "nodes", {:foreign_keys => {:auto_index => false}}, { :parent_id => {} }
320
+ )
321
+ end
322
+
323
+ it "should use children as the inverse of parent" do
324
+ class Node < ActiveRecord::Base ; end
325
+ reflection = Node.reflect_on_association(:children)
326
+ reflection.should_not be_nil
327
+ end
328
+
329
+ it "should use child as the singular inverse of parent" do
330
+ migration.suppress_messages do
331
+ migration.add_index(:nodes, :parent_id, :unique => true)
332
+ end
333
+ class Node < ActiveRecord::Base ; end
334
+ reflection = Node.reflect_on_association(:child)
335
+ reflection.should_not be_nil
336
+ end
337
+ end
338
+
339
+
340
+ context "regarding concise names" do
341
+
342
+ def prefix_one
343
+ create_tables(
344
+ "posts", {}, {},
345
+ "post_comments", {}, { :post_id => {} }
346
+ )
347
+ Object.const_set(:Post, Class.new(ActiveRecord::Base))
348
+ Object.const_set(:PostComment, Class.new(ActiveRecord::Base))
349
+ end
350
+
351
+ def suffix_one
352
+ create_tables(
353
+ "posts", {}, {},
354
+ "comment_posts", {}, { :post_id => {} }
355
+ )
356
+ Object.const_set(:Post, Class.new(ActiveRecord::Base))
357
+ Object.const_set(:CommentPost, Class.new(ActiveRecord::Base))
358
+ end
359
+
360
+ def prefix_both
361
+ create_tables(
362
+ "blog_page_posts", {}, {},
363
+ "blog_page_comments", {}, { :blog_page_post_id => {} }
364
+ )
365
+ Object.const_set(:BlogPagePost, Class.new(ActiveRecord::Base))
366
+ Object.const_set(:BlogPageComment, Class.new(ActiveRecord::Base))
367
+ end
368
+
369
+ it "should use concise association name for one prefix" do
370
+ with_associations_config(:auto_create => true, :concise_names => true) do
371
+ prefix_one
372
+ reflection = Post.reflect_on_association(:comments)
373
+ reflection.should_not be_nil
374
+ reflection.macro.should == :has_many
375
+ reflection.options[:class_name].should == "PostComment"
376
+ reflection.options[:foreign_key].should == "post_id"
377
+ end
378
+ end
379
+
380
+ it "should use concise association name for one suffix" do
381
+ with_associations_config(:auto_create => true, :concise_names => true) do
382
+ suffix_one
383
+ reflection = Post.reflect_on_association(:comments)
384
+ reflection.should_not be_nil
385
+ reflection.macro.should == :has_many
386
+ reflection.options[:class_name].should == "CommentPost"
387
+ reflection.options[:foreign_key].should == "post_id"
388
+ end
389
+ end
390
+
391
+ it "should use concise association name for shared prefixes" do
392
+ with_associations_config(:auto_create => true, :concise_names => true) do
393
+ prefix_both
394
+ reflection = BlogPagePost.reflect_on_association(:comments)
395
+ reflection.should_not be_nil
396
+ reflection.macro.should == :has_many
397
+ reflection.options[:class_name].should == "BlogPageComment"
398
+ reflection.options[:foreign_key].should == "blog_page_post_id"
399
+ end
400
+ end
401
+
402
+ it "should use full names and not concise names when so configured" do
403
+ with_associations_config(:auto_create => true, :concise_names => false) do
404
+ prefix_one
405
+ reflection = Post.reflect_on_association(:post_comments)
406
+ reflection.should_not be_nil
407
+ reflection.macro.should == :has_many
408
+ reflection.options[:class_name].should == "PostComment"
409
+ reflection.options[:foreign_key].should == "post_id"
410
+ reflection = Post.reflect_on_association(:comments)
411
+ reflection.should be_nil
412
+ end
413
+ end
414
+
415
+ it "should use concise names and not full names when so configured" do
416
+ with_associations_config(:auto_create => true, :concise_names => true) do
417
+ prefix_one
418
+ reflection = Post.reflect_on_association(:comments)
419
+ reflection.should_not be_nil
420
+ reflection.macro.should == :has_many
421
+ reflection.options[:class_name].should == "PostComment"
422
+ reflection.options[:foreign_key].should == "post_id"
423
+ reflection = Post.reflect_on_association(:post_comments)
424
+ reflection.should be_nil
425
+ end
426
+ end
427
+
428
+
429
+ end
430
+
431
+ context "with joins table" do
432
+ before(:each) do
433
+ create_tables(
434
+ "posts", {}, {},
435
+ "tags", {}, {},
436
+ "posts_tags", {:id => false}, { :post_id => {}, :tag_id => {}}
437
+ )
438
+ class Post < ActiveRecord::Base ; end
439
+ class Tag < ActiveRecord::Base ; end
440
+ end
441
+ it "should create has_and_belongs_to_many association" do
442
+ reflection = Post.reflect_on_association(:tags)
443
+ reflection.should_not be_nil
444
+ reflection.macro.should == :has_and_belongs_to_many
445
+ reflection.options[:class_name].should == "Tag"
446
+ reflection.options[:join_table].should == "posts_tags"
447
+ end
448
+ end
449
+
450
+ context "regarding existing methods" do
451
+ before(:each) do
452
+ create_tables(
453
+ "types", {}, {},
454
+ "posts", {}, {:type_id => {}}
455
+ )
456
+ end
457
+ it "should define association normally if no existing method is defined" do
458
+ class Type < ActiveRecord::Base ; end
459
+ Type.reflect_on_association(:posts).should_not be_nil # sanity check for this context
460
+ end
461
+ it "should not define association over existing public method" do
462
+ class Type < ActiveRecord::Base
463
+ def posts
464
+ :existing
465
+ end
466
+ end
467
+ Type.reflect_on_association(:posts).should be_nil
468
+ end
469
+ it "should not define association over existing private method" do
470
+ class Type < ActiveRecord::Base
471
+ private
472
+ def posts
473
+ :existing
474
+ end
475
+ end
476
+ Type.reflect_on_association(:posts).should be_nil
477
+ end
478
+ it "should define association :type over (deprecated) kernel method" do
479
+ class Post < ActiveRecord::Base ; end
480
+ Post.reflect_on_association(:type).should_not be_nil
481
+ end
482
+ it "should not define association :type over model method" do
483
+ class Post < ActiveRecord::Base
484
+ def type
485
+ :existing
486
+ end
487
+ end
488
+ Post.reflect_on_association(:type).should be_nil
489
+ end
490
+ end
491
+
492
+ context "regarding relations" do
493
+ before(:each) do
494
+ create_tables(
495
+ "posts", {}, {},
496
+ "comments", {}, { :post_id => {} }
497
+ )
498
+ class Post < ActiveRecord::Base ; end
499
+ class Comment < ActiveRecord::Base ; end
500
+ end
501
+
502
+ it "should define associations before needed by relation" do
503
+ Post.joins(:comments).all
504
+ expect { Post.joins(:comments).all }.should_not raise_error
505
+
506
+ end
507
+
508
+ end
509
+
510
+ protected
511
+
512
+ def with_associations_auto_create(value, &block)
513
+ with_associations_config(:auto_create => value, &block)
514
+ end
515
+
516
+ def with_associations_config(opts, &block)
517
+ save = Hash[opts.keys.collect{|key| [key, SchemaAssociations.config.send(key)]}]
518
+ begin
519
+ SchemaAssociations.setup do |config|
520
+ config.update_attributes(opts)
521
+ yield
522
+ end
523
+ ensure
524
+ SchemaAssociations.config.update_attributes(save)
525
+ end
526
+ end
527
+
528
+ def create_tables(*table_defs)
529
+ ActiveRecord::Migration.suppress_messages do
530
+ ActiveRecord::Base.connection.tables.each do |table|
531
+ ActiveRecord::Migration.drop_table table
532
+ end
533
+ table_defs.each_slice(3) do |table_name, opts, columns_with_options|
534
+ ActiveRecord::Migration.create_table table_name, opts do |t|
535
+ columns_with_options.each_pair do |column, options|
536
+ t.integer column, options
537
+ end
538
+ end
539
+ end
540
+ end
541
+ end
542
+
543
+ end
@@ -0,0 +1,10 @@
1
+
2
+ ActiveRecord::Base.configurations = {
3
+ 'schema_associations' => {
4
+ :adapter => 'sqlite3',
5
+ :database => File.expand_path('schema_associations.sqlite3', File.dirname(__FILE__)),
6
+ }
7
+
8
+ }
9
+
10
+ ActiveRecord::Base.establish_connection 'schema_associations'
@@ -0,0 +1,29 @@
1
+ if RUBY_VERSION > "1.9"
2
+ require 'simplecov'
3
+ require 'simplecov-gem-adapter'
4
+ SimpleCov.start "gem"
5
+ end
6
+
7
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
8
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
9
+
10
+ require 'rspec'
11
+ require 'active_record'
12
+ require 'schema_associations'
13
+ require 'logger'
14
+ require 'connection'
15
+
16
+ ActiveRecord::Base.logger = Logger.new(File.open("spec.log", "w"))
17
+
18
+ Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|f| require f}
19
+
20
+ def remove_all_models
21
+ ObjectSpace.each_object(Class) do |c|
22
+ next unless c.ancestors.include? ActiveRecord::Base
23
+ next if c == ActiveRecord::Base
24
+ next if c.name.blank?
25
+ ActiveSupport::Dependencies.remove_constant c.name
26
+ end
27
+ end
28
+
29
+ SimpleCov.command_name ActiveRecord::Base.connection.adapter_name if defined? SimpleCov