sequel 3.5.0 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/CHANGELOG +108 -0
  2. data/README.rdoc +25 -14
  3. data/Rakefile +20 -1
  4. data/doc/advanced_associations.rdoc +61 -64
  5. data/doc/cheat_sheet.rdoc +16 -7
  6. data/doc/opening_databases.rdoc +3 -3
  7. data/doc/prepared_statements.rdoc +1 -1
  8. data/doc/reflection.rdoc +2 -1
  9. data/doc/release_notes/3.6.0.txt +366 -0
  10. data/doc/schema.rdoc +19 -14
  11. data/lib/sequel/adapters/amalgalite.rb +5 -27
  12. data/lib/sequel/adapters/jdbc.rb +13 -3
  13. data/lib/sequel/adapters/jdbc/h2.rb +17 -0
  14. data/lib/sequel/adapters/jdbc/mysql.rb +20 -7
  15. data/lib/sequel/adapters/mysql.rb +4 -3
  16. data/lib/sequel/adapters/oracle.rb +1 -1
  17. data/lib/sequel/adapters/postgres.rb +87 -28
  18. data/lib/sequel/adapters/shared/mssql.rb +47 -6
  19. data/lib/sequel/adapters/shared/mysql.rb +12 -31
  20. data/lib/sequel/adapters/shared/postgres.rb +15 -12
  21. data/lib/sequel/adapters/shared/sqlite.rb +18 -0
  22. data/lib/sequel/adapters/sqlite.rb +1 -16
  23. data/lib/sequel/connection_pool.rb +1 -1
  24. data/lib/sequel/core.rb +1 -1
  25. data/lib/sequel/database.rb +1 -1
  26. data/lib/sequel/database/schema_generator.rb +2 -0
  27. data/lib/sequel/database/schema_sql.rb +1 -1
  28. data/lib/sequel/dataset.rb +5 -179
  29. data/lib/sequel/dataset/actions.rb +123 -0
  30. data/lib/sequel/dataset/convenience.rb +18 -10
  31. data/lib/sequel/dataset/features.rb +65 -0
  32. data/lib/sequel/dataset/prepared_statements.rb +29 -23
  33. data/lib/sequel/dataset/query.rb +429 -0
  34. data/lib/sequel/dataset/sql.rb +67 -435
  35. data/lib/sequel/model/associations.rb +77 -13
  36. data/lib/sequel/model/base.rb +30 -8
  37. data/lib/sequel/model/errors.rb +4 -4
  38. data/lib/sequel/plugins/caching.rb +38 -15
  39. data/lib/sequel/plugins/force_encoding.rb +18 -7
  40. data/lib/sequel/plugins/hook_class_methods.rb +4 -0
  41. data/lib/sequel/plugins/many_through_many.rb +1 -1
  42. data/lib/sequel/plugins/nested_attributes.rb +40 -11
  43. data/lib/sequel/plugins/serialization.rb +17 -3
  44. data/lib/sequel/plugins/validation_helpers.rb +65 -18
  45. data/lib/sequel/sql.rb +23 -1
  46. data/lib/sequel/version.rb +1 -1
  47. data/spec/adapters/mssql_spec.rb +96 -10
  48. data/spec/adapters/mysql_spec.rb +19 -0
  49. data/spec/adapters/postgres_spec.rb +65 -2
  50. data/spec/adapters/sqlite_spec.rb +10 -0
  51. data/spec/core/core_sql_spec.rb +9 -0
  52. data/spec/core/database_spec.rb +8 -4
  53. data/spec/core/dataset_spec.rb +122 -29
  54. data/spec/core/expression_filters_spec.rb +17 -0
  55. data/spec/extensions/caching_spec.rb +43 -3
  56. data/spec/extensions/force_encoding_spec.rb +43 -1
  57. data/spec/extensions/nested_attributes_spec.rb +55 -2
  58. data/spec/extensions/validation_helpers_spec.rb +71 -0
  59. data/spec/integration/associations_test.rb +281 -0
  60. data/spec/integration/dataset_test.rb +383 -9
  61. data/spec/integration/eager_loader_test.rb +0 -65
  62. data/spec/integration/model_test.rb +110 -0
  63. data/spec/integration/plugin_test.rb +306 -0
  64. data/spec/integration/prepared_statement_test.rb +32 -0
  65. data/spec/integration/schema_test.rb +8 -3
  66. data/spec/integration/spec_helper.rb +1 -25
  67. data/spec/model/association_reflection_spec.rb +38 -0
  68. data/spec/model/associations_spec.rb +184 -8
  69. data/spec/model/eager_loading_spec.rb +23 -0
  70. data/spec/model/model_spec.rb +8 -0
  71. data/spec/model/record_spec.rb +84 -1
  72. metadata +9 -2
@@ -75,13 +75,6 @@ describe "Eagerly loading a tree structure" do
75
75
 
76
76
  it "#descendants should get all descendants in one call" do
77
77
  nodes = Node.filter(:id=>1).eager(:descendants).all
78
- sqls_should_be('SELECT * FROM nodes WHERE (id = 1)',
79
- 'SELECT * FROM nodes WHERE ((parent_id IN (1)) AND (id != parent_id))',
80
- 'SELECT * FROM nodes WHERE ((parent_id IN (2, 3)) AND (id != parent_id))',
81
- 'SELECT * FROM nodes WHERE ((parent_id IN (4)) AND (id != parent_id))',
82
- 'SELECT * FROM nodes WHERE ((parent_id IN (5)) AND (id != parent_id))',
83
- 'SELECT * FROM nodes WHERE ((parent_id IN (6)) AND (id != parent_id))',
84
- 'SELECT * FROM nodes WHERE ((parent_id IN (7)) AND (id != parent_id))')
85
78
  nodes.length.should == 1
86
79
  node = nodes.first
87
80
  node.pk.should == 1
@@ -104,17 +97,10 @@ describe "Eagerly loading a tree structure" do
104
97
  node.children.length.should == 1
105
98
  node.children.first.pk.should == 7
106
99
  node.children.first.parent.should == node
107
- sqls_should_be
108
100
  end
109
101
 
110
102
  it "#ancestors should get all ancestors in one call" do
111
103
  nodes = Node.filter(:id=>[7,3]).order(:id).eager(:ancestors).all
112
- sqls_should_be('SELECT * FROM nodes WHERE (id IN (7, 3)) ORDER BY id',
113
- 'SELECT * FROM nodes WHERE (id IN (1, 6))',
114
- 'SELECT * FROM nodes WHERE (id IN (5))',
115
- 'SELECT * FROM nodes WHERE (id IN (4))',
116
- 'SELECT * FROM nodes WHERE (id IN (2))',
117
- 'SELECT * FROM nodes WHERE (id IN (1))')
118
104
  nodes.length.should == 2
119
105
  nodes.collect{|x| x.pk}.should == [3, 7]
120
106
  nodes.first.parent.pk.should == 1
@@ -130,7 +116,6 @@ describe "Eagerly loading a tree structure" do
130
116
  node = node.parent
131
117
  node.parent.pk.should == 1
132
118
  node.parent.parent.should == nil
133
- sqls_should_be
134
119
  end
135
120
  end
136
121
 
@@ -170,32 +155,19 @@ describe "Association Extensions" do
170
155
 
171
156
  it "should allow methods to be called on the dataset method" do
172
157
  Authorship.count.should == 0
173
- sqls_should_be('SELECT COUNT(*) AS \'count\' FROM authorships LIMIT 1')
174
158
  authorship = @author.authorships_dataset.find_or_create_by_name('Bob')
175
- sqls_should_be("SELECT * FROM authorships WHERE ((authorships.author_id = 1) AND (name = 'Bob')) LIMIT 1",
176
- /INSERT INTO authorships \((author_id, name|name, author_id)\) VALUES \((1, 'Bob'|'Bob', 1)\)/,
177
- "SELECT * FROM authorships WHERE (id = 1) LIMIT 1")
178
159
  Authorship.count.should == 1
179
160
  Authorship.first.should == authorship
180
- sqls_should_be('SELECT COUNT(*) AS \'count\' FROM authorships LIMIT 1', "SELECT * FROM authorships LIMIT 1")
181
161
  authorship.name.should == 'Bob'
182
162
  authorship.author_id.should == @author.id
183
163
  @author.authorships_dataset.find_or_create_by_name('Bob').should == authorship
184
- sqls_should_be("SELECT * FROM authorships WHERE ((authorships.author_id = 1) AND (name = 'Bob')) LIMIT 1")
185
164
  Authorship.count.should == 1
186
- sqls_should_be('SELECT COUNT(*) AS \'count\' FROM authorships LIMIT 1')
187
165
  authorship2 = @author.authorships_dataset.find_or_create(:name=>'Jim')
188
- sqls_should_be("SELECT * FROM authorships WHERE ((authorships.author_id = 1) AND (name = 'Jim')) LIMIT 1",
189
- /INSERT INTO authorships \((author_id, name|name, author_id)\) VALUES \((1, 'Jim'|'Jim', 1)\)/,
190
- "SELECT * FROM authorships WHERE (id = 2) LIMIT 1")
191
166
  Authorship.count.should == 2
192
- sqls_should_be('SELECT COUNT(*) AS \'count\' FROM authorships LIMIT 1')
193
167
  Authorship.order(:name).map(:name).should == ['Bob', 'Jim']
194
- sqls_should_be('SELECT * FROM authorships ORDER BY name')
195
168
  authorship2.name.should == 'Jim'
196
169
  authorship2.author_id.should == @author.id
197
170
  @author.authorships_dataset.find_or_create(:name=>'Jim').should == authorship2
198
- sqls_should_be("SELECT * FROM authorships WHERE ((authorships.author_id = 1) AND (name = 'Jim')) LIMIT 1")
199
171
  end
200
172
  end
201
173
 
@@ -290,19 +262,16 @@ describe "has_many :through has_many and has_one :through belongs_to" do
290
262
 
291
263
  it "should return has_many :through has_many records for a single object" do
292
264
  invs = @firm1.invoices.sort_by{|x| x.pk}
293
- sqls_should_be("SELECT invoices.id, invoices.client_id, client.id AS 'client_id_0', client.firm_id FROM invoices LEFT OUTER JOIN clients AS 'client' ON (client.id = invoices.client_id) WHERE (client.firm_id = 1)")
294
265
  invs.should == [@invoice1, @invoice2, @invoice3]
295
266
  invs[0].client.should == @client1
296
267
  invs[1].client.should == @client1
297
268
  invs[2].client.should == @client2
298
269
  invs.collect{|i| i.firm}.should == [@firm1, @firm1, @firm1]
299
270
  invs.collect{|i| i.client.firm}.should == [@firm1, @firm1, @firm1]
300
- sqls_should_be
301
271
  end
302
272
 
303
273
  it "should eagerly load has_many :through has_many records for multiple objects" do
304
274
  firms = Firm.order(:id).eager(:invoices).all
305
- sqls_should_be("SELECT * FROM firms ORDER BY id", "SELECT invoices.id, invoices.client_id, client.id AS 'client_id_0', client.firm_id FROM invoices LEFT OUTER JOIN clients AS 'client' ON (client.id = invoices.client_id) WHERE (client.firm_id IN (1, 2))")
306
275
  firms.should == [@firm1, @firm2]
307
276
  firm1, firm2 = firms
308
277
  invs1 = firm1.invoices.sort_by{|x| x.pk}
@@ -318,22 +287,18 @@ describe "has_many :through has_many and has_one :through belongs_to" do
318
287
  invs2.collect{|i| i.firm}.should == [@firm2, @firm2]
319
288
  invs1.collect{|i| i.client.firm}.should == [@firm1, @firm1, @firm1]
320
289
  invs2.collect{|i| i.client.firm}.should == [@firm2, @firm2]
321
- sqls_should_be
322
290
  end
323
291
 
324
292
  it "should return has_one :through belongs_to records for a single object" do
325
293
  firm = @invoice1.firm
326
- sqls_should_be("SELECT firms.id, clients.id AS 'clients_id', clients.firm_id FROM firms LEFT OUTER JOIN clients ON (clients.firm_id = firms.id) WHERE (clients.id = 1)")
327
294
  firm.should == @firm1
328
295
  @invoice1.client.should == @client1
329
296
  @invoice1.client.firm.should == @firm1
330
- sqls_should_be
331
297
  firm.associations[:clients].should == nil
332
298
  end
333
299
 
334
300
  it "should eagerly load has_one :through belongs_to records for multiple objects" do
335
301
  invs = Invoice.order(:id).eager(:firm).all
336
- sqls_should_be("SELECT * FROM invoices ORDER BY id", "SELECT firms.id, clients.id AS 'clients_id', clients.firm_id FROM firms LEFT OUTER JOIN clients ON (clients.firm_id = firms.id) WHERE (clients.id IN (1, 2, 3))")
337
302
  invs.should == [@invoice1, @invoice2, @invoice3, @invoice4, @invoice5]
338
303
  invs[0].firm.should == @firm1
339
304
  invs[0].client.should == @client1
@@ -355,7 +320,6 @@ describe "has_many :through has_many and has_one :through belongs_to" do
355
320
  invs[4].client.should == @client3
356
321
  invs[4].client.firm.should == @firm2
357
322
  invs[4].firm.associations[:clients].should == nil
358
- sqls_should_be
359
323
  end
360
324
  end
361
325
 
@@ -468,22 +432,18 @@ describe "Polymorphic Associations" do
468
432
  it "should load the correct associated object for a single object" do
469
433
  @asset1.attachable.should == @post
470
434
  @asset2.attachable.should == @note
471
- sqls_should_be("SELECT * FROM posts WHERE (id = 1) LIMIT 1", "SELECT * FROM notes WHERE (id = 2) LIMIT 1")
472
435
  end
473
436
 
474
437
  it "should eagerly load the correct associated object for a group of objects" do
475
438
  assets = Asset.order(:id).eager(:attachable).all
476
- sqls_should_be("SELECT * FROM assets ORDER BY id", "SELECT * FROM posts WHERE (id IN (1))", "SELECT * FROM notes WHERE (id IN (2))")
477
439
  assets.should == [@asset1, @asset2]
478
440
  assets[0].attachable.should == @post
479
441
  assets[1].attachable.should == @note
480
- sqls_should_be
481
442
  end
482
443
 
483
444
  it "should set items correctly" do
484
445
  @asset1.attachable = @note
485
446
  @asset2.attachable = @post
486
- sqls_should_be("SELECT * FROM posts WHERE (id = 1) LIMIT 1", "SELECT * FROM notes WHERE (id = 2) LIMIT 1")
487
447
  @asset1.attachable.should == @note
488
448
  @asset1.attachable_id.should == @note.pk
489
449
  @asset1.attachable_type.should == 'Note'
@@ -494,38 +454,29 @@ describe "Polymorphic Associations" do
494
454
  @asset1.attachable.should == nil
495
455
  @asset1.attachable_id.should == nil
496
456
  @asset1.attachable_type.should == nil
497
- sqls_should_be
498
457
  end
499
458
 
500
459
  it "should add items correctly" do
501
460
  @post.assets.should == [@asset1]
502
- sqls_should_be("SELECT * FROM assets WHERE ((assets.attachable_id = 1) AND (attachable_type = 'Post'))")
503
461
  @post.add_asset(@asset2)
504
- sqls_should_be(/UPDATE assets SET ((attachable_id = 1|attachable_type = 'Post'|id = 2)(, )?){3} WHERE \(id = 2\)/)
505
462
  @post.assets.should == [@asset1, @asset2]
506
463
  @asset2.attachable.should == @post
507
464
  @asset2.attachable_id.should == @post.pk
508
465
  @asset2.attachable_type.should == 'Post'
509
- sqls_should_be
510
466
  end
511
467
 
512
468
  it "should remove items correctly" do
513
469
  @note.assets.should == [@asset2]
514
- sqls_should_be("SELECT * FROM assets WHERE ((assets.attachable_id = 2) AND (attachable_type = 'Note'))")
515
470
  @note.remove_asset(@asset2)
516
- sqls_should_be(/UPDATE assets SET ((attachable_id = NULL|attachable_type = NULL|id = 2)(, )?){3} WHERE \(id = 2\)/)
517
471
  @note.assets.should == []
518
472
  @asset2.attachable.should == nil
519
473
  @asset2.attachable_id.should == nil
520
474
  @asset2.attachable_type.should == nil
521
- sqls_should_be
522
475
  end
523
476
 
524
477
  it "should remove all items correctly" do
525
478
  @post.remove_all_assets
526
479
  @note.remove_all_assets
527
- sqls_should_be(/UPDATE assets SET attachable_(id|type) = NULL, attachable_(type|id) = NULL WHERE \(\(attachable_(id|type) = (1|'Post')\) AND \(attachable_(type|id) = ('Post'|1)\)\)/,
528
- /UPDATE assets SET attachable_(id|type) = NULL, attachable_(type|id) = NULL WHERE \(\(attachable_(id|type) = (2|'Note')\) AND \(attachable_(type|id) = ('Note'|2)\)\)/)
529
480
  @asset1.reload.attachable.should == nil
530
481
  @asset2.reload.attachable.should == nil
531
482
  end
@@ -604,77 +555,61 @@ describe "many_to_one/one_to_many not referencing primary key" do
604
555
 
605
556
  it "should load all associated one_to_many objects for a single object" do
606
557
  invs = @client1.invoices
607
- sqls_should_be("SELECT * FROM invoices WHERE (client_name = 'X')")
608
558
  invs.should == [@invoice1, @invoice2]
609
559
  invs[0].client.should == @client1
610
560
  invs[1].client.should == @client1
611
- sqls_should_be
612
561
  end
613
562
 
614
563
  it "should load the associated many_to_one object for a single object" do
615
564
  client = @invoice1.client
616
- sqls_should_be("SELECT * FROM clients WHERE (name = 'X') LIMIT 1")
617
565
  client.should == @client1
618
566
  end
619
567
 
620
568
  it "should eagerly load all associated one_to_many objects for a group of objects" do
621
569
  clients = Client.order(:id).eager(:invoices).all
622
- sqls_should_be("SELECT * FROM clients ORDER BY id", "SELECT * FROM invoices WHERE (client_name IN ('X', 'Y'))")
623
570
  clients.should == [@client1, @client2]
624
571
  clients[1].invoices.should == []
625
572
  invs = clients[0].invoices.sort_by{|x| x.pk}
626
573
  invs.should == [@invoice1, @invoice2]
627
574
  invs[0].client.should == @client1
628
575
  invs[1].client.should == @client1
629
- sqls_should_be
630
576
  end
631
577
 
632
578
  it "should eagerly load the associated many_to_one object for a group of objects" do
633
579
  invoices = Invoice.order(:id).eager(:client).all
634
- sqls_should_be("SELECT * FROM invoices ORDER BY id", "SELECT * FROM clients WHERE (name IN ('X'))")
635
580
  invoices.should == [@invoice1, @invoice2]
636
581
  invoices[0].client.should == @client1
637
582
  invoices[1].client.should == @client1
638
- sqls_should_be
639
583
  end
640
584
 
641
585
  it "should set the associated object correctly" do
642
586
  @invoice1.client = @client2
643
- sqls_should_be("SELECT * FROM clients WHERE (name = 'X') LIMIT 1")
644
587
  @invoice1.client.should == @client2
645
588
  @invoice1.client_name.should == 'Y'
646
589
  @invoice1.client = nil
647
590
  @invoice1.client_name.should == nil
648
- sqls_should_be
649
591
  end
650
592
 
651
593
  it "should add the associated object correctly" do
652
594
  @client2.invoices.should == []
653
- sqls_should_be("SELECT * FROM invoices WHERE (client_name = 'Y')")
654
595
  @client2.add_invoice(@invoice1)
655
- sqls_should_be(/UPDATE invoices SET (client_name = 'Y'|id = 1), (client_name = 'Y'|id = 1) WHERE \(id = 1\)/)
656
596
  @client2.invoices.should == [@invoice1]
657
597
  @invoice1.client_name.should == 'Y'
658
598
  @invoice1.client = nil
659
599
  @invoice1.client_name.should == nil
660
- sqls_should_be
661
600
  end
662
601
 
663
602
  it "should remove the associated object correctly" do
664
603
  invs = @client1.invoices.sort_by{|x| x.pk}
665
- sqls_should_be("SELECT * FROM invoices WHERE (client_name = 'X')")
666
604
  invs.should == [@invoice1, @invoice2]
667
605
  @client1.remove_invoice(@invoice1)
668
- sqls_should_be(/UPDATE invoices SET (client_name = NULL|id = 1), (client_name = NULL|id = 1) WHERE \(id = 1\)/)
669
606
  @client1.invoices.should == [@invoice2]
670
607
  @invoice1.client_name.should == nil
671
608
  @invoice1.client.should == nil
672
- sqls_should_be
673
609
  end
674
610
 
675
611
  it "should remove all associated objects correctly" do
676
612
  invs = @client1.remove_all_invoices
677
- sqls_should_be("UPDATE invoices SET client_name = NULL WHERE (client_name = 'X')")
678
613
  @invoice1.refresh.client.should == nil
679
614
  @invoice1.client_name.should == nil
680
615
  @invoice2.refresh.client.should == nil
@@ -0,0 +1,110 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper.rb')
2
+
3
+ describe "Sequel::Model basic support" do
4
+ before do
5
+ @db = INTEGRATION_DB
6
+ @db.create_table!(:items) do
7
+ primary_key :id
8
+ String :name
9
+ end
10
+ class ::Item < Sequel::Model(@db)
11
+ end
12
+ end
13
+ after do
14
+ @db.drop_table(:items)
15
+ Object.send(:remove_const, :Item)
16
+ end
17
+
18
+ specify ".find should return first matching item" do
19
+ Item.all.should == []
20
+ Item.find(:name=>'J').should == nil
21
+ Item.create(:name=>'J')
22
+ Item.find(:name=>'J').should == Item.load(:id=>1, :name=>'J')
23
+ end
24
+
25
+ specify ".find_or_create should return first matching item, or create it if it doesn't exist" do
26
+ Item.all.should == []
27
+ Item.find_or_create(:name=>'J').should == Item.load(:id=>1, :name=>'J')
28
+ Item.all.should == [Item.load(:id=>1, :name=>'J')]
29
+ Item.find_or_create(:name=>'J').should == Item.load(:id=>1, :name=>'J')
30
+ Item.all.should == [Item.load(:id=>1, :name=>'J')]
31
+ end
32
+
33
+ specify "should not raise an error if the implied database table doesn't exist " do
34
+ class ::Item::Thing < Sequel::Model(@db)
35
+ set_dataset :items
36
+ end
37
+ Item.create(:name=>'J')
38
+ Item::Thing.first.should == Item::Thing.load(:id=>1, :name=>'J')
39
+ end
40
+
41
+ specify "should work correctly when a dataset restricts the colums it selects" do
42
+ class ::Item::Thing < Sequel::Model(@db[:items].select(:name))
43
+ end
44
+ Item.create(:name=>'J')
45
+ Item::Thing.first.should == Item::Thing.load(:name=>'J')
46
+ end
47
+
48
+ specify "#delete should delete items correctly" do
49
+ i = Item.create(:name=>'J')
50
+ Item.count.should == 1
51
+ i.delete
52
+ Item.count.should == 0
53
+ end
54
+
55
+ specify "#exists? should return whether the item is still in the database" do
56
+ i = Item.create(:name=>'J')
57
+ i.exists?.should == true
58
+ Item.delete
59
+ i.exists?.should == false
60
+ end
61
+
62
+ specify "#save should only update specified columns when saving" do
63
+ @db.create_table!(:items) do
64
+ primary_key :id
65
+ String :name
66
+ Integer :num
67
+ end
68
+ Item.dataset = Item.dataset
69
+ i = Item.create(:name=>'J', :num=>1)
70
+ Item.all.should == [Item.load(:id=>1, :name=>'J', :num=>1)]
71
+ i.set(:name=>'K', :num=>2)
72
+ i.save(:name)
73
+ Item.all.should == [Item.load(:id=>1, :name=>'K', :num=>1)]
74
+ i.set(:name=>'L')
75
+ i.save(:num)
76
+ Item.all.should == [Item.load(:id=>1, :name=>'K', :num=>2)]
77
+ end
78
+
79
+ specify ".to_hash should return a hash keyed on primary key if no argument provided" do
80
+ i = Item.create(:name=>'J')
81
+ Item.to_hash.should == {1=>Item.load(:id=>1, :name=>'J')}
82
+ end
83
+
84
+ specify ".to_hash should return a hash keyed on argument if one argument provided" do
85
+ i = Item.create(:name=>'J')
86
+ Item.to_hash(:name).should == {'J'=>Item.load(:id=>1, :name=>'J')}
87
+ end
88
+
89
+ specify "should be marshallable before and after saving if marshallable! is called" do
90
+ i = Item.new(:name=>'J')
91
+ s = nil
92
+ i2 = nil
93
+ i.marshallable!
94
+ proc{s = Marshal.dump(i)}.should_not raise_error
95
+ proc{i2 = Marshal.load(s)}.should_not raise_error
96
+ i2.should == i
97
+
98
+ i.save
99
+ i.marshallable!
100
+ proc{s = Marshal.dump(i)}.should_not raise_error
101
+ proc{i2 = Marshal.load(s)}.should_not raise_error
102
+ i2.should == i
103
+
104
+ i.save
105
+ i.marshallable!
106
+ proc{s = Marshal.dump(i)}.should_not raise_error
107
+ proc{i2 = Marshal.load(s)}.should_not raise_error
108
+ i2.should == i
109
+ end
110
+ end
@@ -137,3 +137,309 @@ describe "Class Table Inheritance Plugin" do
137
137
  end
138
138
  end
139
139
  end
140
+
141
+ describe "Many Through Many Plugin" do
142
+ before do
143
+ @db = INTEGRATION_DB
144
+ @db.instance_variable_set(:@schemas, {})
145
+ @db.create_table!(:albums) do
146
+ primary_key :id
147
+ String :name
148
+ end
149
+ @db.create_table!(:artists) do
150
+ primary_key :id
151
+ String :name
152
+ end
153
+ @db.create_table!(:albums_artists) do
154
+ foreign_key :album_id, :albums
155
+ foreign_key :artist_id, :artists
156
+ end
157
+ class ::Album < Sequel::Model(@db)
158
+ many_to_many :artists
159
+ end
160
+ class ::Artist < Sequel::Model(@db)
161
+ plugin :many_through_many
162
+ end
163
+
164
+ @artist1 = Artist.create(:name=>'1')
165
+ @artist2 = Artist.create(:name=>'2')
166
+ @artist3 = Artist.create(:name=>'3')
167
+ @artist4 = Artist.create(:name=>'4')
168
+ @album1 = Album.create(:name=>'A')
169
+ @album1.add_artist(@artist1)
170
+ @album1.add_artist(@artist2)
171
+ @album2 = Album.create(:name=>'B')
172
+ @album2.add_artist(@artist3)
173
+ @album2.add_artist(@artist4)
174
+ @album3 = Album.create(:name=>'C')
175
+ @album3.add_artist(@artist2)
176
+ @album3.add_artist(@artist3)
177
+ @album4 = Album.create(:name=>'D')
178
+ @album4.add_artist(@artist1)
179
+ @album4.add_artist(@artist4)
180
+
181
+ clear_sqls
182
+ end
183
+ after do
184
+ @db.drop_table :albums_artists, :albums, :artists
185
+ [:Album, :Artist].each{|s| Object.send(:remove_const, s)}
186
+ end
187
+
188
+ specify "should handle super simple case with 1 join table" do
189
+ Artist.many_through_many :albums, [[:albums_artists, :artist_id, :album_id]]
190
+ Artist[1].albums.map{|x| x.name}.sort.should == %w'A D'
191
+ Artist[2].albums.map{|x| x.name}.sort.should == %w'A C'
192
+ Artist[3].albums.map{|x| x.name}.sort.should == %w'B C'
193
+ Artist[4].albums.map{|x| x.name}.sort.should == %w'B D'
194
+
195
+ Artist.filter(:id=>1).eager(:albums).all.map{|x| x.albums.map{|a| a.name}}.flatten.sort.should == %w'A D'
196
+ Artist.filter(:id=>2).eager(:albums).all.map{|x| x.albums.map{|a| a.name}}.flatten.sort.should == %w'A C'
197
+ Artist.filter(:id=>3).eager(:albums).all.map{|x| x.albums.map{|a| a.name}}.flatten.sort.should == %w'B C'
198
+ Artist.filter(:id=>4).eager(:albums).all.map{|x| x.albums.map{|a| a.name}}.flatten.sort.should == %w'B D'
199
+
200
+ Artist.filter(:artists__id=>1).eager_graph(:albums).all.map{|x| x.albums.map{|a| a.name}}.flatten.sort.should == %w'A D'
201
+ Artist.filter(:artists__id=>2).eager_graph(:albums).all.map{|x| x.albums.map{|a| a.name}}.flatten.sort.should == %w'A C'
202
+ Artist.filter(:artists__id=>3).eager_graph(:albums).all.map{|x| x.albums.map{|a| a.name}}.flatten.sort.should == %w'B C'
203
+ Artist.filter(:artists__id=>4).eager_graph(:albums).all.map{|x| x.albums.map{|a| a.name}}.flatten.sort.should == %w'B D'
204
+ end
205
+
206
+ specify "should handle typical case with 3 join tables" do
207
+ Artist.many_through_many :related_artists, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_artists, :album_id, :artist_id]], :class=>Artist, :distinct=>true
208
+ Artist[1].related_artists.map{|x| x.name}.sort.should == %w'1 2 4'
209
+ Artist[2].related_artists.map{|x| x.name}.sort.should == %w'1 2 3'
210
+ Artist[3].related_artists.map{|x| x.name}.sort.should == %w'2 3 4'
211
+ Artist[4].related_artists.map{|x| x.name}.sort.should == %w'1 3 4'
212
+
213
+ Artist.filter(:id=>1).eager(:related_artists).all.map{|x| x.related_artists.map{|a| a.name}}.flatten.sort.should == %w'1 2 4'
214
+ Artist.filter(:id=>2).eager(:related_artists).all.map{|x| x.related_artists.map{|a| a.name}}.flatten.sort.should == %w'1 2 3'
215
+ Artist.filter(:id=>3).eager(:related_artists).all.map{|x| x.related_artists.map{|a| a.name}}.flatten.sort.should == %w'2 3 4'
216
+ Artist.filter(:id=>4).eager(:related_artists).all.map{|x| x.related_artists.map{|a| a.name}}.flatten.sort.should == %w'1 3 4'
217
+
218
+ Artist.filter(:artists__id=>1).eager_graph(:related_artists).all.map{|x| x.related_artists.map{|a| a.name}}.flatten.sort.should == %w'1 2 4'
219
+ Artist.filter(:artists__id=>2).eager_graph(:related_artists).all.map{|x| x.related_artists.map{|a| a.name}}.flatten.sort.should == %w'1 2 3'
220
+ Artist.filter(:artists__id=>3).eager_graph(:related_artists).all.map{|x| x.related_artists.map{|a| a.name}}.flatten.sort.should == %w'2 3 4'
221
+ Artist.filter(:artists__id=>4).eager_graph(:related_artists).all.map{|x| x.related_artists.map{|a| a.name}}.flatten.sort.should == %w'1 3 4'
222
+ end
223
+
224
+ specify "should handle extreme case with 5 join tables" do
225
+ Artist.many_through_many :related_albums, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_artists, :album_id, :artist_id], [:artists, :id, :id], [:albums_artists, :artist_id, :album_id]], :class=>Album, :distinct=>true
226
+ @db[:albums_artists].delete
227
+ @album1.add_artist(@artist1)
228
+ @album1.add_artist(@artist2)
229
+ @album2.add_artist(@artist2)
230
+ @album2.add_artist(@artist3)
231
+ @album3.add_artist(@artist1)
232
+ @album4.add_artist(@artist3)
233
+ @album4.add_artist(@artist4)
234
+
235
+ Artist[1].related_albums.map{|x| x.name}.sort.should == %w'A B C'
236
+ Artist[2].related_albums.map{|x| x.name}.sort.should == %w'A B C D'
237
+ Artist[3].related_albums.map{|x| x.name}.sort.should == %w'A B D'
238
+ Artist[4].related_albums.map{|x| x.name}.sort.should == %w'B D'
239
+
240
+ Artist.filter(:id=>1).eager(:related_albums).all.map{|x| x.related_albums.map{|a| a.name}}.flatten.sort.should == %w'A B C'
241
+ Artist.filter(:id=>2).eager(:related_albums).all.map{|x| x.related_albums.map{|a| a.name}}.flatten.sort.should == %w'A B C D'
242
+ Artist.filter(:id=>3).eager(:related_albums).all.map{|x| x.related_albums.map{|a| a.name}}.flatten.sort.should == %w'A B D'
243
+ Artist.filter(:id=>4).eager(:related_albums).all.map{|x| x.related_albums.map{|a| a.name}}.flatten.sort.should == %w'B D'
244
+
245
+ Artist.filter(:artists__id=>1).eager_graph(:related_albums).all.map{|x| x.related_albums.map{|a| a.name}}.flatten.sort.should == %w'A B C'
246
+ Artist.filter(:artists__id=>2).eager_graph(:related_albums).all.map{|x| x.related_albums.map{|a| a.name}}.flatten.sort.should == %w'A B C D'
247
+ Artist.filter(:artists__id=>3).eager_graph(:related_albums).all.map{|x| x.related_albums.map{|a| a.name}}.flatten.sort.should == %w'A B D'
248
+ Artist.filter(:artists__id=>4).eager_graph(:related_albums).all.map{|x| x.related_albums.map{|a| a.name}}.flatten.sort.should == %w'B D'
249
+ end
250
+ end
251
+
252
+ describe "Lazy Attributes plugin" do
253
+ before do
254
+ @db = INTEGRATION_DB
255
+ @db.create_table!(:items) do
256
+ primary_key :id
257
+ String :name
258
+ Integer :num
259
+ end
260
+ class ::Item < Sequel::Model(@db)
261
+ plugin :lazy_attributes, :num
262
+ end
263
+ Item.create(:name=>'J', :num=>1)
264
+ end
265
+ after do
266
+ @db.drop_table(:items)
267
+ Object.send(:remove_const, :Item)
268
+ end
269
+
270
+ specify "should not include lazy attribute columns by default" do
271
+ Item.first.should == Item.load(:id=>1, :name=>'J')
272
+ end
273
+
274
+ specify "should load lazy attribute on access" do
275
+ Item.first.num.should == 1
276
+ end
277
+
278
+ specify "should load lazy attribute for all items returned when accessing any item if using identity map " do
279
+ Item.create(:name=>'K', :num=>2)
280
+ Item.with_identity_map do
281
+ a = Item.order(:name).all
282
+ a.should == [Item.load(:id=>1, :name=>'J'), Item.load(:id=>2, :name=>'K')]
283
+ a.map{|x| x[:num]}.should == [nil, nil]
284
+ a.first.num.should == 1
285
+ a.map{|x| x[:num]}.should == [1, 2]
286
+ a.last.num.should == 2
287
+ end
288
+ end
289
+ end
290
+
291
+ describe "Tactical Eager Loading Plugin" do
292
+ before do
293
+ @db = INTEGRATION_DB
294
+ @db.instance_variable_set(:@schemas, {})
295
+ @db.create_table!(:artists) do
296
+ primary_key :id
297
+ String :name
298
+ end
299
+ @db.create_table!(:albums) do
300
+ primary_key :id
301
+ String :name
302
+ foreign_key :artist_id, :artists
303
+ end
304
+ class ::Album < Sequel::Model(@db)
305
+ plugin :tactical_eager_loading
306
+ many_to_one :artist
307
+ end
308
+ class ::Artist < Sequel::Model(@db)
309
+ plugin :tactical_eager_loading
310
+ one_to_many :albums, :order=>:name
311
+ end
312
+
313
+ @artist1 = Artist.create(:name=>'1')
314
+ @artist2 = Artist.create(:name=>'2')
315
+ @artist3 = Artist.create(:name=>'3')
316
+ @artist4 = Artist.create(:name=>'4')
317
+ @album1 = Album.create(:name=>'A', :artist=>@artist1)
318
+ @album2 = Album.create(:name=>'B', :artist=>@artist1)
319
+ @album3 = Album.create(:name=>'C', :artist=>@artist2)
320
+ @album4 = Album.create(:name=>'D', :artist=>@artist3)
321
+
322
+ clear_sqls
323
+ end
324
+ after do
325
+ @db.drop_table :albums, :artists
326
+ [:Album, :Artist].each{|s| Object.send(:remove_const, s)}
327
+ end
328
+
329
+ specify "should eagerly load associations for all items when accessing any item" do
330
+ a = Artist.order(:name).all
331
+ a.map{|x| x.associations}.should == [{}, {}, {}, {}]
332
+ a.first.albums.should == [@album1, @album2]
333
+ a.map{|x| x.associations}.should == [{:albums=>[@album1, @album2]}, {:albums=>[@album3]}, {:albums=>[@album4]}, {:albums=>[]}]
334
+
335
+ a = Album.order(:name).all
336
+ a.map{|x| x.associations}.should == [{}, {}, {}, {}]
337
+ a.first.artist.should == @artist1
338
+ a.map{|x| x.associations}.should == [{:artist=>@artist1}, {:artist=>@artist1}, {:artist=>@artist2}, {:artist=>@artist3}]
339
+ end
340
+ end
341
+
342
+ describe "Identity Map plugin" do
343
+ before do
344
+ @db = INTEGRATION_DB
345
+ @db.create_table!(:items) do
346
+ primary_key :id
347
+ String :name
348
+ Integer :num
349
+ end
350
+ class ::Item < Sequel::Model(@db)
351
+ plugin :identity_map
352
+ end
353
+ Item.create(:name=>'J', :num=>3)
354
+ end
355
+ after do
356
+ @db.drop_table(:items)
357
+ Object.send(:remove_const, :Item)
358
+ end
359
+
360
+ specify "should return the same instance if retrieved more than once" do
361
+ Item.with_identity_map{Item.first.object_id.should == Item.first.object_id}
362
+ end
363
+
364
+ specify "should merge attributes that don't exist in the model" do
365
+ Item.with_identity_map do
366
+ i = Item.select(:id, :name).first
367
+ i.values.should == {:id=>1, :name=>'J'}
368
+ Item.first
369
+ i.values.should == {:id=>1, :name=>'J', :num=>3}
370
+ end
371
+ end
372
+ end
373
+
374
+ describe "Touch plugin" do
375
+ before do
376
+ @db = INTEGRATION_DB
377
+ @db.instance_variable_set(:@schemas, {})
378
+ @db.create_table!(:artists) do
379
+ primary_key :id
380
+ String :name
381
+ DateTime :updated_at
382
+ end
383
+ @db.create_table!(:albums) do
384
+ primary_key :id
385
+ String :name
386
+ foreign_key :artist_id, :artists
387
+ DateTime :updated_at
388
+ end
389
+ class ::Album < Sequel::Model(@db)
390
+ many_to_one :artist
391
+ plugin :touch, :associations=>:artist
392
+ end
393
+ class ::Artist < Sequel::Model(@db)
394
+ end
395
+
396
+ @artist = Artist.create(:name=>'1')
397
+ @album = Album.create(:name=>'A', :artist=>@artist)
398
+ end
399
+ after do
400
+ @db.drop_table :albums, :artists
401
+ [:Album, :Artist].each{|s| Object.send(:remove_const, s)}
402
+ end
403
+
404
+ specify "should update the timestamp column when touching the record" do
405
+ @album.updated_at.should == nil
406
+ @album.touch
407
+ @album.updated_at.to_i.should be_close(Time.now.to_i, 2)
408
+ end
409
+
410
+ cspecify "should update the timestamp column for associated records when the record is updated or destroyed", [:do], [:jdbc, :sqlite] do
411
+ @artist.updated_at.should == nil
412
+ @album.update(:name=>'B')
413
+ @artist.reload.updated_at.to_i.should be_close(Time.now.to_i, 2)
414
+ @artist.update(:updated_at=>nil)
415
+ @album.destroy
416
+ @artist.reload.updated_at.to_i.should be_close(Time.now.to_i, 2)
417
+ end
418
+ end
419
+
420
+ describe "Serialization plugin" do
421
+ before do
422
+ @db = INTEGRATION_DB
423
+ @db.create_table!(:items) do
424
+ primary_key :id
425
+ String :stuff
426
+ end
427
+ class ::Item < Sequel::Model(@db)
428
+ plugin :serialization, :marshal, :stuff
429
+ end
430
+ end
431
+ after do
432
+ @db.drop_table(:items)
433
+ Object.send(:remove_const, :Item)
434
+ end
435
+
436
+ specify "should serialize and deserialize items as needed" do
437
+ i = Item.create(:stuff=>{:a=>1})
438
+ i.stuff.should == {:a=>1}
439
+ i.stuff = [1, 2, 3]
440
+ i.save
441
+ Item.first.stuff.should == [1, 2, 3]
442
+ i.update(:stuff=>Item.new)
443
+ Item.first.stuff.should == Item.new
444
+ end
445
+ end