sequel 0.2.1.1 → 0.3.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.
@@ -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