sequel 0.2.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,90 +2,154 @@ require File.join(File.dirname(__FILE__), 'spec_helper')
2
2
 
3
3
  Sequel::Model.db = MODEL_DB = MockDatabase.new
4
4
 
5
- context "A model class" do
6
- specify "should be associated with a dataset" do
7
- @m = Class.new(Sequel::Model) do
8
- set_dataset MODEL_DB[:items]
9
- end
5
+ describe Sequel::Model do
6
+
7
+ it "should have class method aliased as model" do
8
+ Sequel::Model.instance_methods.should include('model')
9
+
10
+ model_a = Class.new Sequel::Model
11
+ model_a.new.model.should be(model_a)
12
+ end
13
+
14
+ it "should be associated with a dataset" do
15
+ model_a = Class.new(Sequel::Model) { set_dataset MODEL_DB[:as] }
10
16
 
11
- @m.dataset.should be_a_kind_of(MockDataset)
12
- @m.dataset.opts[:from].should == [:items]
17
+ model_a.dataset.should be_a_kind_of(MockDataset)
18
+ model_a.dataset.opts[:from].should == [:as]
13
19
 
14
- @m2 = Class.new(Sequel::Model) do
15
- set_dataset MODEL_DB[:zzz]
16
- end
20
+ model_b = Class.new(Sequel::Model) { set_dataset MODEL_DB[:bs] }
17
21
 
18
- @m2.dataset.should be_a_kind_of(MockDataset)
19
- @m2.dataset.opts[:from].should == [:zzz]
20
- @m.dataset.opts[:from].should == [:items]
22
+ model_b.dataset.should be_a_kind_of(MockDataset)
23
+ model_b.dataset.opts[:from].should == [:bs]
24
+
25
+ model_a.dataset.opts[:from].should == [:as]
21
26
  end
27
+
22
28
  end
23
29
 
24
- context "A model's primary key" do
25
- specify "should default to id" do
26
- @m = Class.new(Sequel::Model) do
27
- end
28
-
29
- @m.primary_key.should == :id
30
+ describe Sequel::Model, 'w/ primary key' do
31
+
32
+ it "should default to ':id'" do
33
+ model_a = Class.new Sequel::Model
34
+ model_a.primary_key.should be_equal(:id)
30
35
  end
31
-
32
- specify "should be changeable through Model.set_primary_key" do
33
- @m = Class.new(Sequel::Model) do
34
- set_primary_key :xxx
35
- end
36
-
37
- @m.primary_key.should == :xxx
36
+
37
+ it "should be changed through 'set_primary_key'" do
38
+ model_a = Class.new(Sequel::Model) { set_primary_key :a }
39
+ model_a.primary_key.should be_equal(:a)
38
40
  end
39
-
40
- specify "should support composite primary keys" do
41
- @m = Class.new(Sequel::Model) do
42
- set_primary_key [:node_id, :session_id]
43
- end
44
- @m.primary_key.should == [:node_id, :session_id]
41
+
42
+ it "should support multi argument composite keys" do
43
+ model_a = Class.new(Sequel::Model) { set_primary_key :a, :b }
44
+ model_a.primary_key.should be_eql([:a, :b])
45
+ end
46
+
47
+ it "should accept single argument composite keys" do
48
+ model_a = Class.new(Sequel::Model) { set_primary_key [:a, :b] }
49
+ model_a.primary_key.should be_eql([:a, :b])
45
50
  end
51
+
46
52
  end
47
53
 
48
- context "A model without a primary key" do
49
- setup do
50
- @m = Class.new(Sequel::Model) do
51
- no_primary_key
52
- end
54
+ describe Sequel::Model, 'w/o primary key' do
55
+
56
+ it "should return nil for primary key" do
57
+ Class.new(Sequel::Model) { no_primary_key }.primary_key.should be_nil
53
58
  end
54
-
55
- specify "should return nil for primary_key" do
56
- @m.primary_key.should be_nil
59
+
60
+ it "should raise a SequelError on 'this'" do
61
+ instance = Class.new(Sequel::Model) { no_primary_key }.new
62
+ proc { instance.this }.should raise_error(SequelError)
57
63
  end
58
-
59
- specify "should raise on #this" do
60
- o = @m.new
61
- proc {o.this}.should raise_error(SequelError)
64
+
65
+ end
66
+
67
+ describe Sequel::Model, 'with this' do
68
+
69
+ before { @example = Class.new Sequel::Model(:examples) }
70
+
71
+ it "should return a dataset identifying the record" do
72
+ instance = @example.new :id => 3
73
+ instance.this.sql.should be_eql("SELECT * FROM examples WHERE (id = 3) LIMIT 1")
62
74
  end
75
+
76
+ it "should support arbitary primary keys" do
77
+ @example.set_primary_key :a
78
+
79
+ instance = @example.new :a => 3
80
+ instance.this.sql.should be_eql("SELECT * FROM examples WHERE (a = 3) LIMIT 1")
81
+ end
82
+
83
+ it "should support composite primary keys" do
84
+ @example.set_primary_key :x, :y
85
+ instance = @example.new :x => 4, :y => 5
86
+
87
+ parts = ['SELECT * FROM examples WHERE %s LIMIT 1',
88
+ '(x = 4) AND (y = 5)', '(y = 5) AND (x = 4)'
89
+ ].map { |expr| Regexp.escape expr }
90
+ regexp = Regexp.new parts.first % "(?:#{parts[1]}|#{parts[2]})"
91
+
92
+ instance.this.sql.should match(regexp)
93
+ end
94
+
63
95
  end
64
96
 
65
- context "Model#this" do
66
- setup do
67
- @m = Class.new(Sequel::Model(:items)) do
68
- end
97
+ describe Sequel::Model, 'with hooks' do
98
+
99
+ before do
100
+ MODEL_DB.reset
101
+ Sequel::Model.hooks.clear
102
+
103
+ @hooks = %w{
104
+ before_save before_create before_update before_destroy
105
+ after_save after_create after_update after_destroy
106
+ }.select { |hook| !hook.empty? }
69
107
  end
70
-
71
- specify "should return a dataset identifying the record" do
72
- o = @m.new(:id => 3)
73
- o.this.sql.should == "SELECT * FROM items WHERE (id = 3)"
108
+
109
+ it "should have hooks for everything" do
110
+ Sequel::Model.methods.should include('hooks')
111
+ Sequel::Model.methods.should include(*@hooks)
112
+ @hooks.each do |hook|
113
+ Sequel::Model.hooks[hook.to_sym].should be_an_instance_of(Array)
114
+ end
74
115
  end
75
-
76
- specify "should support arbitrary primary keys" do
77
- @m.set_primary_key(:xxx)
78
-
79
- o = @m.new(:xxx => 3)
80
- o.this.sql.should == "SELECT * FROM items WHERE (xxx = 3)"
116
+ it "should be inherited" do
117
+ pending 'soon'
118
+
119
+ @hooks.each do |hook|
120
+ Sequel::Model.send(hook.to_sym) { nil }
121
+ end
122
+
123
+ model = Class.new Sequel::Model(:models)
124
+ model.hooks.should == Sequel::Model.hooks
81
125
  end
82
-
83
- specify "should support composite primary keys" do
84
- @m.set_primary_key [:x, :y]
85
- o = @m.new(:x => 4, :y => 5)
86
126
 
87
- o.this.sql.should =~ /^SELECT \* FROM items WHERE (\(x = 4\) AND \(y = 5\))|(\(y = 5\) AND \(x = 4\))$/
127
+ it "should run hooks" do
128
+ pending 'soon'
129
+
130
+ test = mock 'Test'
131
+ test.should_receive(:run).exactly(@hooks.length)
132
+
133
+ @hooks.each do |hook|
134
+ Sequel::Model.send(hook.to_sym) { test.run }
135
+ end
136
+
137
+ model = Class.new Sequel::Model(:models)
138
+ model.hooks.should == Sequel::Model.hooks
139
+
140
+ model_instance = model.new
141
+ @hooks.each { |hook| model_instance.run_hooks(hook) }
142
+ end
143
+ it "should run hooks around save and create" do
144
+ pending 'test execution'
145
+ end
146
+ it "should run hooks around save and update" do
147
+ pending 'test execution'
88
148
  end
149
+ it "should run hooks around delete" do
150
+ pending 'test execution'
151
+ end
152
+
89
153
  end
90
154
 
91
155
  context "A new model instance" do
@@ -214,7 +278,7 @@ context "A model class without a primary key" do
214
278
  i = nil
215
279
  proc {i = @c.create(:x => 1)}.should_not raise_error
216
280
  i.class.should be(@c)
217
- i.values.should == {:x => 1}
281
+ i.values.to_hash.should == {:x => 1}
218
282
 
219
283
  MODEL_DB.sqls.should == ['INSERT INTO items (x) VALUES (1);']
220
284
  end
@@ -325,6 +389,7 @@ context "Model#serialize" do
325
389
  o.set(:abc => 23)
326
390
  ds.sqls.should == "UPDATE items SET abc = '#{23.to_yaml}' WHERE (id = 1)"
327
391
 
392
+ ds.raw = {:id => 1, :abc => "--- 1\n", :def => "--- hello\n"}
328
393
  o = @c.create(:abc => [1, 2, 3])
329
394
  ds.sqls.should == "INSERT INTO items (abc) VALUES ('#{[1, 2, 3].to_yaml}');"
330
395
  end
@@ -377,9 +442,6 @@ context "Model#new?" do
377
442
  MODEL_DB.reset
378
443
 
379
444
  @c = Class.new(Sequel::Model(:items)) do
380
- def columns
381
- [:id, :x, :y]
382
- end
383
445
  end
384
446
  end
385
447
 
@@ -436,5 +498,267 @@ context "Model.after_create" do
436
498
  end
437
499
  end
438
500
 
439
- context "Model.serialize" do
501
+ context "Model.subset" do
502
+ setup do
503
+ MODEL_DB.reset
504
+
505
+ @c = Class.new(Sequel::Model(:items)) do
506
+ def columns
507
+ [:id, :x, :y]
508
+ end
509
+ end
510
+ end
511
+
512
+ specify "should create a filter on the underlying dataset" do
513
+ proc {@c.new_only}.should raise_error(NoMethodError)
514
+
515
+ @c.subset(:new_only) {:age == 'new'}
516
+
517
+ @c.new_only.sql.should == "SELECT * FROM items WHERE (age = 'new')"
518
+ @c.dataset.new_only.sql.should == "SELECT * FROM items WHERE (age = 'new')"
519
+
520
+ @c.subset(:pricey) {:price > 100}
521
+
522
+ @c.pricey.sql.should == "SELECT * FROM items WHERE (price > 100)"
523
+ @c.dataset.pricey.sql.should == "SELECT * FROM items WHERE (price > 100)"
524
+
525
+ # check if subsets are composable
526
+ @c.pricey.new_only.sql.should == "SELECT * FROM items WHERE (price > 100) AND (age = 'new')"
527
+ @c.new_only.pricey.sql.should == "SELECT * FROM items WHERE (age = 'new') AND (price > 100)"
528
+ end
529
+ end
530
+
531
+ context "Model.find" do
532
+ setup do
533
+ MODEL_DB.reset
534
+
535
+ @c = Class.new(Sequel::Model(:items)) do
536
+ def self.columns
537
+ [:name, :id]
538
+ end
539
+ end
540
+
541
+ $cache_dataset_row = {:name => 'sharon', :id => 1}
542
+ @dataset = @c.dataset
543
+ $sqls = []
544
+ @dataset.extend(Module.new {
545
+ def fetch_rows(sql)
546
+ $sqls << sql
547
+ yield $cache_dataset_row
548
+ end
549
+ })
550
+ end
551
+
552
+ specify "should return the first record matching the given filter" do
553
+ @c.find(:name => 'sharon').should be_a_kind_of(@c)
554
+ $sqls.last.should == "SELECT * FROM items WHERE (name = 'sharon') LIMIT 1"
555
+
556
+ @c.find {"name LIKE 'abc%'".lit}.should be_a_kind_of(@c)
557
+ $sqls.last.should == "SELECT * FROM items WHERE name LIKE 'abc%' LIMIT 1"
558
+ end
559
+
560
+ specify "should accept filter blocks" do
561
+ @c.find {:id == 1}.should be_a_kind_of(@c)
562
+ $sqls.last.should == "SELECT * FROM items WHERE (id = 1) LIMIT 1"
563
+
564
+ @c.find {:x > 1 && :y < 2}.should be_a_kind_of(@c)
565
+ $sqls.last.should == "SELECT * FROM items WHERE ((x > 1) AND (y < 2)) LIMIT 1"
566
+ end
567
+ end
568
+
569
+ context "Model.[]" do
570
+ setup do
571
+ MODEL_DB.reset
572
+
573
+ @c = Class.new(Sequel::Model(:items)) do
574
+ def self.columns
575
+ [:name, :id]
576
+ end
577
+ end
578
+
579
+ $cache_dataset_row = {:name => 'sharon', :id => 1}
580
+ @dataset = @c.dataset
581
+ $sqls = []
582
+ @dataset.extend(Module.new {
583
+ def fetch_rows(sql)
584
+ $sqls << sql
585
+ yield $cache_dataset_row
586
+ end
587
+ })
588
+ end
589
+
590
+ specify "should return the first record for the given pk" do
591
+ @c[1].should be_a_kind_of(@c)
592
+ $sqls.last.should == "SELECT * FROM items WHERE (id = 1) LIMIT 1"
593
+ @c[9999].should be_a_kind_of(@c)
594
+ $sqls.last.should == "SELECT * FROM items WHERE (id = 9999) LIMIT 1"
595
+ end
596
+
597
+ specify "should work correctly for custom primary key" do
598
+ @c.set_primary_key :name
599
+ @c['sharon'].should be_a_kind_of(@c)
600
+ $sqls.last.should == "SELECT * FROM items WHERE (name = 'sharon') LIMIT 1"
601
+ end
602
+
603
+ specify "should work correctly for composite primary key" do
604
+ @c.set_primary_key [:node_id, :kind]
605
+ @c[3921, 201].should be_a_kind_of(@c)
606
+ $sqls.last.should =~ \
607
+ /^SELECT \* FROM items WHERE (\(node_id = 3921\) AND \(kind = 201\))|(\(kind = 201\) AND \(node_id = 3921\)) LIMIT 1$/
608
+ end
609
+
610
+ specify "should act as shortcut to find if a hash is given" do
611
+ @c[:id => 1].should be_a_kind_of(@c)
612
+ $sqls.last.should == "SELECT * FROM items WHERE (id = 1) LIMIT 1"
613
+
614
+ @c[:name => ['abc', 'def']].should be_a_kind_of(@c)
615
+ $sqls.last.should == "SELECT * FROM items WHERE (name IN ('abc', 'def')) LIMIT 1"
616
+ end
617
+ end
618
+
619
+ context "A cached model" do
620
+ setup do
621
+ MODEL_DB.reset
622
+
623
+ @cache_class = Class.new(Hash) do
624
+ attr_accessor :ttl
625
+ def set(k, v, ttl); self[k] = v; @ttl = ttl; end
626
+ def get(k); self[k]; end
627
+ end
628
+ cache = @cache_class.new
629
+ @cache = cache
630
+
631
+ @c = Class.new(Sequel::Model(:items)) do
632
+ set_cache cache
633
+
634
+ def self.columns
635
+ [:name, :id]
636
+ end
637
+ end
638
+
639
+ $cache_dataset_row = {:name => 'sharon', :id => 1}
640
+ @dataset = @c.dataset
641
+ $sqls = []
642
+ @dataset.extend(Module.new {
643
+ def fetch_rows(sql)
644
+ $sqls << sql
645
+ yield $cache_dataset_row
646
+ end
647
+
648
+ def update(values)
649
+ $sqls << update_sql(values)
650
+ $cache_dataset_row.merge!(values)
651
+ end
652
+
653
+ def delete
654
+ $sqls << delete_sql
655
+ end
656
+ })
657
+ end
658
+
659
+ specify "should set the model's cache store" do
660
+ @c.cache_store.should be(@cache)
661
+ end
662
+
663
+ specify "should have a default ttl of 3600" do
664
+ @c.cache_ttl.should == 3600
665
+ end
666
+
667
+ specify "should take a ttl option" do
668
+ @c.set_cache @cache, :ttl => 1234
669
+ @c.cache_ttl.should == 1234
670
+ end
671
+
672
+ specify "should offer a set_cache_ttl method for setting the ttl" do
673
+ @c.cache_ttl.should == 3600
674
+ @c.set_cache_ttl 1234
675
+ @c.cache_ttl.should == 1234
676
+ end
677
+
678
+ specify "should generate a cache key appropriate to the class" do
679
+ m = @c.new
680
+ m.values[:id] = 1
681
+ m.cache_key.should == "#{m.class}:1"
682
+
683
+ # custom primary key
684
+ @c.set_primary_key :ttt
685
+ m = @c.new
686
+ m.values[:ttt] = 333
687
+ m.cache_key.should == "#{m.class}:333"
688
+
689
+ # composite primary key
690
+ @c.set_primary_key [:a, :b, :c]
691
+ m = @c.new
692
+ m.values[:a] = 123
693
+ m.values[:c] = 456
694
+ m.values[:b] = 789
695
+ m.cache_key.should == "#{m.class}:123,789,456"
696
+ end
697
+
698
+ specify "should raise error if attempting to generate cache_key and primary key value is null" do
699
+ m = @c.new
700
+ proc {m.cache_key}.should raise_error(SequelError)
701
+
702
+ m.values[:id] = 1
703
+ proc {m.cache_key}.should_not raise_error(SequelError)
704
+ end
705
+
706
+ specify "should set the cache when reading from the database" do
707
+ $sqls.should == []
708
+ @cache.should be_empty
709
+
710
+ m = @c[1]
711
+ $sqls.should == ['SELECT * FROM items WHERE (id = 1) LIMIT 1']
712
+ m.values.should == $cache_dataset_row
713
+ @cache[m.cache_key].should == m
714
+
715
+ # read from cache
716
+ m2 = @c[1]
717
+ $sqls.should == ['SELECT * FROM items WHERE (id = 1) LIMIT 1']
718
+ m2.should == m
719
+ m2.values.should == $cache_dataset_row
720
+ end
721
+
722
+ specify "should delete the cache when writing to the database" do
723
+ # fill the cache
724
+ m = @c[1]
725
+ @cache[m.cache_key].should == m
726
+
727
+ m.set(:name => 'tutu')
728
+ @cache.has_key?(m.cache_key).should be_false
729
+ $sqls.last.should == "UPDATE items SET name = 'tutu' WHERE (id = 1)"
730
+
731
+ m = @c[1]
732
+ @cache[m.cache_key].should == m
733
+ m.name = 'hey'
734
+ m.save
735
+ @cache.has_key?(m.cache_key).should be_false
736
+ $sqls.last.should == "UPDATE items SET name = 'hey', id = 1 WHERE (id = 1)"
737
+ end
738
+
739
+ specify "should delete the cache when deleting the record" do
740
+ # fill the cache
741
+ m = @c[1]
742
+ @cache[m.cache_key].should == m
743
+
744
+ m.delete
745
+ @cache.has_key?(m.cache_key).should be_false
746
+ $sqls.last.should == "DELETE FROM items WHERE (id = 1)"
747
+ end
748
+
749
+ specify "should support #[] as a shortcut to #find with hash" do
750
+ m = @c[:id => 3]
751
+ @cache[m.cache_key].should be_nil
752
+ $sqls.last.should == "SELECT * FROM items WHERE (id = 3) LIMIT 1"
753
+
754
+ m = @c[1]
755
+ @cache[m.cache_key].should == m
756
+ $sqls.should == ["SELECT * FROM items WHERE (id = 3) LIMIT 1", \
757
+ "SELECT * FROM items WHERE (id = 1) LIMIT 1"]
758
+
759
+ @c[:id => 4]
760
+ $sqls.should == ["SELECT * FROM items WHERE (id = 3) LIMIT 1", \
761
+ "SELECT * FROM items WHERE (id = 1) LIMIT 1", \
762
+ "SELECT * FROM items WHERE (id = 4) LIMIT 1"]
763
+ end
440
764
  end