sequel 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1449 @@
1
+ require File.join(File.dirname(__FILE__), '../lib/sequel')
2
+
3
+ context "Dataset" do
4
+ setup do
5
+ @dataset = Sequel::Dataset.new("db")
6
+ end
7
+
8
+ specify "should accept database and opts in initialize" do
9
+ db = 'db'
10
+ opts = {:from => :test}
11
+ d = Sequel::Dataset.new(db, opts)
12
+ d.db.should be(db)
13
+ d.opts.should be(opts)
14
+
15
+ d = Sequel::Dataset.new(db)
16
+ d.db.should be(db)
17
+ d.opts.should be_a_kind_of(Hash)
18
+ d.opts.should == {}
19
+ end
20
+
21
+ specify "should provide clone_merge for chainability." do
22
+ d1 = @dataset.clone_merge(:from => :test)
23
+ d1.class.should == @dataset.class
24
+ d1.should_not == @dataset
25
+ d1.db.should be(@dataset.db)
26
+ d1.opts[:from].should == :test
27
+ @dataset.opts[:from].should be_nil
28
+
29
+ d2 = d1.clone_merge(:order => :name)
30
+ d2.class.should == @dataset.class
31
+ d2.should_not == d1
32
+ d2.should_not == @dataset
33
+ d2.db.should be(@dataset.db)
34
+ d2.opts[:from].should == :test
35
+ d2.opts[:order].should == :name
36
+ d1.opts[:order].should be_nil
37
+ end
38
+
39
+ specify "should include Enumerable" do
40
+ Sequel::Dataset.included_modules.should include(Enumerable)
41
+ end
42
+
43
+ specify "should raise NotImplementedError for the dataset interface methods" do
44
+ proc {@dataset.fetch_rows('abc')}.should raise_error(NotImplementedError)
45
+ proc {@dataset.insert(1, 2, 3)}.should raise_error(NotImplementedError)
46
+ proc {@dataset.update(:name => 'abc')}.should raise_error(NotImplementedError)
47
+ proc {@dataset.delete}.should raise_error(NotImplementedError)
48
+ end
49
+ end
50
+
51
+ context "Dataset#clone_merge" do
52
+ setup do
53
+ @dataset = Sequel::Dataset.new(nil).from(:items)
54
+ end
55
+
56
+ specify "should return a clone self" do
57
+ clone = @dataset.clone_merge({})
58
+ clone.class.should == @dataset.class
59
+ clone.db.should == @dataset.db
60
+ clone.opts.should == @dataset.opts
61
+ end
62
+
63
+ specify "should merge the specified options" do
64
+ clone = @dataset.clone_merge(1 => 2)
65
+ clone.opts.should == {1 => 2, :from => [:items]}
66
+ end
67
+
68
+ specify "should overwrite existing options" do
69
+ clone = @dataset.clone_merge(:from => [:other])
70
+ clone.opts.should == {:from => [:other]}
71
+ end
72
+
73
+ specify "should create a clone with a deep copy of options" do
74
+ clone = @dataset.clone_merge(:from => [:other])
75
+ @dataset.opts[:from].should == [:items]
76
+ clone.opts[:from].should == [:other]
77
+ end
78
+
79
+ specify "should return an object with the same modules included" do
80
+ m = Module.new do
81
+ def __xyz__; "xyz"; end
82
+ end
83
+ @dataset.extend(m)
84
+ @dataset.clone_merge({}).should respond_to(:__xyz__)
85
+ end
86
+ end
87
+
88
+ context "A simple dataset" do
89
+ setup do
90
+ @dataset = Sequel::Dataset.new(nil).from(:test)
91
+ end
92
+
93
+ specify "should format a select statement" do
94
+ @dataset.select_sql.should == 'SELECT * FROM test'
95
+ end
96
+
97
+ specify "should format a delete statement" do
98
+ @dataset.delete_sql.should == 'DELETE FROM test'
99
+ end
100
+
101
+ specify "should format an insert statement" do
102
+ @dataset.insert_sql.should == 'INSERT INTO test DEFAULT VALUES'
103
+ @dataset.insert_sql(:name => 'wxyz', :price => 342).
104
+ should match(/INSERT INTO test \(name, price\) VALUES \('wxyz', 342\)|INSERT INTO test \(price, name\) VALUES \(342, 'wxyz'\)/)
105
+ @dataset.insert_sql('a', 2, 6.5).should ==
106
+ "INSERT INTO test VALUES ('a', 2, 6.5)"
107
+ end
108
+
109
+ specify "should format an update statement" do
110
+ @dataset.update_sql(:name => 'abc').should ==
111
+ "UPDATE test SET name = 'abc'"
112
+ end
113
+ end
114
+
115
+ context "A dataset with multiple tables in its FROM clause" do
116
+ setup do
117
+ @dataset = Sequel::Dataset.new(nil).from(:t1, :t2)
118
+ end
119
+
120
+ specify "should raise on #update_sql" do
121
+ proc {@dataset.update_sql(:a=>1)}.should raise_error
122
+ end
123
+
124
+ specify "should raise on #delete_sql" do
125
+ proc {@dataset.delete_sql}.should raise_error
126
+ end
127
+
128
+ specify "should generate a select query FROM all specified tables" do
129
+ @dataset.select_sql.should == "SELECT * FROM t1, t2"
130
+ end
131
+ end
132
+
133
+ context "Dataset#where" do
134
+ setup do
135
+ @dataset = Sequel::Dataset.new(nil).from(:test)
136
+ @d1 = @dataset.where(:region => 'Asia')
137
+ @d2 = @dataset.where('(region = ?)', 'Asia')
138
+ @d3 = @dataset.where("(a = 1)")
139
+ end
140
+
141
+ specify "should work with hashes" do
142
+ @dataset.where(:name => 'xyz', :price => 342).select_sql.
143
+ should match(/WHERE \(name = 'xyz'\) AND \(price = 342\)|WHERE \(price = 342\) AND \(name = 'xyz'\)/)
144
+ end
145
+
146
+ specify "should work with arrays (ala ActiveRecord)" do
147
+ @dataset.where('price < ? AND id in (?)', 100, [1, 2, 3]).select_sql.should ==
148
+ "SELECT * FROM test WHERE price < 100 AND id in (1, 2, 3)"
149
+ end
150
+
151
+ specify "should work with strings (custom SQL expressions)" do
152
+ @dataset.where('(a = 1 AND b = 2)').select_sql.should ==
153
+ "SELECT * FROM test WHERE (a = 1 AND b = 2)"
154
+ end
155
+
156
+ specify "should affect select, delete and update statements" do
157
+ @d1.select_sql.should == "SELECT * FROM test WHERE (region = 'Asia')"
158
+ @d1.delete_sql.should == "DELETE FROM test WHERE (region = 'Asia')"
159
+ @d1.update_sql(:GDP => 0).should == "UPDATE test SET GDP = 0 WHERE (region = 'Asia')"
160
+
161
+ @d2.select_sql.should == "SELECT * FROM test WHERE (region = 'Asia')"
162
+ @d2.delete_sql.should == "DELETE FROM test WHERE (region = 'Asia')"
163
+ @d2.update_sql(:GDP => 0).should == "UPDATE test SET GDP = 0 WHERE (region = 'Asia')"
164
+
165
+ @d3.select_sql.should == "SELECT * FROM test WHERE (a = 1)"
166
+ @d3.delete_sql.should == "DELETE FROM test WHERE (a = 1)"
167
+ @d3.update_sql(:GDP => 0).should == "UPDATE test SET GDP = 0 WHERE (a = 1)"
168
+
169
+ end
170
+
171
+ specify "should be composable using AND operator (for scoping)" do
172
+ # hashes are merged, no problem
173
+ @d1.where(:size => 'big').select_sql.should ==
174
+ "SELECT * FROM test WHERE (region = 'Asia') AND (size = 'big')"
175
+
176
+ # hash and string
177
+ @d1.where('population > 1000').select_sql.should ==
178
+ "SELECT * FROM test WHERE (region = 'Asia') AND (population > 1000)"
179
+ @d1.where('(a > 1) OR (b < 2)').select_sql.should ==
180
+ "SELECT * FROM test WHERE (region = 'Asia') AND ((a > 1) OR (b < 2))"
181
+
182
+ # hash and array
183
+ @d1.where('(GDP > ?)', 1000).select_sql.should ==
184
+ "SELECT * FROM test WHERE (region = 'Asia') AND (GDP > 1000)"
185
+
186
+ # array and array
187
+ @d2.where('(GDP > ?)', 1000).select_sql.should ==
188
+ "SELECT * FROM test WHERE (region = 'Asia') AND (GDP > 1000)"
189
+
190
+ # array and hash
191
+ @d2.where(:name => ['Japan', 'China']).select_sql.should ==
192
+ "SELECT * FROM test WHERE (region = 'Asia') AND (name IN ('Japan', 'China'))"
193
+
194
+ # array and string
195
+ @d2.where('GDP > ?').select_sql.should ==
196
+ "SELECT * FROM test WHERE (region = 'Asia') AND (GDP > ?)"
197
+
198
+ # string and string
199
+ @d3.where('b = 2').select_sql.should ==
200
+ "SELECT * FROM test WHERE (a = 1) AND (b = 2)"
201
+
202
+ # string and hash
203
+ @d3.where(:c => 3).select_sql.should ==
204
+ "SELECT * FROM test WHERE (a = 1) AND (c = 3)"
205
+
206
+ # string and array
207
+ @d3.where('(d = ?)', 4).select_sql.should ==
208
+ "SELECT * FROM test WHERE (a = 1) AND (d = 4)"
209
+
210
+ # string and proc expr
211
+ @d3.where {e < 5}.select_sql.should ==
212
+ "SELECT * FROM test WHERE (a = 1) AND (e < 5)"
213
+ end
214
+
215
+ specify "should raise if the dataset is grouped" do
216
+ proc {@dataset.group(:t).where(:a => 1)}.should raise_error
217
+ end
218
+
219
+ specify "should accept ranges" do
220
+ @dataset.filter(:id => 4..7).sql.should ==
221
+ 'SELECT * FROM test WHERE (id >= 4 AND id <= 7)'
222
+ @dataset.filter(:id => 4...7).sql.should ==
223
+ 'SELECT * FROM test WHERE (id >= 4 AND id < 7)'
224
+
225
+ @dataset.filter {id == (4..7)}.sql.should ==
226
+ 'SELECT * FROM test WHERE (id >= 4 AND id <= 7)'
227
+
228
+ @dataset.filter {id.in 4..7}.sql.should ==
229
+ 'SELECT * FROM test WHERE (id >= 4 AND id <= 7)'
230
+ end
231
+
232
+ specify "should accept nil" do
233
+ @dataset.filter(:owner_id => nil).sql.should ==
234
+ 'SELECT * FROM test WHERE (owner_id IS NULL)'
235
+
236
+ @dataset.filter{owner_id.nil?}.sql.should ==
237
+ 'SELECT * FROM test WHERE (owner_id IS NULL)'
238
+ end
239
+
240
+ specify "should accept a subquery" do
241
+ # select all countries that have GDP greater than the average for Asia
242
+ @dataset.filter('gdp > ?', @d1.select(:gdp.AVG)).sql.should ==
243
+ "SELECT * FROM test WHERE gdp > (SELECT avg(gdp) FROM test WHERE (region = 'Asia'))"
244
+
245
+ @dataset.filter(:id => @d1.select(:id)).sql.should ==
246
+ "SELECT * FROM test WHERE (id IN (SELECT id FROM test WHERE (region = 'Asia')))"
247
+ end
248
+
249
+ specify "should accept a subquery for an EXISTS clause" do
250
+ a = @dataset.filter {price < 100}
251
+ @dataset.filter(a.exists).sql.should ==
252
+ 'SELECT * FROM test WHERE EXISTS (SELECT 1 FROM test WHERE (price < 100))'
253
+ end
254
+
255
+ specify "should accept proc expressions (nice!)" do
256
+ d = @d1.select(:gdp.AVG)
257
+ @dataset.filter {gdp > d}.sql.should ==
258
+ "SELECT * FROM test WHERE (gdp > (SELECT avg(gdp) FROM test WHERE (region = 'Asia')))"
259
+
260
+ @dataset.filter {id.in 4..7}.sql.should ==
261
+ 'SELECT * FROM test WHERE (id >= 4 AND id <= 7)'
262
+
263
+ @dataset.filter {c == 3}.sql.should ==
264
+ 'SELECT * FROM test WHERE (c = 3)'
265
+
266
+ @dataset.filter {id == :items__id}.sql.should ==
267
+ 'SELECT * FROM test WHERE (id = items.id)'
268
+
269
+ @dataset.filter {a < 1}.sql.should ==
270
+ 'SELECT * FROM test WHERE (a < 1)'
271
+
272
+ @dataset.filter {a <=> 1}.sql.should ==
273
+ 'SELECT * FROM test WHERE NOT (a = 1)'
274
+
275
+ @dataset.filter {a >= 1 && b <= 2}.sql.should ==
276
+ 'SELECT * FROM test WHERE (a >= 1) AND (b <= 2)'
277
+
278
+ @dataset.filter {c =~ 'ABC%'}.sql.should ==
279
+ "SELECT * FROM test WHERE (c LIKE 'ABC%')"
280
+
281
+ @dataset.filter {test.ccc =~ 'ABC%'}.sql.should ==
282
+ "SELECT * FROM test WHERE (test.ccc LIKE 'ABC%')"
283
+ end
284
+
285
+ specify "should raise SequelError for invalid proc expressions" do
286
+ proc {@dataset.filter {Object.czxczxcz}}.should raise_error(SequelError)
287
+ proc {@dataset.filter {a.bcvxv}}.should raise_error(SequelError)
288
+ proc {@dataset.filter {x}}.should raise_error(SequelError)
289
+ end
290
+ end
291
+
292
+ context "Dataset#or" do
293
+ setup do
294
+ @dataset = Sequel::Dataset.new(nil).from(:test)
295
+ @d1 = @dataset.where(:x => 1)
296
+ end
297
+
298
+ specify "should raise if no filter exists" do
299
+ proc {@dataset.or(:a => 1)}.should raise_error(SequelError)
300
+ end
301
+
302
+ specify "should add an alternative expression to the where clause" do
303
+ @d1.or(:y => 2).sql.should ==
304
+ 'SELECT * FROM test WHERE (x = 1) OR (y = 2)'
305
+ end
306
+
307
+ specify "should accept all forms of filters" do
308
+ # probably not exhaustive, but good enough
309
+ @d1.or('(y > ?)', 2).sql.should ==
310
+ 'SELECT * FROM test WHERE (x = 1) OR (y > 2)'
311
+
312
+ (@d1.or {yy > 3}).sql.should ==
313
+ 'SELECT * FROM test WHERE (x = 1) OR (yy > 3)'
314
+ end
315
+
316
+ specify "should correctly add parens to give predictable results" do
317
+ @d1.filter(:y => 2).or(:z => 3).sql.should ==
318
+ 'SELECT * FROM test WHERE ((x = 1) AND (y = 2)) OR (z = 3)'
319
+
320
+ @d1.or(:y => 2).filter(:z => 3).sql.should ==
321
+ 'SELECT * FROM test WHERE ((x = 1) OR (y = 2)) AND (z = 3)'
322
+ end
323
+ end
324
+
325
+ context "Dataset#and" do
326
+ setup do
327
+ @dataset = Sequel::Dataset.new(nil).from(:test)
328
+ @d1 = @dataset.where(:x => 1)
329
+ end
330
+
331
+ specify "should raise if no filter exists" do
332
+ proc {@dataset.and(:a => 1)}.should raise_error(SequelError)
333
+ end
334
+
335
+ specify "should add an alternative expression to the where clause" do
336
+ @d1.and(:y => 2).sql.should ==
337
+ 'SELECT * FROM test WHERE (x = 1) AND (y = 2)'
338
+ end
339
+
340
+ specify "should accept all forms of filters" do
341
+ # probably not exhaustive, but good enough
342
+ @d1.and('(y > ?)', 2).sql.should ==
343
+ 'SELECT * FROM test WHERE (x = 1) AND (y > 2)'
344
+
345
+ (@d1.and {yy > 3}).sql.should ==
346
+ 'SELECT * FROM test WHERE (x = 1) AND (yy > 3)'
347
+ end
348
+
349
+ specify "should correctly add parens to give predictable results" do
350
+ @d1.or(:y => 2).and(:z => 3).sql.should ==
351
+ 'SELECT * FROM test WHERE ((x = 1) OR (y = 2)) AND (z = 3)'
352
+
353
+ @d1.and(:y => 2).or(:z => 3).sql.should ==
354
+ 'SELECT * FROM test WHERE ((x = 1) AND (y = 2)) OR (z = 3)'
355
+ end
356
+ end
357
+
358
+ context "Dataset#exclude" do
359
+ setup do
360
+ @dataset = Sequel::Dataset.new(nil).from(:test)
361
+ end
362
+
363
+ specify "should correctly include the NOT operator when one condition is given" do
364
+ @dataset.exclude(:region=>'Asia').select_sql.should ==
365
+ "SELECT * FROM test WHERE NOT (region = 'Asia')"
366
+ end
367
+
368
+ specify "should take multiple conditions as a hash and express the logic correctly in SQL" do
369
+ @dataset.exclude(:region => 'Asia', :name => 'Japan').select_sql.
370
+ should match(Regexp.union(/WHERE NOT \(\(region = 'Asia'\) AND \(name = 'Japan'\)\)/,
371
+ /WHERE NOT \(\(name = 'Japan'\) AND \(region = 'Asia'\)\)/))
372
+ end
373
+
374
+ specify "should parenthesize a single string condition correctly" do
375
+ @dataset.exclude("region = 'Asia' AND name = 'Japan'").select_sql.should ==
376
+ "SELECT * FROM test WHERE NOT (region = 'Asia' AND name = 'Japan')"
377
+ end
378
+
379
+ specify "should parenthesize an array condition correctly" do
380
+ @dataset.exclude('region = ? AND name = ?', 'Asia', 'Japan').select_sql.should ==
381
+ "SELECT * FROM test WHERE NOT (region = 'Asia' AND name = 'Japan')"
382
+ end
383
+
384
+ specify "should corrently parenthesize when it is used twice" do
385
+ @dataset.exclude(:region => 'Asia').exclude(:name => 'Japan').select_sql.should ==
386
+ "SELECT * FROM test WHERE NOT (region = 'Asia') AND NOT (name = 'Japan')"
387
+ end
388
+
389
+ specify "should support proc expressions" do
390
+ @dataset.exclude {id == (6...12)}.sql.should ==
391
+ 'SELECT * FROM test WHERE NOT ((id >= 6 AND id < 12))'
392
+ end
393
+ end
394
+
395
+ context "Dataset#having" do
396
+ setup do
397
+ @dataset = Sequel::Dataset.new(nil).from(:test)
398
+ @grouped = @dataset.group(:region).select(:region, :population.SUM, :gdp.AVG)
399
+ @d1 = @grouped.having('sum(population) > 10')
400
+ @d2 = @grouped.having(:region => 'Asia')
401
+ @fields = "region, sum(population), avg(gdp)"
402
+ end
403
+
404
+ specify "should raise if the dataset is not grouped" do
405
+ proc {@dataset.having('avg(gdp) > 10')}.should raise_error
406
+ end
407
+
408
+ specify "should affect select statements" do
409
+ @d1.select_sql.should ==
410
+ "SELECT #{@fields} FROM test GROUP BY region HAVING sum(population) > 10"
411
+ end
412
+
413
+ specify "should support proc expressions" do
414
+ @grouped.having {SUM(:population) > 10}.sql.should ==
415
+ "SELECT #{@fields} FROM test GROUP BY region HAVING (sum(population) > 10)"
416
+ end
417
+ end
418
+
419
+ context "a grouped dataset" do
420
+ setup do
421
+ @dataset = Sequel::Dataset.new(nil).from(:test).group(:type_id)
422
+ end
423
+
424
+ specify "should raise when trying to generate an update statement" do
425
+ proc {@dataset.update_sql(:id => 0)}.should raise_error
426
+ end
427
+
428
+ specify "should raise when trying to generate a delete statement" do
429
+ proc {@dataset.delete_sql}.should raise_error
430
+ end
431
+
432
+ specify "should specify the grouping in generated select statement" do
433
+ @dataset.select_sql.should ==
434
+ "SELECT * FROM test GROUP BY type_id"
435
+ end
436
+ end
437
+
438
+
439
+ context "Dataset#literal" do
440
+ setup do
441
+ @dataset = Sequel::Dataset.new(nil).from(:test)
442
+ end
443
+
444
+ specify "should escape strings properly" do
445
+ @dataset.literal('abc').should == "'abc'"
446
+ @dataset.literal('a"x"bc').should == "'a\"x\"bc'"
447
+ @dataset.literal("a'bc").should == "'a''bc'"
448
+ @dataset.literal("a''bc").should == "'a''''bc'"
449
+ end
450
+
451
+ specify "should literalize numbers properly" do
452
+ @dataset.literal(1).should == "1"
453
+ @dataset.literal(1.5).should == "1.5"
454
+ end
455
+
456
+ specify "should literalize nil as NULL" do
457
+ @dataset.literal(nil).should == "NULL"
458
+ end
459
+
460
+ specify "should literalize an array properly" do
461
+ @dataset.literal([]).should == "NULL"
462
+ @dataset.literal([1, 'abc', 3]).should == "1, 'abc', 3"
463
+ @dataset.literal([1, "a'b''c", 3]).should == "1, 'a''b''''c', 3"
464
+ end
465
+
466
+ specify "should literalize symbols as column references" do
467
+ @dataset.literal(:name).should == "name"
468
+ @dataset.literal(:items__name).should == "items.name"
469
+ end
470
+
471
+ specify "should raise an error for unsupported types" do
472
+ proc {@dataset.literal({})}.should raise_error
473
+ end
474
+
475
+ specify "should literalize datasets as subqueries" do
476
+ d = @dataset.from(:test)
477
+ d.literal(d).should == "(#{d.sql})"
478
+ end
479
+
480
+ specify "should literalize Time properly" do
481
+ t = Time.now
482
+ s = t.strftime("TIMESTAMP '%Y-%m-%d %H:%M:%S'")
483
+ @dataset.literal(t).should == s
484
+ end
485
+
486
+ specify "should literalize Date properly" do
487
+ d = Date.today
488
+ s = d.strftime("DATE '%Y-%m-%d'")
489
+ @dataset.literal(d).should == s
490
+ end
491
+
492
+ specify "should not literalize expression strings" do
493
+ @dataset.literal('col1 + 2'.expr).should == 'col1 + 2'
494
+
495
+ @dataset.update_sql(:a => 'a + 2'.expr).should ==
496
+ 'UPDATE test SET a = a + 2'
497
+ end
498
+ end
499
+
500
+ context "Dataset#from" do
501
+ setup do
502
+ @dataset = Sequel::Dataset.new(nil)
503
+ end
504
+
505
+ specify "should accept a Dataset" do
506
+ proc {@dataset.from(@dataset)}.should_not raise_error
507
+ end
508
+
509
+ specify "should format a Dataset as a subquery if it has had options set" do
510
+ @dataset.from(@dataset.from(:a).where(:a=>1)).select_sql.should ==
511
+ "SELECT * FROM (SELECT * FROM a WHERE (a = 1))"
512
+ end
513
+
514
+ specify "should use the relevant table name if given a simple dataset" do
515
+ @dataset.from(@dataset.from(:a)).select_sql.should ==
516
+ "SELECT * FROM a"
517
+ end
518
+
519
+ specify "should raise if no source is given" do
520
+ proc {@dataset.from(@dataset.from).select_sql}.should raise_error(SequelError)
521
+ end
522
+ end
523
+
524
+ context "Dataset#select" do
525
+ setup do
526
+ @d = Sequel::Dataset.new(nil).from(:test)
527
+ end
528
+
529
+ specify "should accept variable arity" do
530
+ @d.select(:name).sql.should == 'SELECT name FROM test'
531
+ @d.select(:a, :b, :test__c).sql.should == 'SELECT a, b, test.c FROM test'
532
+ end
533
+
534
+ specify "should accept mixed types (strings and symbols)" do
535
+ @d.select('aaa').sql.should == 'SELECT aaa FROM test'
536
+ @d.select(:a, 'b').sql.should == 'SELECT a, b FROM test'
537
+ @d.select(:test__cc, 'test.d AS e').sql.should ==
538
+ 'SELECT test.cc, test.d AS e FROM test'
539
+ @d.select('test.d AS e', :test__cc).sql.should ==
540
+ 'SELECT test.d AS e, test.cc FROM test'
541
+
542
+ # symbol helpers
543
+ @d.select(:test.ALL).sql.should ==
544
+ 'SELECT test.* FROM test'
545
+ @d.select(:test__name.AS(:n)).sql.should ==
546
+ 'SELECT test.name AS n FROM test'
547
+ @d.select(:test__name___n).sql.should ==
548
+ 'SELECT test.name AS n FROM test'
549
+ end
550
+
551
+ specify "should use the wildcard if no arguments are given" do
552
+ @d.select.sql.should == 'SELECT * FROM test'
553
+ end
554
+
555
+ specify "should overrun the previous select option" do
556
+ @d.select(:a, :b, :c).select.sql.should == 'SELECT * FROM test'
557
+ @d.select(:price).select(:name).sql.should == 'SELECT name FROM test'
558
+ end
559
+ end
560
+
561
+ context "Dataset#order" do
562
+ setup do
563
+ @dataset = Sequel::Dataset.new(nil).from(:test)
564
+ end
565
+
566
+ specify "should include an ORDER BY clause in the select statement" do
567
+ @dataset.order(:name).sql.should ==
568
+ 'SELECT * FROM test ORDER BY name'
569
+ end
570
+
571
+ specify "should accept multiple arguments" do
572
+ @dataset.order(:name, :price.DESC).sql.should ==
573
+ 'SELECT * FROM test ORDER BY name, price DESC'
574
+ end
575
+
576
+ specify "should overrun a previous ordering" do
577
+ @dataset.order(:name).order(:stamp).sql.should ==
578
+ 'SELECT * FROM test ORDER BY stamp'
579
+ end
580
+
581
+ specify "should accept a string" do
582
+ @dataset.order('dada ASC').sql.should ==
583
+ 'SELECT * FROM test ORDER BY dada ASC'
584
+ end
585
+ end
586
+
587
+ context "Dataset#reverse_order" do
588
+ setup do
589
+ @dataset = Sequel::Dataset.new(nil).from(:test)
590
+ end
591
+
592
+ specify "should use DESC as default order" do
593
+ @dataset.reverse_order(:name).sql.should ==
594
+ 'SELECT * FROM test ORDER BY name DESC'
595
+ end
596
+
597
+ specify "should invert the order given" do
598
+ @dataset.reverse_order(:name.DESC).sql.should ==
599
+ 'SELECT * FROM test ORDER BY name'
600
+ end
601
+
602
+ specify "should accept multiple arguments" do
603
+ @dataset.reverse_order(:name, :price.DESC).sql.should ==
604
+ 'SELECT * FROM test ORDER BY name DESC, price'
605
+ end
606
+
607
+ specify "should reverse a previous ordering if no arguments are given" do
608
+ @dataset.order(:name).reverse_order.sql.should ==
609
+ 'SELECT * FROM test ORDER BY name DESC'
610
+ @dataset.order('clumsy DESC, fool').reverse_order.sql.should ==
611
+ 'SELECT * FROM test ORDER BY clumsy, fool DESC'
612
+ end
613
+ end
614
+
615
+ context "Dataset#limit" do
616
+ setup do
617
+ @dataset = Sequel::Dataset.new(nil).from(:test)
618
+ end
619
+
620
+ specify "should include a LIMIT clause in the select statement" do
621
+ @dataset.limit(10).sql.should ==
622
+ 'SELECT * FROM test LIMIT 10'
623
+ end
624
+
625
+ specify "should accept ranges" do
626
+ @dataset.limit(3..7).sql.should ==
627
+ 'SELECT * FROM test LIMIT 5 OFFSET 3'
628
+
629
+ @dataset.limit(3...7).sql.should ==
630
+ 'SELECT * FROM test LIMIT 4 OFFSET 3'
631
+ end
632
+
633
+ specify "should include an offset if a second argument is given" do
634
+ @dataset.limit(6, 10).sql.should ==
635
+ 'SELECT * FROM test LIMIT 6 OFFSET 10'
636
+ end
637
+ end
638
+
639
+ context "Dataset#naked" do
640
+ setup do
641
+ @d1 = Sequel::Dataset.new(nil, {1 => 2, 3 => 4})
642
+ @d2 = Sequel::Dataset.new(nil, {1 => 2, 3 => 4}).set_model(Object)
643
+ end
644
+
645
+ specify "should return a clone with :naked option set" do
646
+ naked = @d1.naked
647
+ naked.opts[:naked].should be_true
648
+ end
649
+
650
+ specify "should remove any existing reference to a model class" do
651
+ naked = @d2.naked
652
+ naked.opts[:models].should be_nil
653
+ end
654
+ end
655
+
656
+ context "Dataset#qualified_field_name" do
657
+ setup do
658
+ @dataset = Sequel::Dataset.new(nil).from(:test)
659
+ end
660
+
661
+ specify "should return the same if already qualified" do
662
+ @dataset.qualified_field_name('test.a', :items).should == 'test.a'
663
+ @dataset.qualified_field_name(:ccc__b, :items).should == 'ccc.b'
664
+ end
665
+
666
+ specify "should qualify the field with the supplied table name" do
667
+ @dataset.qualified_field_name('a', :items).should == 'items.a'
668
+ @dataset.qualified_field_name(:b1, :items).should == 'items.b1'
669
+ end
670
+ end
671
+
672
+ class DummyDataset < Sequel::Dataset
673
+ VALUES = [
674
+ {:a => 1, :b => 2},
675
+ {:a => 3, :b => 4},
676
+ {:a => 5, :b => 6}
677
+ ]
678
+ def fetch_rows(sql, &block)
679
+ VALUES.each(&block)
680
+ end
681
+ end
682
+
683
+ context "Dataset#map" do
684
+ setup do
685
+ @d = DummyDataset.new(nil).from(:items)
686
+ end
687
+
688
+ specify "should provide the usual functionality if no argument is given" do
689
+ @d.map {|n| n[:a] + n[:b]}.should == [3, 7, 11]
690
+ end
691
+
692
+ specify "should map using #[fieldname] if fieldname is given" do
693
+ @d.map(:a).should == [1, 3, 5]
694
+ end
695
+
696
+ specify "should return the complete dataset values if nothing is given" do
697
+ @d.map.should == DummyDataset::VALUES
698
+ end
699
+ end
700
+
701
+ context "Dataset#to_hash" do
702
+ setup do
703
+ @d = DummyDataset.new(nil).from(:items)
704
+ end
705
+
706
+ specify "should provide a hash with the first field as key and the second as value" do
707
+ @d.to_hash(:a, :b).should == {1 => 2, 3 => 4, 5 => 6}
708
+ @d.to_hash(:b, :a).should == {2 => 1, 4 => 3, 6 => 5}
709
+ end
710
+ end
711
+
712
+ context "Dataset#uniq" do
713
+ setup do
714
+ @dataset = Sequel::Dataset.new(nil).from(:test).select(:name)
715
+ end
716
+
717
+ specify "should include DISTINCT clause in statement" do
718
+ @dataset.uniq.sql.should == 'SELECT DISTINCT name FROM test'
719
+ end
720
+
721
+ specify "should be aliased by Dataset#distinct" do
722
+ @dataset.distinct.sql.should == 'SELECT DISTINCT name FROM test'
723
+ end
724
+ end
725
+
726
+ context "Dataset#count" do
727
+ setup do
728
+ @c = Class.new(Sequel::Dataset) do
729
+ def self.sql
730
+ @@sql
731
+ end
732
+
733
+ def fetch_rows(sql)
734
+ @@sql = sql
735
+ yield({1 => 1})
736
+ end
737
+ end
738
+ @dataset = @c.new(nil).from(:test)
739
+ end
740
+
741
+ specify "should format SQL propertly" do
742
+ @dataset.count.should == 1
743
+ @c.sql.should == 'SELECT COUNT(*) FROM test'
744
+ end
745
+
746
+ specify "should be aliased by #size" do
747
+ @dataset.size.should == 1
748
+ end
749
+
750
+ specify "should include the where clause if it's there" do
751
+ @dataset.filter {abc < 30}.count.should == 1
752
+ @c.sql.should == 'SELECT COUNT(*) FROM test WHERE (abc < 30)'
753
+ end
754
+ end
755
+
756
+ context "Dataset#join_table" do
757
+ setup do
758
+ @d = Sequel::Dataset.new(nil).from(:items)
759
+ end
760
+
761
+ specify "should format the JOIN clause properly" do
762
+ @d.join_table(:left_outer, :categories, :category_id => :id).sql.should ==
763
+ 'SELECT * FROM items LEFT OUTER JOIN categories ON (categories.category_id = items.id)'
764
+ end
765
+
766
+ specify "should include WHERE clause if applicable" do
767
+ @d.filter {price < 100}.join_table(:right_outer, :categories, :category_id => :id).sql.should ==
768
+ 'SELECT * FROM items RIGHT OUTER JOIN categories ON (categories.category_id = items.id) WHERE (price < 100)'
769
+ end
770
+
771
+ specify "should include ORDER BY clause if applicable" do
772
+ @d.order(:stamp).join_table(:full_outer, :categories, :category_id => :id).sql.should ==
773
+ 'SELECT * FROM items FULL OUTER JOIN categories ON (categories.category_id = items.id) ORDER BY stamp'
774
+ end
775
+
776
+ specify "should support multiple joins" do
777
+ @d.join_table(:inner, :b, :items_id).join_table(:left_outer, :c, :b_id => :b__id).sql.should ==
778
+ 'SELECT * FROM items INNER JOIN b ON (b.items_id = items.id) LEFT OUTER JOIN c ON (c.b_id = b.id)'
779
+ end
780
+
781
+ specify "should use id as implicit relation primary key if ommited" do
782
+ @d.join_table(:left_outer, :categories, :category_id).sql.should ==
783
+ @d.join_table(:left_outer, :categories, :category_id => :id).sql
784
+
785
+ # when doing multiple joins, id should be qualified using the last joined table
786
+ @d.join_table(:right_outer, :b, :items_id).join_table(:full_outer, :c, :b_id).sql.should ==
787
+ 'SELECT * FROM items RIGHT OUTER JOIN b ON (b.items_id = items.id) FULL OUTER JOIN c ON (c.b_id = b.id)'
788
+ end
789
+
790
+ specify "should support left outer joins" do
791
+ @d.join_table(:left_outer, :categories, :category_id).sql.should ==
792
+ 'SELECT * FROM items LEFT OUTER JOIN categories ON (categories.category_id = items.id)'
793
+
794
+ @d.left_outer_join(:categories, :category_id).sql.should ==
795
+ 'SELECT * FROM items LEFT OUTER JOIN categories ON (categories.category_id = items.id)'
796
+ end
797
+
798
+ specify "should support right outer joins" do
799
+ @d.join_table(:right_outer, :categories, :category_id).sql.should ==
800
+ 'SELECT * FROM items RIGHT OUTER JOIN categories ON (categories.category_id = items.id)'
801
+
802
+ @d.right_outer_join(:categories, :category_id).sql.should ==
803
+ 'SELECT * FROM items RIGHT OUTER JOIN categories ON (categories.category_id = items.id)'
804
+ end
805
+
806
+ specify "should support full outer joins" do
807
+ @d.join_table(:full_outer, :categories, :category_id).sql.should ==
808
+ 'SELECT * FROM items FULL OUTER JOIN categories ON (categories.category_id = items.id)'
809
+
810
+ @d.full_outer_join(:categories, :category_id).sql.should ==
811
+ 'SELECT * FROM items FULL OUTER JOIN categories ON (categories.category_id = items.id)'
812
+ end
813
+
814
+ specify "should support inner joins" do
815
+ @d.join_table(:inner, :categories, :category_id).sql.should ==
816
+ 'SELECT * FROM items INNER JOIN categories ON (categories.category_id = items.id)'
817
+
818
+ @d.inner_join(:categories, :category_id).sql.should ==
819
+ 'SELECT * FROM items INNER JOIN categories ON (categories.category_id = items.id)'
820
+ end
821
+
822
+ specify "should default to an inner join" do
823
+ @d.join_table(nil, :categories, :category_id).sql.should ==
824
+ 'SELECT * FROM items INNER JOIN categories ON (categories.category_id = items.id)'
825
+
826
+ @d.join(:categories, :category_id).sql.should ==
827
+ 'SELECT * FROM items INNER JOIN categories ON (categories.category_id = items.id)'
828
+ end
829
+
830
+ specify "should raise if an invalid join type is specified" do
831
+ proc {@d.join_table(:invalid, :a, :b)}.should raise_error(SequelError)
832
+ end
833
+ end
834
+
835
+ context "Dataset#[]=" do
836
+ setup do
837
+ c = Class.new(Sequel::Dataset) do
838
+ def last_sql
839
+ @@last_sql
840
+ end
841
+
842
+ def update(*args)
843
+ @@last_sql = update_sql(*args)
844
+ end
845
+ end
846
+
847
+ @d = c.new(nil).from(:items)
848
+ end
849
+
850
+ specify "should perform an update on the specified filter" do
851
+ @d[:a => 1] = {:x => 3}
852
+ @d.last_sql.should == 'UPDATE items SET x = 3 WHERE (a = 1)'
853
+ end
854
+ end
855
+
856
+ context "Dataset#insert_multiple" do
857
+ setup do
858
+ c = Class.new(Sequel::Dataset) do
859
+ attr_reader :inserts
860
+ def insert(arg)
861
+ @inserts ||= []
862
+ @inserts << arg
863
+ end
864
+ end
865
+
866
+ @d = c.new(nil)
867
+ end
868
+
869
+ specify "should insert all items in the supplied array" do
870
+ @d.insert_multiple [:aa, 5, 3, {1 => 2}]
871
+ @d.inserts.should == [:aa, 5, 3, {1 => 2}]
872
+ end
873
+
874
+ specify "should pass array items through the supplied block if given" do
875
+ a = ["inevitable", "hello", "the ticking clock"]
876
+ @d.insert_multiple(a) {|i| i.gsub('l', 'r')}
877
+ @d.inserts.should == ["inevitabre", "herro", "the ticking crock"]
878
+ end
879
+ end
880
+
881
+ context "Dataset aggregate methods" do
882
+ setup do
883
+ c = Class.new(Sequel::Dataset) do
884
+ def fetch_rows(sql)
885
+ yield({1 => sql})
886
+ end
887
+ end
888
+ @d = c.new(nil).from(:test)
889
+ end
890
+
891
+ specify "should include min" do
892
+ @d.min(:a).should == 'SELECT min(a) FROM test'
893
+ end
894
+
895
+ specify "should include max" do
896
+ @d.max(:b).should == 'SELECT max(b) FROM test'
897
+ end
898
+
899
+ specify "should include sum" do
900
+ @d.sum(:c).should == 'SELECT sum(c) FROM test'
901
+ end
902
+
903
+ specify "should include avg" do
904
+ @d.avg(:d).should == 'SELECT avg(d) FROM test'
905
+ end
906
+
907
+ specify "should accept qualified fields" do
908
+ @d.avg(:test__bc).should == 'SELECT avg(test.bc) FROM test'
909
+ end
910
+ end
911
+
912
+ context "Dataset#first" do
913
+ setup do
914
+ @c = Class.new(Sequel::Dataset) do
915
+ @@last_dataset = nil
916
+ @@last_opts = nil
917
+
918
+ def self.last_dataset
919
+ @@last_dataset
920
+ end
921
+
922
+ def self.last_opts
923
+ @@last_opts
924
+ end
925
+
926
+ def single_record(opts = nil)
927
+ @@last_opts = @opts.merge(opts || {})
928
+ {:a => 1, :b => 2}
929
+ end
930
+
931
+ def all
932
+ @@last_dataset = self
933
+ [{:a => 1, :b => 2}] * @opts[:limit]
934
+ end
935
+ end
936
+ @d = @c.new(nil).from(:test)
937
+ end
938
+
939
+ specify "should return the first matching record if a hash is specified" do
940
+ @d.first(:z => 26).should == {:a => 1, :b => 2}
941
+ @c.last_opts[:where].should == ('(z = 26)')
942
+
943
+ @d.first('z = ?', 15)
944
+ @c.last_opts[:where].should == ('z = 15')
945
+ end
946
+
947
+ specify "should return a single record if no argument is given" do
948
+ @d.first.should == {:a => 1, :b => 2}
949
+ end
950
+
951
+ specify "should set the limit according to the given number" do
952
+ @d.first
953
+ @c.last_opts[:limit].should == 1
954
+
955
+ i = rand(10) + 10
956
+ @d.first(i)
957
+ @c.last_dataset.opts[:limit].should == i
958
+ end
959
+
960
+ specify "should return an array with the records if argument is greater than 1" do
961
+ i = rand(10) + 10
962
+ r = @d.first(i)
963
+ r.should be_a_kind_of(Array)
964
+ r.size.should == i
965
+ r.each {|row| row.should == {:a => 1, :b => 2}}
966
+ end
967
+ end
968
+
969
+ context "Dataset#last" do
970
+ setup do
971
+ @c = Class.new(Sequel::Dataset) do
972
+ @@last_dataset = nil
973
+
974
+ def self.last_dataset
975
+ @@last_dataset
976
+ end
977
+
978
+ def single_record(opts = nil)
979
+ @@last_dataset = clone_merge(opts) if opts
980
+ {:a => 1, :b => 2}
981
+ end
982
+
983
+ def all
984
+ @@last_dataset = self
985
+ [{:a => 1, :b => 2}] * @opts[:limit]
986
+ end
987
+ end
988
+ @d = @c.new(nil).from(:test)
989
+ end
990
+
991
+ specify "should raise if no order is given" do
992
+ proc {@d.last}.should raise_error(SequelError)
993
+ proc {@d.last(2)}.should raise_error(SequelError)
994
+ proc {@d.order(:a).last}.should_not raise_error
995
+ proc {@d.order(:a).last(2)}.should_not raise_error
996
+ end
997
+
998
+ specify "should invert the order" do
999
+ @d.order(:a).last
1000
+ @c.last_dataset.opts[:order].should == ['a DESC']
1001
+
1002
+ @d.order(:b.DESC).last
1003
+ @c.last_dataset.opts[:order].should == ['b']
1004
+
1005
+ @d.order(:c, :d).last
1006
+ @c.last_dataset.opts[:order].should == ['c DESC', 'd DESC']
1007
+
1008
+ @d.order(:e.DESC, :f).last
1009
+ @c.last_dataset.opts[:order].should == ['e', 'f DESC']
1010
+ end
1011
+
1012
+ specify "should return the first matching record if a hash is specified" do
1013
+ @d.order(:a).last(:z => 26).should == {:a => 1, :b => 2}
1014
+ @c.last_dataset.opts[:where].should == ('(z = 26)')
1015
+
1016
+ @d.order(:a).last('z = ?', 15)
1017
+ @c.last_dataset.opts[:where].should == ('z = 15')
1018
+ end
1019
+
1020
+ specify "should return a single record if no argument is given" do
1021
+ @d.order(:a).last.should == {:a => 1, :b => 2}
1022
+ end
1023
+
1024
+ specify "should set the limit according to the given number" do
1025
+ i = rand(10) + 10
1026
+ r = @d.order(:a).last(i)
1027
+ @c.last_dataset.opts[:limit].should == i
1028
+ end
1029
+
1030
+ specify "should return an array with the records if argument is greater than 1" do
1031
+ i = rand(10) + 10
1032
+ r = @d.order(:a).last(i)
1033
+ r.should be_a_kind_of(Array)
1034
+ r.size.should == i
1035
+ r.each {|row| row.should == {:a => 1, :b => 2}}
1036
+ end
1037
+ end
1038
+
1039
+ context "Dataset set operations" do
1040
+ setup do
1041
+ @a = Sequel::Dataset.new(nil).from(:a).filter(:z => 1)
1042
+ @b = Sequel::Dataset.new(nil).from(:b).filter(:z => 2)
1043
+ end
1044
+
1045
+ specify "should support UNION and UNION ALL" do
1046
+ @a.union(@b).sql.should == \
1047
+ "SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2)"
1048
+ @b.union(@a, true).sql.should == \
1049
+ "SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)"
1050
+ end
1051
+
1052
+ specify "should support INTERSECT and INTERSECT ALL" do
1053
+ @a.intersect(@b).sql.should == \
1054
+ "SELECT * FROM a WHERE (z = 1) INTERSECT SELECT * FROM b WHERE (z = 2)"
1055
+ @b.intersect(@a, true).sql.should == \
1056
+ "SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)"
1057
+ end
1058
+
1059
+ specify "should support EXCEPT and EXCEPT ALL" do
1060
+ @a.except(@b).sql.should == \
1061
+ "SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2)"
1062
+ @b.except(@a, true).sql.should == \
1063
+ "SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)"
1064
+ end
1065
+ end
1066
+
1067
+ context "Dataset#[]" do
1068
+ setup do
1069
+ @c = Class.new(Sequel::Dataset) do
1070
+ @@last_dataset = nil
1071
+
1072
+ def self.last_dataset
1073
+ @@last_dataset
1074
+ end
1075
+
1076
+ def single_record(opts = nil)
1077
+ @@last_dataset = opts ? clone_merge(opts) : self
1078
+ {1 => 2, 3 => 4}
1079
+ end
1080
+ end
1081
+ @d = @c.new(nil).from(:test)
1082
+ end
1083
+
1084
+ specify "should return a single record filtered according to the given conditions" do
1085
+ @d[:name => 'didi'].should == {1 => 2, 3 => 4}
1086
+ @c.last_dataset.opts[:where].should == "(name = 'didi')"
1087
+
1088
+ @d[:id => 5..45].should == {1 => 2, 3 => 4}
1089
+ @c.last_dataset.opts[:where].should == "(id >= 5 AND id <= 45)"
1090
+ end
1091
+ end
1092
+
1093
+ context "Dataset#single_record" do
1094
+ setup do
1095
+ @c = Class.new(Sequel::Dataset) do
1096
+ def fetch_rows(sql)
1097
+ yield sql
1098
+ end
1099
+ end
1100
+ @cc = Class.new(@c) do
1101
+ def fetch_rows(sql); end
1102
+ end
1103
+ @d = @c.new(nil).from(:test)
1104
+ @e = @cc.new(nil).from(:test)
1105
+ end
1106
+
1107
+ specify "should call each and return the first record" do
1108
+ @d.single_record.should == 'SELECT * FROM test'
1109
+ end
1110
+
1111
+ specify "should pass opts to each" do
1112
+ @d.single_record(:limit => 3).should == 'SELECT * FROM test LIMIT 3'
1113
+ end
1114
+
1115
+ specify "should return nil if no record is present" do
1116
+ @e.single_record.should be_nil
1117
+ end
1118
+ end
1119
+
1120
+ context "Dataset#single_value" do
1121
+ setup do
1122
+ @c = Class.new(Sequel::Dataset) do
1123
+ def fetch_rows(sql)
1124
+ yield({1 => sql})
1125
+ end
1126
+ end
1127
+ @d = @c.new(nil).from(:test)
1128
+ end
1129
+
1130
+ specify "should call each and return the first value of the first record" do
1131
+ @d.single_value.should == 'SELECT * FROM test'
1132
+ end
1133
+
1134
+ specify "should pass opts to each" do
1135
+ @d.single_value(:limit => 3).should == 'SELECT * FROM test LIMIT 3'
1136
+ end
1137
+ end
1138
+
1139
+ context "Dataset#set_model" do
1140
+ setup do
1141
+ @c = Class.new(Sequel::Dataset) do
1142
+ def fetch_rows(sql, &block)
1143
+ (1..10).each(&block)
1144
+ end
1145
+ end
1146
+ @dataset = @c.new(nil).from(:items)
1147
+ @m = Class.new do
1148
+ attr_accessor :c
1149
+ def initialize(c); @c = c; end
1150
+ def ==(o); @c == o.c; end
1151
+ end
1152
+ end
1153
+
1154
+ specify "should clear the models hash and restore the stock #each if nil is specified" do
1155
+ @dataset.set_model(@m)
1156
+ @dataset.set_model(nil)
1157
+ @dataset.first.should == 1
1158
+ @dataset.model_classes.should be_nil
1159
+ end
1160
+
1161
+ specify "should clear the models hash and restore the stock #each if nothing is specified" do
1162
+ @dataset.set_model(@m)
1163
+ @dataset.set_model
1164
+ @dataset.first.should == 1
1165
+ @dataset.model_classes.should be_nil
1166
+ end
1167
+
1168
+ specify "should alter #each to provide model instances" do
1169
+ @dataset.first.should == 1
1170
+ @dataset.set_model(@m)
1171
+ @dataset.first.should == @m.new(1)
1172
+ end
1173
+
1174
+ specify "should extend the dataset with a #destroy method" do
1175
+ @dataset.should_not respond_to(:destroy)
1176
+ @dataset.set_model(@m)
1177
+ @dataset.should respond_to(:destroy)
1178
+ end
1179
+
1180
+ specify "should set opts[:naked] to nil" do
1181
+ @dataset.opts[:naked] = true
1182
+ @dataset.set_model(@m)
1183
+ @dataset.opts[:naked].should be_nil
1184
+ end
1185
+
1186
+ specify "should provide support for polymorphic model instantiation" do
1187
+ @m1 = Class.new(@m)
1188
+ @m2 = Class.new(@m)
1189
+ @dataset.set_model(0, 0 => @m1, 1 => @m2)
1190
+ all = @dataset.all
1191
+ all[0].class.should == @m2
1192
+ all[1].class.should == @m1
1193
+ all[2].class.should == @m2
1194
+ all[3].class.should == @m1
1195
+ #...
1196
+ end
1197
+ end
1198
+
1199
+ context "Dataset#model_classes" do
1200
+ setup do
1201
+ @c = Class.new(Sequel::Dataset) do
1202
+ # # We don't need that for now
1203
+ # def fetch_rows(sql, &block)
1204
+ # (1..10).each(&block)
1205
+ # end
1206
+ end
1207
+ @dataset = @c.new(nil).from(:items)
1208
+ @m = Class.new do
1209
+ attr_accessor :c
1210
+ def initialize(c); @c = c; end
1211
+ def ==(o); @c == o.c; end
1212
+ end
1213
+ end
1214
+
1215
+ specify "should return nil for a naked dataset" do
1216
+ @dataset.model_classes.should == nil
1217
+ end
1218
+
1219
+ specify "should return a {nil => model_class} hash for a model dataset" do
1220
+ @dataset.set_model(@m)
1221
+ @dataset.model_classes.should == {nil => @m}
1222
+ end
1223
+
1224
+ specify "should return the polymorphic hash for a polymorphic model dataset" do
1225
+ @m1 = Class.new(@m)
1226
+ @m2 = Class.new(@m)
1227
+ @dataset.set_model(0, 0 => @m1, 1 => @m2)
1228
+ @dataset.model_classes.should == {0 => @m1, 1 => @m2}
1229
+ end
1230
+ end
1231
+
1232
+ context "Dataset#polymorphic_key" do
1233
+ setup do
1234
+ @c = Class.new(Sequel::Dataset) do
1235
+ # # We don't need this for now
1236
+ # def fetch_rows(sql, &block)
1237
+ # (1..10).each(&block)
1238
+ # end
1239
+ end
1240
+ @dataset = @c.new(nil).from(:items)
1241
+ @m = Class.new do
1242
+ attr_accessor :c
1243
+ def initialize(c); @c = c; end
1244
+ def ==(o); @c == o.c; end
1245
+ end
1246
+ end
1247
+
1248
+ specify "should return nil for a naked dataset" do
1249
+ @dataset.polymorphic_key.should be_nil
1250
+ end
1251
+
1252
+ specify "should return the polymorphic key" do
1253
+ @dataset.set_model(:id, nil => @m)
1254
+ @dataset.polymorphic_key.should == :id
1255
+ end
1256
+ end
1257
+
1258
+ context "A model dataset" do
1259
+ setup do
1260
+ @c = Class.new(Sequel::Dataset) do
1261
+ def fetch_rows(sql, &block)
1262
+ (1..10).each(&block)
1263
+ end
1264
+ end
1265
+ @dataset = @c.new(nil).from(:items)
1266
+ @m = Class.new do
1267
+ attr_accessor :c
1268
+ def initialize(c); @c = c; end
1269
+ def ==(o); @c == o.c; end
1270
+ end
1271
+ @dataset.set_model(@m)
1272
+ end
1273
+
1274
+ specify "should supply naked records if the naked option is specified" do
1275
+ @dataset.each {|r| r.class.should == @m}
1276
+ @dataset.each(:naked => true) {|r| r.class.should == Fixnum}
1277
+ end
1278
+ end
1279
+
1280
+ context "A polymorphic model dataset" do
1281
+ setup do
1282
+ @c = Class.new(Sequel::Dataset) do
1283
+ def fetch_rows(sql, &block)
1284
+ (1..10).each(&block)
1285
+ end
1286
+ end
1287
+ @dataset = @c.new(nil).from(:items)
1288
+ @m = Class.new do
1289
+ attr_accessor :c
1290
+ def initialize(c); @c = c; end
1291
+ def ==(o); @c == o.c; end
1292
+ end
1293
+ end
1294
+
1295
+ specify "should use a nil key in the polymorphic hash to specify the default model class" do
1296
+ @m2 = Class.new(@m)
1297
+ @dataset.set_model(0, nil => @m, 1 => @m2)
1298
+ all = @dataset.all
1299
+ all[0].class.should == @m2
1300
+ all[1].class.should == @m
1301
+ all[2].class.should == @m2
1302
+ all[3].class.should == @m
1303
+ #...
1304
+ end
1305
+
1306
+ specify "should raise SequelError if no suitable class is found in the polymorphic hash" do
1307
+ @m2 = Class.new(@m)
1308
+ @dataset.set_model(0, 1 => @m2)
1309
+ proc {@dataset.all}.should raise_error(SequelError)
1310
+ end
1311
+
1312
+ specify "should supply naked records if the naked option is specified" do
1313
+ @dataset.set_model(0, nil => @m)
1314
+ @dataset.each(:naked => true) {|r| r.class.should == Fixnum}
1315
+ end
1316
+ end
1317
+
1318
+ context "Dataset#destroy" do
1319
+ setup do
1320
+ db = Object.new
1321
+ m = Module.new do
1322
+ def transaction; yield; end
1323
+ end
1324
+ db.extend(m)
1325
+
1326
+ DESTROYED = []
1327
+
1328
+ @m = Class.new do
1329
+ def initialize(c)
1330
+ @c = c
1331
+ end
1332
+
1333
+ attr_accessor :c
1334
+
1335
+ def ==(o)
1336
+ @c == o.c
1337
+ end
1338
+
1339
+ def destroy
1340
+ DESTROYED << self
1341
+ end
1342
+ end
1343
+ MODELS = [@m.new(12), @m.new(13)]
1344
+
1345
+ c = Class.new(Sequel::Dataset) do
1346
+ def fetch_rows(sql, &block)
1347
+ (12..13).each(&block)
1348
+ end
1349
+ end
1350
+
1351
+ @d = c.new(db).from(:test)
1352
+ @d.set_model(@m)
1353
+ end
1354
+
1355
+ specify "should destroy raise for every model in the dataset" do
1356
+ count = @d.destroy
1357
+ count.should == 2
1358
+ DESTROYED.should == MODELS
1359
+ end
1360
+ end
1361
+
1362
+ context "Dataset#<<" do
1363
+ setup do
1364
+ @d = Sequel::Dataset.new(nil)
1365
+ @d.meta_def(:insert) do
1366
+ 1234567890
1367
+ end
1368
+ end
1369
+
1370
+ specify "should call #insert" do
1371
+ (@d << {:name => 1}).should == 1234567890
1372
+ end
1373
+ end
1374
+
1375
+ context "A paginated dataset" do
1376
+ setup do
1377
+ @d = Sequel::Dataset.new(nil)
1378
+ @d.meta_def(:count) {153}
1379
+
1380
+ @paginated = @d.paginate(1, 20)
1381
+ end
1382
+
1383
+ specify "should set the limit and offset options correctly" do
1384
+ @paginated.opts[:limit].should == 20
1385
+ @paginated.opts[:offset].should == 0
1386
+ end
1387
+
1388
+ specify "should set the page count correctly" do
1389
+ @paginated.page_count.should == 8
1390
+ @d.paginate(1, 50).page_count.should == 4
1391
+ end
1392
+
1393
+ specify "should set the current page number correctly" do
1394
+ @paginated.current_page.should == 1
1395
+ @d.paginate(3, 50).current_page.should == 3
1396
+ end
1397
+
1398
+ specify "should return the next page number or nil if we're on the last" do
1399
+ @paginated.next_page.should == 2
1400
+ @d.paginate(4, 50).next_page.should be_nil
1401
+ end
1402
+
1403
+ specify "should return the previous page number or nil if we're on the last" do
1404
+ @paginated.prev_page.should be_nil
1405
+ @d.paginate(4, 50).prev_page.should == 3
1406
+ end
1407
+ end
1408
+
1409
+ context "Dataset#columns" do
1410
+ setup do
1411
+ @dataset = DummyDataset.new(nil).from(:items)
1412
+ @dataset.meta_def(:columns=) {|c| @columns = c}
1413
+ @dataset.meta_def(:first) {@columns = select_sql(nil)}
1414
+ end
1415
+
1416
+ specify "should return the value of @columns" do
1417
+ @dataset.columns = [:a, :b, :c]
1418
+ @dataset.columns.should == [:a, :b, :c]
1419
+ end
1420
+
1421
+ specify "should call first if @columns is nil" do
1422
+ @dataset.columns = nil
1423
+ @dataset.columns.should == 'SELECT * FROM items'
1424
+ @dataset.opts[:from] = [:nana]
1425
+ @dataset.columns.should == 'SELECT * FROM items'
1426
+ end
1427
+ end
1428
+
1429
+ require 'stringio'
1430
+
1431
+ context "Dataset#print" do
1432
+ setup do
1433
+ @output = StringIO.new
1434
+ @orig_stdout = $stdout
1435
+ $stdout = @output
1436
+ @dataset = DummyDataset.new(nil).from(:items)
1437
+ end
1438
+
1439
+ teardown do
1440
+ $stdout = @orig_stdout
1441
+ end
1442
+
1443
+ specify "should print out a table with the values" do
1444
+ @dataset.print(:a, :b)
1445
+ @output.rewind
1446
+ @output.read.should == \
1447
+ "+-+-+\n|a|b|\n+-+-+\n|1|2|\n|3|4|\n|5|6|\n+-+-+\n"
1448
+ end
1449
+ end