sequel 3.5.0 → 3.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +108 -0
- data/README.rdoc +25 -14
- data/Rakefile +20 -1
- data/doc/advanced_associations.rdoc +61 -64
- data/doc/cheat_sheet.rdoc +16 -7
- data/doc/opening_databases.rdoc +3 -3
- data/doc/prepared_statements.rdoc +1 -1
- data/doc/reflection.rdoc +2 -1
- data/doc/release_notes/3.6.0.txt +366 -0
- data/doc/schema.rdoc +19 -14
- data/lib/sequel/adapters/amalgalite.rb +5 -27
- data/lib/sequel/adapters/jdbc.rb +13 -3
- data/lib/sequel/adapters/jdbc/h2.rb +17 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +20 -7
- data/lib/sequel/adapters/mysql.rb +4 -3
- data/lib/sequel/adapters/oracle.rb +1 -1
- data/lib/sequel/adapters/postgres.rb +87 -28
- data/lib/sequel/adapters/shared/mssql.rb +47 -6
- data/lib/sequel/adapters/shared/mysql.rb +12 -31
- data/lib/sequel/adapters/shared/postgres.rb +15 -12
- data/lib/sequel/adapters/shared/sqlite.rb +18 -0
- data/lib/sequel/adapters/sqlite.rb +1 -16
- data/lib/sequel/connection_pool.rb +1 -1
- data/lib/sequel/core.rb +1 -1
- data/lib/sequel/database.rb +1 -1
- data/lib/sequel/database/schema_generator.rb +2 -0
- data/lib/sequel/database/schema_sql.rb +1 -1
- data/lib/sequel/dataset.rb +5 -179
- data/lib/sequel/dataset/actions.rb +123 -0
- data/lib/sequel/dataset/convenience.rb +18 -10
- data/lib/sequel/dataset/features.rb +65 -0
- data/lib/sequel/dataset/prepared_statements.rb +29 -23
- data/lib/sequel/dataset/query.rb +429 -0
- data/lib/sequel/dataset/sql.rb +67 -435
- data/lib/sequel/model/associations.rb +77 -13
- data/lib/sequel/model/base.rb +30 -8
- data/lib/sequel/model/errors.rb +4 -4
- data/lib/sequel/plugins/caching.rb +38 -15
- data/lib/sequel/plugins/force_encoding.rb +18 -7
- data/lib/sequel/plugins/hook_class_methods.rb +4 -0
- data/lib/sequel/plugins/many_through_many.rb +1 -1
- data/lib/sequel/plugins/nested_attributes.rb +40 -11
- data/lib/sequel/plugins/serialization.rb +17 -3
- data/lib/sequel/plugins/validation_helpers.rb +65 -18
- data/lib/sequel/sql.rb +23 -1
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +96 -10
- data/spec/adapters/mysql_spec.rb +19 -0
- data/spec/adapters/postgres_spec.rb +65 -2
- data/spec/adapters/sqlite_spec.rb +10 -0
- data/spec/core/core_sql_spec.rb +9 -0
- data/spec/core/database_spec.rb +8 -4
- data/spec/core/dataset_spec.rb +122 -29
- data/spec/core/expression_filters_spec.rb +17 -0
- data/spec/extensions/caching_spec.rb +43 -3
- data/spec/extensions/force_encoding_spec.rb +43 -1
- data/spec/extensions/nested_attributes_spec.rb +55 -2
- data/spec/extensions/validation_helpers_spec.rb +71 -0
- data/spec/integration/associations_test.rb +281 -0
- data/spec/integration/dataset_test.rb +383 -9
- data/spec/integration/eager_loader_test.rb +0 -65
- data/spec/integration/model_test.rb +110 -0
- data/spec/integration/plugin_test.rb +306 -0
- data/spec/integration/prepared_statement_test.rb +32 -0
- data/spec/integration/schema_test.rb +8 -3
- data/spec/integration/spec_helper.rb +1 -25
- data/spec/model/association_reflection_spec.rb +38 -0
- data/spec/model/associations_spec.rb +184 -8
- data/spec/model/eager_loading_spec.rb +23 -0
- data/spec/model/model_spec.rb +8 -0
- data/spec/model/record_spec.rb +84 -1
- 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
|