sequel_model 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,107 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ describe Sequel::Model, "hooks" do
4
+
5
+ before do
6
+ MODEL_DB.reset
7
+ Sequel::Model.hooks.clear
8
+
9
+ @hooks = %w[
10
+ before_save before_create before_update before_destroy
11
+ after_save after_create after_update after_destroy
12
+ ].select { |hook| !hook.empty? }
13
+ end
14
+
15
+ it "should have hooks for everything" do
16
+ Sequel::Model.methods.should include('hooks')
17
+ Sequel::Model.methods.should include(*@hooks)
18
+ @hooks.each do |hook|
19
+ Sequel::Model.hooks[hook.to_sym].should be_an_instance_of(Array)
20
+ end
21
+ end
22
+
23
+ it "should be inherited" do
24
+ pending 'soon'
25
+
26
+ @hooks.each do |hook|
27
+ Sequel::Model.send(hook.to_sym) { nil }
28
+ end
29
+
30
+ model = Class.new Sequel::Model(:models)
31
+ model.hooks.should == Sequel::Model.hooks
32
+ end
33
+
34
+ it "should run hooks" do
35
+ pending 'soon'
36
+
37
+ test = mock 'Test'
38
+ test.should_receive(:run).exactly(@hooks.length)
39
+
40
+ @hooks.each do |hook|
41
+ Sequel::Model.send(hook.to_sym) { test.run }
42
+ end
43
+
44
+ model = Class.new Sequel::Model(:models)
45
+ model.hooks.should == Sequel::Model.hooks
46
+
47
+ model_instance = model.new
48
+ @hooks.each { |hook| model_instance.run_hooks(hook) }
49
+ end
50
+
51
+ it "should run hooks around save and create" do
52
+ pending 'test execution'
53
+ end
54
+
55
+ it "should run hooks around save and update" do
56
+ pending 'test execution'
57
+ end
58
+
59
+ it "should run hooks around delete" do
60
+ pending 'test execution'
61
+ end
62
+
63
+ end
64
+
65
+ describe "Model.after_create" do
66
+
67
+ before(:each) do
68
+ MODEL_DB.reset
69
+
70
+ @c = Class.new(Sequel::Model(:items)) do
71
+ def columns
72
+ [:id, :x, :y]
73
+ end
74
+ end
75
+
76
+ ds = @c.dataset
77
+ def ds.insert(*args)
78
+ super(*args)
79
+ 1
80
+ end
81
+ end
82
+
83
+ it "should be called after creation" do
84
+ s = []
85
+
86
+ @c.after_create do
87
+ s = MODEL_DB.sqls.dup
88
+ end
89
+
90
+ n = @c.create(:x => 1)
91
+ MODEL_DB.sqls.should == ["INSERT INTO items (x) VALUES (1)", "SELECT * FROM items WHERE (id = 1) LIMIT 1"]
92
+ s.should == ["INSERT INTO items (x) VALUES (1)", "SELECT * FROM items WHERE (id = 1) LIMIT 1"]
93
+ end
94
+
95
+ it "should allow calling save in the hook" do
96
+ @c.after_create do
97
+ values.delete(:x)
98
+ self.id = 2
99
+ save
100
+ end
101
+
102
+ n = @c.create(:id => 1)
103
+ MODEL_DB.sqls.should == ["INSERT INTO items (id) VALUES (1)", "SELECT * FROM items WHERE (id = 1) LIMIT 1", "UPDATE items SET id = 2 WHERE (id = 1)"]
104
+ end
105
+
106
+ end
107
+
@@ -0,0 +1,564 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ describe Sequel::Model do
4
+ it "should have class method aliased as model" do
5
+ Sequel::Model.instance_methods.should include("model")
6
+
7
+ model_a = Class.new Sequel::Model
8
+ model_a.new.model.should be(model_a)
9
+ end
10
+
11
+ it "should be associated with a dataset" do
12
+ model_a = Class.new(Sequel::Model) { set_dataset MODEL_DB[:as] }
13
+
14
+ model_a.dataset.should be_a_kind_of(MockDataset)
15
+ model_a.dataset.opts[:from].should == [:as]
16
+
17
+ model_b = Class.new(Sequel::Model) { set_dataset MODEL_DB[:bs] }
18
+
19
+ model_b.dataset.should be_a_kind_of(MockDataset)
20
+ model_b.dataset.opts[:from].should == [:bs]
21
+
22
+ model_a.dataset.opts[:from].should == [:as]
23
+ end
24
+
25
+ end
26
+
27
+ describe Sequel::Model, "dataset & schema" do
28
+
29
+ before do
30
+ @model = Class.new(Sequel::Model(:items))
31
+ end
32
+
33
+ it "creates dynamic model subclass with set table name" do
34
+ @model.table_name.should == :items
35
+ end
36
+
37
+ it "defaults to primary key of id" do
38
+ @model.primary_key.should == :id
39
+ end
40
+
41
+ it "allow primary key change" do
42
+ @model.set_primary_key :ssn
43
+ @model.primary_key.should == :ssn
44
+ end
45
+
46
+ it "allows dataset change" do
47
+ @model.set_dataset(MODEL_DB[:foo])
48
+ @model.table_name.should == :foo
49
+ end
50
+
51
+ it "sets schema with implicit table name" do
52
+ @model.set_schema do
53
+ primary_key :ssn, :string
54
+ end
55
+ @model.primary_key.should == :ssn
56
+ @model.table_name.should == :items
57
+ end
58
+
59
+ it "sets schema with explicit table name" do
60
+ @model.set_schema :foo do
61
+ primary_key :id
62
+ end
63
+ @model.primary_key.should == :id
64
+ @model.table_name.should == :foo
65
+ end
66
+
67
+ it "puts the lotion in the basket or it gets the hose again" do
68
+ # just kidding!
69
+ end
70
+ end
71
+
72
+ describe Sequel::Model, "constructor" do
73
+
74
+ before(:each) do
75
+ @m = Class.new(Sequel::Model)
76
+ end
77
+
78
+ it "should accept a hash" do
79
+ m = @m.new(:a => 1, :b => 2)
80
+ m.values.should == {:a => 1, :b => 2}
81
+ m.should be_new
82
+ end
83
+
84
+ it "should accept a block and yield itself to the block" do
85
+ block_called = false
86
+ m = @m.new {|i| block_called = true; i.should be_a_kind_of(@m); i.values[:a] = 1}
87
+
88
+ block_called.should be_true
89
+ m.values[:a].should == 1
90
+ end
91
+
92
+ end
93
+
94
+ describe Sequel::Model, "new" do
95
+
96
+ before(:each) do
97
+ @m = Class.new(Sequel::Model) do
98
+ set_dataset MODEL_DB[:items]
99
+ end
100
+ end
101
+
102
+ it "should be marked as new?" do
103
+ o = @m.new
104
+ o.should be_new
105
+ o.should be_new_record
106
+ end
107
+
108
+ it "should not be marked as new? once it is saved" do
109
+ o = @m.new(:x => 1)
110
+ o.should be_new
111
+ o.save
112
+ o.should_not be_new
113
+ o.should_not be_new_record
114
+ end
115
+
116
+ it "should use the last inserted id as primary key if not in values" do
117
+ d = @m.dataset
118
+ def d.insert(*args)
119
+ super
120
+ 1234
121
+ end
122
+
123
+ def d.first
124
+ {:x => 1, :id => 1234}
125
+ end
126
+
127
+ o = @m.new(:x => 1)
128
+ o.save
129
+ o.id.should == 1234
130
+
131
+ o = @m.new(:x => 1, :id => 333)
132
+ o.save
133
+ o.id.should == 333
134
+ end
135
+
136
+ end
137
+
138
+ describe Sequel::Model, ".subset" do
139
+
140
+ before(:each) do
141
+ MODEL_DB.reset
142
+
143
+ @c = Class.new(Sequel::Model(:items))
144
+ end
145
+
146
+ it "should create a filter on the underlying dataset" do
147
+ proc {@c.new_only}.should raise_error(NoMethodError)
148
+
149
+ @c.subset(:new_only) {:age == 'new'}
150
+
151
+ @c.new_only.sql.should == "SELECT * FROM items WHERE (age = 'new')"
152
+ @c.dataset.new_only.sql.should == "SELECT * FROM items WHERE (age = 'new')"
153
+
154
+ @c.subset(:pricey) {:price > 100}
155
+
156
+ @c.pricey.sql.should == "SELECT * FROM items WHERE (price > 100)"
157
+ @c.dataset.pricey.sql.should == "SELECT * FROM items WHERE (price > 100)"
158
+
159
+ # check if subsets are composable
160
+ @c.pricey.new_only.sql.should == "SELECT * FROM items WHERE (price > 100) AND (age = 'new')"
161
+ @c.new_only.pricey.sql.should == "SELECT * FROM items WHERE (age = 'new') AND (price > 100)"
162
+ end
163
+
164
+ end
165
+
166
+ describe Sequel::Model, ".find" do
167
+
168
+ before(:each) do
169
+ MODEL_DB.reset
170
+
171
+ @c = Class.new(Sequel::Model(:items))
172
+
173
+ $cache_dataset_row = {:name => 'sharon', :id => 1}
174
+ @dataset = @c.dataset
175
+ $sqls = []
176
+ @dataset.extend(Module.new {
177
+ def fetch_rows(sql)
178
+ $sqls << sql
179
+ yield $cache_dataset_row
180
+ end
181
+ })
182
+ end
183
+
184
+ it "should return the first record matching the given filter" do
185
+ @c.find(:name => 'sharon').should be_a_kind_of(@c)
186
+ $sqls.last.should == "SELECT * FROM items WHERE (name = 'sharon') LIMIT 1"
187
+
188
+ @c.find {"name LIKE 'abc%'".lit}.should be_a_kind_of(@c)
189
+ $sqls.last.should == "SELECT * FROM items WHERE name LIKE 'abc%' LIMIT 1"
190
+ end
191
+
192
+ it "should accept filter blocks" do
193
+ @c.find {:id == 1}.should be_a_kind_of(@c)
194
+ $sqls.last.should == "SELECT * FROM items WHERE (id = 1) LIMIT 1"
195
+
196
+ @c.find {:x > 1 && :y < 2}.should be_a_kind_of(@c)
197
+ $sqls.last.should == "SELECT * FROM items WHERE ((x > 1) AND (y < 2)) LIMIT 1"
198
+ end
199
+
200
+ end
201
+
202
+ describe Sequel::Model, ".fetch" do
203
+
204
+ before(:each) do
205
+ MODEL_DB.reset
206
+ @c = Class.new(Sequel::Model(:items))
207
+ end
208
+
209
+ it "should return instances of Model" do
210
+ @c.fetch("SELECT * FROM items").first.should be_a_kind_of(@c)
211
+ end
212
+
213
+ it "should return true for .empty? and not raise an error on empty selection" do
214
+ rows = @c.fetch("SELECT * FROM items WHERE FALSE")
215
+ @c.class_def(:fetch_rows) {|sql| yield({:count => 0})}
216
+ proc {rows.empty?}.should_not raise_error
217
+ end
218
+
219
+ end
220
+
221
+ describe Sequel::Model, "magic methods" do
222
+
223
+ before(:each) do
224
+ @c = Class.new(Sequel::Dataset) do
225
+ @@sqls = []
226
+
227
+ def self.sqls; @@sqls; end
228
+
229
+ def fetch_rows(sql)
230
+ @@sqls << sql
231
+ yield({:id => 123, :name => 'hey'})
232
+ end
233
+ end
234
+
235
+ @m = Class.new(Sequel::Model(@c.new(nil).from(:items)))
236
+ end
237
+
238
+ it "should support order_by_xxx" do
239
+ @m.order_by_name.should be_a_kind_of(@c)
240
+ @m.order_by_name.sql.should == "SELECT * FROM items ORDER BY name"
241
+ end
242
+
243
+ it "should support group_by_xxx" do
244
+ @m.group_by_name.should be_a_kind_of(@c)
245
+ @m.group_by_name.sql.should == "SELECT * FROM items GROUP BY name"
246
+ end
247
+
248
+ it "should support count_by_xxx" do
249
+ @m.count_by_name.should be_a_kind_of(@c)
250
+ @m.count_by_name.sql.should == "SELECT name, count(name) AS count FROM items GROUP BY name ORDER BY count"
251
+ end
252
+
253
+ it "should support filter_by_xxx" do
254
+ @m.filter_by_name('sharon').should be_a_kind_of(@c)
255
+ @m.filter_by_name('sharon').sql.should == "SELECT * FROM items WHERE (name = 'sharon')"
256
+ end
257
+
258
+ it "should support all_by_xxx" do
259
+ all = @m.all_by_name('sharon')
260
+ all.class.should == Array
261
+ all.size.should == 1
262
+ all.first.should be_a_kind_of(@m)
263
+ all.first.values.should == {:id => 123, :name => 'hey'}
264
+ @c.sqls.should == ["SELECT * FROM items WHERE (name = 'sharon')"]
265
+ end
266
+
267
+ it "should support find_by_xxx" do
268
+ @m.find_by_name('sharon').should be_a_kind_of(@m)
269
+ @m.find_by_name('sharon').values.should == {:id => 123, :name => 'hey'}
270
+ @c.sqls.should == ["SELECT * FROM items WHERE (name = 'sharon') LIMIT 1"] * 2
271
+ end
272
+
273
+ it "should support first_by_xxx" do
274
+ @m.first_by_name('sharon').should be_a_kind_of(@m)
275
+ @m.first_by_name('sharon').values.should == {:id => 123, :name => 'hey'}
276
+ @c.sqls.should == ["SELECT * FROM items ORDER BY name LIMIT 1"] * 2
277
+ end
278
+
279
+ it "should support last_by_xxx" do
280
+ @m.last_by_name('sharon').should be_a_kind_of(@m)
281
+ @m.last_by_name('sharon').values.should == {:id => 123, :name => 'hey'}
282
+ @c.sqls.should == ["SELECT * FROM items ORDER BY name DESC LIMIT 1"] * 2
283
+ end
284
+
285
+ end
286
+
287
+ describe Sequel::Model, ".find_or_create" do
288
+
289
+ before(:each) do
290
+ MODEL_DB.reset
291
+ @c = Class.new(Sequel::Model(:items)) do
292
+ no_primary_key
293
+ end
294
+ end
295
+
296
+ it "should find the record" do
297
+ @c.find_or_create(:x => 1)
298
+ MODEL_DB.sqls.should == ["SELECT * FROM items WHERE (x = 1) LIMIT 1"]
299
+
300
+ MODEL_DB.reset
301
+ end
302
+
303
+ it "should create the record if not found" do
304
+ @c.meta_def(:find) do |*args|
305
+ dataset.filter(*args).first
306
+ nil
307
+ end
308
+
309
+ @c.find_or_create(:x => 1)
310
+ MODEL_DB.sqls.should == [
311
+ "SELECT * FROM items WHERE (x = 1) LIMIT 1",
312
+ "INSERT INTO items (x) VALUES (1)"
313
+ ]
314
+ end
315
+ end
316
+
317
+ describe Sequel::Model, ".delete_all" do
318
+
319
+ before(:each) do
320
+ MODEL_DB.reset
321
+ @c = Class.new(Sequel::Model(:items)) do
322
+ no_primary_key
323
+ end
324
+
325
+ @c.dataset.meta_def(:delete) {MODEL_DB << delete_sql}
326
+ end
327
+
328
+ it "should delete all records in the dataset" do
329
+ @c.delete_all
330
+ MODEL_DB.sqls.should == ["DELETE FROM items"]
331
+ end
332
+
333
+ end
334
+
335
+ describe Sequel::Model, ".destroy_all" do
336
+
337
+ before(:each) do
338
+ MODEL_DB.reset
339
+ @c = Class.new(Sequel::Model(:items)) do
340
+ no_primary_key
341
+ end
342
+
343
+ @c.dataset.meta_def(:delete) {MODEL_DB << delete_sql}
344
+ end
345
+
346
+ it "should delete all records in the dataset" do
347
+ @c.destroy_all
348
+ MODEL_DB.sqls.should == ["DELETE FROM items"]
349
+ end
350
+
351
+ it "should call dataset destroy method if *_destroy hooks exist" do
352
+ @c.dataset.stub!(:destroy).and_return(true)
353
+ @c.should_receive(:has_hooks?).with(:before_destroy).and_return(true)
354
+ @c.destroy_all
355
+ end
356
+
357
+ it "should call dataset delete method if no hooks are present" do
358
+ @c.dataset.stub!(:delete).and_return(true)
359
+ @c.should_receive(:has_hooks?).with(:before_destroy).and_return(false)
360
+ @c.should_receive(:has_hooks?).with(:after_destroy).and_return(false)
361
+ @c.destroy_all
362
+ end
363
+
364
+ end
365
+
366
+ describe Sequel::Model, ".join" do
367
+
368
+ before(:each) do
369
+ MODEL_DB.reset
370
+ @c = Class.new(Sequel::Model(:items)) do
371
+ no_primary_key
372
+ end
373
+ end
374
+
375
+ it "should format proper SQL" do
376
+ @c.join(:atts, :item_id => :id).sql.should == \
377
+ "SELECT items.* FROM items INNER JOIN atts ON (atts.item_id = items.id)"
378
+ end
379
+
380
+ end
381
+
382
+ describe Sequel::Model, ".all" do
383
+
384
+ before(:each) do
385
+ MODEL_DB.reset
386
+ @c = Class.new(Sequel::Model(:items)) do
387
+ no_primary_key
388
+ end
389
+
390
+ @c.dataset.meta_def(:all) {1234}
391
+ end
392
+
393
+ it "should return all records in the dataset" do
394
+ @c.all.should == 1234
395
+ end
396
+
397
+ end
398
+
399
+ class DummyModelBased < Sequel::Model(:blog)
400
+ end
401
+
402
+ describe Sequel::Model, "(:tablename)" do
403
+
404
+ it "should allow reopening of descendant classes" do
405
+ proc do
406
+ eval "class DummyModelBased < Sequel::Model(:blog); end"
407
+ end.should_not raise_error
408
+ end
409
+
410
+ end
411
+
412
+ describe Sequel::Model, ".create" do
413
+
414
+ before(:each) do
415
+ MODEL_DB.reset
416
+ @c = Class.new(Sequel::Model(:items))
417
+ end
418
+
419
+ it "should be able to create rows in the associated table" do
420
+ o = @c.create(:x => 1)
421
+ o.class.should == @c
422
+ MODEL_DB.sqls.should == ['INSERT INTO items (x) VALUES (1)', "SELECT * FROM items WHERE (id IN ('INSERT INTO items (x) VALUES (1)')) LIMIT 1"]
423
+ end
424
+
425
+ it "should be able to create rows without any values specified" do
426
+ o = @c.create
427
+ o.class.should == @c
428
+ MODEL_DB.sqls.should == ["INSERT INTO items DEFAULT VALUES", "SELECT * FROM items WHERE (id IN ('INSERT INTO items DEFAULT VALUES')) LIMIT 1"]
429
+ end
430
+
431
+ end
432
+
433
+ describe Sequel::Model, "A model class without a primary key" do
434
+
435
+ before(:each) do
436
+ MODEL_DB.reset
437
+ @c = Class.new(Sequel::Model(:items)) do
438
+ no_primary_key
439
+ end
440
+ end
441
+
442
+ it "should be able to insert records without selecting them back" do
443
+ i = nil
444
+ proc {i = @c.create(:x => 1)}.should_not raise_error
445
+ i.class.should be(@c)
446
+ i.values.to_hash.should == {:x => 1}
447
+
448
+ MODEL_DB.sqls.should == ['INSERT INTO items (x) VALUES (1)']
449
+ end
450
+
451
+ it "should raise when deleting" do
452
+ o = @c.new
453
+ proc {o.delete}.should raise_error
454
+ end
455
+
456
+ it "should insert a record when saving" do
457
+ o = @c.new(:x => 2)
458
+ o.should be_new
459
+ o.save
460
+ MODEL_DB.sqls.should == ['INSERT INTO items (x) VALUES (2)']
461
+ end
462
+
463
+ end
464
+
465
+ describe Sequel::Model, "attribute accessors" do
466
+
467
+ before(:each) do
468
+ MODEL_DB.reset
469
+
470
+ @c = Class.new(Sequel::Model(:items)) do
471
+ def columns
472
+ [:id, :x, :y]
473
+ end
474
+ end
475
+ end
476
+
477
+ it "should be created dynamically" do
478
+ o = @c.new
479
+
480
+ o.should_not be_respond_to(:x)
481
+ o.x.should be_nil
482
+ o.should be_respond_to(:x)
483
+
484
+ o.should_not be_respond_to(:x=)
485
+ o.x = 34
486
+ o.x.should == 34
487
+ o.should be_respond_to(:x=)
488
+ end
489
+
490
+ it "should raise for a column that doesn't exist in the dataset" do
491
+ o = @c.new
492
+
493
+ proc {o.x}.should_not raise_error
494
+ proc {o.xx}.should raise_error(Sequel::Error)
495
+
496
+ proc {o.x = 3}.should_not raise_error
497
+ proc {o.yy = 4}.should raise_error(Sequel::Error)
498
+
499
+ proc {o.yy?}.should raise_error(NoMethodError)
500
+ end
501
+
502
+ it "should not raise for a column not in the dataset, but for which there's a value" do
503
+ o = @c.new
504
+
505
+ proc {o.xx}.should raise_error(Sequel::Error)
506
+ proc {o.yy}.should raise_error(Sequel::Error)
507
+
508
+ o.values[:xx] = 123
509
+ o.values[:yy] = nil
510
+
511
+ proc {o.xx; o.yy}.should_not raise_error(Sequel::Error)
512
+
513
+ o.xx.should == 123
514
+ o.yy.should == nil
515
+
516
+ proc {o.xx = 3}.should raise_error(Sequel::Error)
517
+ end
518
+
519
+ end
520
+
521
+ describe Sequel::Model, ".[]" do
522
+
523
+ before(:each) do
524
+ MODEL_DB.reset
525
+
526
+ @c = Class.new(Sequel::Model(:items))
527
+
528
+ $cache_dataset_row = {:name => 'sharon', :id => 1}
529
+ @dataset = @c.dataset
530
+ $sqls = []
531
+ @dataset.extend(Module.new {
532
+ def fetch_rows(sql)
533
+ $sqls << sql
534
+ yield $cache_dataset_row
535
+ end
536
+ })
537
+ end
538
+
539
+ it "should return the first record for the given pk" do
540
+ @c[1].should be_a_kind_of(@c)
541
+ $sqls.last.should == "SELECT * FROM items WHERE (id = 1) LIMIT 1"
542
+ @c[9999].should be_a_kind_of(@c)
543
+ $sqls.last.should == "SELECT * FROM items WHERE (id = 9999) LIMIT 1"
544
+ end
545
+
546
+ it "should raise for boolean argument (mistaken comparison)" do
547
+ # This in order to prevent stuff like Model[:a == 'b']
548
+ proc {@c[:a == 1]}.should raise_error(Sequel::Error)
549
+ proc {@c[:a != 1]}.should raise_error(Sequel::Error)
550
+ end
551
+
552
+ it "should work correctly for custom primary key" do
553
+ @c.set_primary_key :name
554
+ @c['sharon'].should be_a_kind_of(@c)
555
+ $sqls.last.should == "SELECT * FROM items WHERE (name = 'sharon') LIMIT 1"
556
+ end
557
+
558
+ it "should work correctly for composite primary key" do
559
+ @c.set_primary_key [:node_id, :kind]
560
+ @c[3921, 201].should be_a_kind_of(@c)
561
+ $sqls.last.should =~ \
562
+ /^SELECT \* FROM items WHERE (\(node_id = 3921\) AND \(kind = 201\))|(\(kind = 201\) AND \(node_id = 3921\)) LIMIT 1$/
563
+ end
564
+ end