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