sequel 3.5.0 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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