sequel 4.8.0 → 4.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +48 -0
  3. data/doc/association_basics.rdoc +1 -1
  4. data/doc/opening_databases.rdoc +4 -0
  5. data/doc/postgresql.rdoc +27 -3
  6. data/doc/release_notes/4.9.0.txt +190 -0
  7. data/doc/security.rdoc +1 -1
  8. data/doc/testing.rdoc +2 -2
  9. data/doc/validations.rdoc +8 -0
  10. data/lib/sequel/adapters/jdbc.rb +5 -3
  11. data/lib/sequel/adapters/jdbc/derby.rb +2 -8
  12. data/lib/sequel/adapters/jdbc/h2.rb +2 -13
  13. data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -16
  14. data/lib/sequel/adapters/mysql2.rb +11 -1
  15. data/lib/sequel/adapters/postgres.rb +33 -10
  16. data/lib/sequel/adapters/shared/db2.rb +2 -10
  17. data/lib/sequel/adapters/shared/mssql.rb +10 -8
  18. data/lib/sequel/adapters/shared/oracle.rb +9 -24
  19. data/lib/sequel/adapters/shared/postgres.rb +32 -9
  20. data/lib/sequel/adapters/shared/sqlanywhere.rb +2 -4
  21. data/lib/sequel/adapters/shared/sqlite.rb +4 -7
  22. data/lib/sequel/database/schema_methods.rb +15 -0
  23. data/lib/sequel/dataset.rb +1 -1
  24. data/lib/sequel/dataset/actions.rb +159 -27
  25. data/lib/sequel/dataset/graph.rb +29 -7
  26. data/lib/sequel/dataset/misc.rb +6 -0
  27. data/lib/sequel/dataset/placeholder_literalizer.rb +164 -0
  28. data/lib/sequel/dataset/query.rb +2 -0
  29. data/lib/sequel/dataset/sql.rb +103 -91
  30. data/lib/sequel/extensions/current_datetime_timestamp.rb +57 -0
  31. data/lib/sequel/extensions/pg_array.rb +68 -106
  32. data/lib/sequel/extensions/pg_hstore.rb +5 -5
  33. data/lib/sequel/extensions/schema_dumper.rb +49 -49
  34. data/lib/sequel/model.rb +4 -2
  35. data/lib/sequel/model/associations.rb +1 -1
  36. data/lib/sequel/model/base.rb +136 -3
  37. data/lib/sequel/model/errors.rb +6 -0
  38. data/lib/sequel/plugins/defaults_setter.rb +1 -1
  39. data/lib/sequel/plugins/eager_each.rb +9 -0
  40. data/lib/sequel/plugins/nested_attributes.rb +2 -2
  41. data/lib/sequel/plugins/timestamps.rb +2 -2
  42. data/lib/sequel/plugins/touch.rb +2 -2
  43. data/lib/sequel/sql.rb +20 -15
  44. data/lib/sequel/version.rb +1 -1
  45. data/spec/adapters/postgres_spec.rb +70 -8
  46. data/spec/core/dataset_spec.rb +172 -27
  47. data/spec/core/expression_filters_spec.rb +3 -3
  48. data/spec/core/object_graph_spec.rb +17 -1
  49. data/spec/core/placeholder_literalizer_spec.rb +128 -0
  50. data/spec/core/schema_spec.rb +54 -0
  51. data/spec/extensions/current_datetime_timestamp_spec.rb +27 -0
  52. data/spec/extensions/defaults_setter_spec.rb +12 -0
  53. data/spec/extensions/eager_each_spec.rb +6 -0
  54. data/spec/extensions/nested_attributes_spec.rb +14 -2
  55. data/spec/extensions/pg_array_spec.rb +15 -7
  56. data/spec/extensions/shared_caching_spec.rb +5 -5
  57. data/spec/extensions/timestamps_spec.rb +9 -0
  58. data/spec/extensions/touch_spec.rb +9 -0
  59. data/spec/integration/database_test.rb +1 -1
  60. data/spec/integration/dataset_test.rb +27 -5
  61. data/spec/model/eager_loading_spec.rb +32 -0
  62. data/spec/model/model_spec.rb +119 -9
  63. metadata +8 -2
@@ -0,0 +1,27 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper')
2
+
3
+ describe "current_datetime_timestamp extension" do
4
+ before do
5
+ @ds = Sequel.mock[:table].extension(:current_datetime_timestamp)
6
+ end
7
+ after do
8
+ Sequel.datetime_class = Time
9
+ end
10
+
11
+ specify "should have current_timestamp respect Sequel.datetime_class" do
12
+ t = Sequel::Dataset.new(nil).current_datetime
13
+ t.should be_a_kind_of(Time)
14
+ (Time.now - t < 0.1).should == true
15
+
16
+ Sequel.datetime_class = DateTime
17
+ t = Sequel::Dataset.new(nil).current_datetime
18
+ t.should be_a_kind_of(DateTime)
19
+ (DateTime.now - t < (0.1/86400)).should == true
20
+ end
21
+
22
+ specify "should have current_timestamp value be literalized as CURRENT_TIMESTAMP" do
23
+ @ds.literal(@ds.current_datetime).should == 'CURRENT_TIMESTAMP'
24
+ Sequel.datetime_class = DateTime
25
+ @ds.literal(@ds.current_datetime).should == 'CURRENT_TIMESTAMP'
26
+ end
27
+ end
@@ -47,6 +47,18 @@ describe "Sequel::Plugins::DefaultsSetter" do
47
47
  (t - DateTime.now).should < 1/86400.0
48
48
  end
49
49
 
50
+ it "should work correctly with the current_datetime_timestamp extension" do
51
+ @db.autoid = 1
52
+ @db.fetch = {:id=>1}
53
+ @c.dataset = @c.dataset.extension(:current_datetime_timestamp)
54
+ c = @pr.call(Sequel::CURRENT_TIMESTAMP)
55
+ @db.sqls
56
+ o = c.new
57
+ o.a = o.a
58
+ o.save
59
+ @db.sqls.should == ["INSERT INTO foo (a) VALUES (CURRENT_TIMESTAMP)", "SELECT * FROM foo WHERE (id = 1) LIMIT 1"]
60
+ end
61
+
50
62
  it "should not override a given value" do
51
63
  @pr.call(2)
52
64
  @c.new('a'=>3).a.should == 3
@@ -33,4 +33,10 @@ describe "Sequel::Plugins::EagerEach" do
33
33
  a.map{|c| c.associations[:children]}.should == [[@c.load(:id=>3, :parent_id=>1), @c.load(:id=>4, :parent_id=>1)], [@c.load(:id=>5, :parent_id=>2), @c.load(:id=>6, :parent_id=>2)]]
34
34
  @c.db.sqls.should == ['SELECT items.id, items.parent_id, children.id AS children_id, children.parent_id AS children_parent_id FROM items LEFT OUTER JOIN items AS children ON (children.parent_id = items.id)']
35
35
  end
36
+
37
+ it "should not attempt to eager load when getting the columns" do
38
+ ds = @c.eager(:children)
39
+ def ds.all; raise; end
40
+ proc{ds.columns!}.should_not raise_error
41
+ end
36
42
  end
@@ -5,7 +5,7 @@ describe "NestedAttributes plugin" do
5
5
  if should.is_a?(Array)
6
6
  should.should include(is)
7
7
  else
8
- should.should == is
8
+ is.should == should
9
9
  end
10
10
  end
11
11
 
@@ -94,7 +94,19 @@ describe "NestedAttributes plugin" do
94
94
  "UPDATE albums SET artist_id = NULL WHERE (artist_id = 1)",
95
95
  ["INSERT INTO albums (artist_id, name) VALUES (1, 'Al')", "INSERT INTO albums (name, artist_id) VALUES ('Al', 1)"])
96
96
  end
97
-
97
+
98
+ it "should should not remove existing values from object when validating" do
99
+ @Artist.one_to_one :first_album, :class=>@Album, :key=>:id
100
+ @Artist.nested_attributes :first_album
101
+ @db.fetch = {:id=>1}
102
+ a = @Artist.load(:id=>1)
103
+ a.set(:first_album_attributes=>{:id=>1, :name=>'Ar'})
104
+ a.first_album.values.should == {:id=>1, :name=>'Ar'}
105
+ @db.sqls.should == ["SELECT * FROM albums WHERE (albums.id = 1) LIMIT 1"]
106
+ a.save_changes
107
+ check_sql_array("UPDATE albums SET name = 'Ar' WHERE (id = 1)")
108
+ end
109
+
98
110
  it "should support creating new many_to_many objects" do
99
111
  a = @Album.new({:name=>'Al', :tags_attributes=>[{:name=>'T'}]})
100
112
  @db.sqls.should == []
@@ -24,6 +24,9 @@ describe "pg_array extension" do
24
24
  c = @converter[1009]
25
25
  c.call("{a}").to_a.first.should be_a_kind_of(String)
26
26
  c.call("{}").to_a.should == []
27
+ c.call('{""}').to_a.should == [""]
28
+ c.call('{"",""}').to_a.should == ["",""]
29
+ c.call('{"","",""}').to_a.should == ["","",""]
27
30
  c.call("{a}").to_a.should == ['a']
28
31
  c.call('{"a b"}').to_a.should == ['a b']
29
32
  c.call('{a,b}').to_a.should == ['a', 'b']
@@ -126,6 +129,17 @@ describe "pg_array extension" do
126
129
  c.call('{NULLA,"NULL",NULL}').to_a.should == ["NULLA", "NULL", nil]
127
130
  end
128
131
 
132
+ it "should raise errors when for certain recognized invalid arrays" do
133
+ c = @converter[1009]
134
+ proc{c.call('')}.should raise_error(Sequel::Error)
135
+ proc{c.call('}')}.should raise_error(Sequel::Error)
136
+ proc{c.call('{{}')}.should raise_error(Sequel::Error)
137
+ proc{c.call('{}}')}.should raise_error(Sequel::Error)
138
+ proc{c.call('{a""}')}.should raise_error(Sequel::Error)
139
+ proc{c.call('{a{}}')}.should raise_error(Sequel::Error)
140
+ proc{c.call('{""a}')}.should raise_error(Sequel::Error)
141
+ end
142
+
129
143
  it "should literalize arrays without types correctly" do
130
144
  @db.literal(@m::PGArray.new([])).should == 'ARRAY[]'
131
145
  @db.literal(@m::PGArray.new([1])).should == 'ARRAY[1]'
@@ -189,7 +203,7 @@ describe "pg_array extension" do
189
203
 
190
204
  it "should parse array types from the schema correctly" do
191
205
  @db.fetch = [{:name=>'id', :db_type=>'integer'}, {:name=>'i', :db_type=>'integer[]'}, {:name=>'f', :db_type=>'real[]'}, {:name=>'d', :db_type=>'numeric[]'}, {:name=>'t', :db_type=>'text[]'}]
192
- @db.schema(:items).map{|e| e[1][:type]}.should == [:integer, :integer_array, :float_array, :decimal_array, :string_array]
206
+ @db.schema(:items).map{|e| e[1][:type]}.should == [:integer, :integer_array, :real_array, :decimal_array, :string_array]
193
207
  end
194
208
 
195
209
  it "should support typecasting of the various array types" do
@@ -317,12 +331,6 @@ describe "pg_array extension" do
317
331
  @db.literal(Sequel::Postgres::PG_TYPES[3].call('{}')).should == "'{}'::blah[]"
318
332
  end
319
333
 
320
- it "should use and not override existing database typecast method if :typecast_method option is given" do
321
- Sequel::Postgres::PGArray.register('foo', :typecast_method=>:float)
322
- @db.fetch = [{:name=>'id', :db_type=>'foo[]'}]
323
- @db.schema(:items).map{|e| e[1][:type]}.should == [:float_array]
324
- end
325
-
326
334
  it "should support registering custom array types on a per-Database basis" do
327
335
  @db.register_array_type('banana', :oid=>7865){|s| s}
328
336
  @db.typecast_value(:banana_array, []).should be_a_kind_of(Sequel::Postgres::PGArray)
@@ -27,7 +27,7 @@ describe "Shared caching behavior" do
27
27
  @c.load(:id=>3, :caching_model_id=>1, :caching_model_id2=>2).caching_model2.should equal(@cm12)
28
28
  @c.load(:id=>3, :caching_model_id=>2, :caching_model_id2=>1).caching_model2.should equal(@cm21)
29
29
  @db.sqls.should == []
30
- @cc.dataset._fetch = []
30
+ @db.fetch = []
31
31
  @c.load(:id=>4, :caching_model_id=>2, :caching_model_id2=>2).caching_model2.should == nil
32
32
  end
33
33
  end
@@ -39,7 +39,7 @@ describe "Shared caching behavior" do
39
39
  @c.load(:id=>3, :caching_model_id=>1).caching_model.should equal(@cm1)
40
40
  @c.load(:id=>4, :caching_model_id=>2).caching_model.should equal(@cm2)
41
41
  @db.sqls.should == []
42
- @cc.dataset._fetch = []
42
+ @db.fetch = []
43
43
  @c.load(:id=>4, :caching_model_id=>3).caching_model.should == nil
44
44
  end
45
45
 
@@ -128,7 +128,7 @@ describe "Shared caching behavior" do
128
128
  @cache = cache
129
129
 
130
130
  @cc.plugin :caching, @cache
131
- @cc.dataset._fetch = {:id=>1}
131
+ @db.fetch = {:id=>1}
132
132
  @cm1 = @cc[1]
133
133
  @cm2 = @cc[2]
134
134
  @cm12 = @cc[1, 2]
@@ -143,7 +143,7 @@ describe "Shared caching behavior" do
143
143
 
144
144
  describe "With static_cache plugin with single key" do
145
145
  before do
146
- @cc.dataset._fetch = [{:id=>1}, {:id=>2}]
146
+ @db.fetch = [{:id=>1}, {:id=>2}]
147
147
  @cc.plugin :static_cache
148
148
  @cm1 = @cc[1]
149
149
  @cm2 = @cc[2]
@@ -163,7 +163,7 @@ describe "Shared caching behavior" do
163
163
  describe "With static_cache plugin with composite key" do
164
164
  before do
165
165
  @cc.set_primary_key([:id, :id2])
166
- @cc.dataset._fetch = [{:id=>1, :id2=>2}, {:id=>2, :id2=>1}]
166
+ @db.fetch = [{:id=>1, :id2=>2}, {:id=>2, :id2=>1}]
167
167
  @cc.plugin :static_cache
168
168
  @cm12 = @cc[[1, 2]]
169
169
  @cm21 = @cc[[2, 1]]
@@ -34,6 +34,15 @@ describe "Sequel::Plugins::Timestamps" do
34
34
  o.updated_at.should == '2009-08-01'
35
35
  end
36
36
 
37
+ it "should work with current_datetime_timestamp extension" do
38
+ Sequel.datetime_class = Time
39
+ @c.dataset = @c.dataset.extension(:current_datetime_timestamp)
40
+ o = @c.create
41
+ @c.db.sqls.should == ["INSERT INTO t (created_at) VALUES (CURRENT_TIMESTAMP)"]
42
+ o = @c.load(:id=>1).save
43
+ @c.db.sqls.should == ["UPDATE t SET updated_at = CURRENT_TIMESTAMP WHERE (id = 1)"]
44
+ end
45
+
37
46
  it "should not update the update timestamp on creation" do
38
47
  @c.create.updated_at.should == nil
39
48
  end
@@ -26,6 +26,15 @@ describe "Touch plugin" do
26
26
  DB.sqls.first.should =~ /UPDATE a SET updated_at = '[-0-9 :.]+' WHERE \(id = 1\)/
27
27
  end
28
28
 
29
+ specify "should work with current_datetime_timestamp extension" do
30
+ c = Class.new(Sequel::Model).set_dataset(:a)
31
+ c.dataset = c.dataset.extension(:current_datetime_timestamp)
32
+ c.plugin :touch
33
+ c.columns :id, :updated_at
34
+ c.load(:id=>1).touch
35
+ DB.sqls.should == ["UPDATE a SET updated_at = CURRENT_TIMESTAMP WHERE (id = 1)"]
36
+ end
37
+
29
38
  specify "should allow #touch instance method for updating the updated_at column" do
30
39
  @Artist.plugin :touch
31
40
  @a.touch
@@ -53,7 +53,7 @@ describe Sequel::Database do
53
53
  proc{@db[:test].update(:a=>'1', :b=>'2')}.should raise_error(Sequel::UniqueConstraintViolation)
54
54
  end
55
55
 
56
- cspecify "should raise Sequel::CheckConstraintViolation when a check constraint is violated", :mysql, :sqlite, [:db2] do
56
+ cspecify "should raise Sequel::CheckConstraintViolation when a check constraint is violated", :mysql, [:db2], [proc{|db| db.sqlite_version < 30802}, :sqlite] do
57
57
  @db.create_table!(:test){String :a; check Sequel.~(:a=>'1')}
58
58
  proc{@db[:test].insert('1')}.should raise_error(Sequel::CheckConstraintViolation)
59
59
  @db[:test].insert('2')
@@ -124,17 +124,39 @@ describe "Simple Dataset operations" do
124
124
  specify "should support iterating over large numbers of records with paged_each" do
125
125
  (2..100).each{|i| @ds.insert(:number=>i*10)}
126
126
 
127
+ [:offset, :filter].each do |strategy|
128
+ rows = []
129
+ @ds.order(:number).paged_each(:rows_per_fetch=>5, :strategy=>strategy){|row| rows << row}
130
+ rows.should == (1..100).map{|i| {:id=>i, :number=>i*10}}
131
+
132
+ rows = []
133
+ @ds.order(:number).paged_each(:rows_per_fetch=>3, :strategy=>strategy){|row| rows << row}
134
+ rows.should == (1..100).map{|i| {:id=>i, :number=>i*10}}
135
+
136
+ rows = []
137
+ @ds.order(:number, :id).paged_each(:rows_per_fetch=>5, :strategy=>strategy){|row| rows << row}
138
+ rows.should == (1..100).map{|i| {:id=>i, :number=>i*10}}
139
+
140
+ rows = []
141
+ @ds.reverse_order(:number).paged_each(:rows_per_fetch=>5, :strategy=>strategy){|row| rows << row}
142
+ rows.should == (1..100).map{|i| {:id=>i, :number=>i*10}}.reverse
143
+
144
+ rows = []
145
+ @ds.order(Sequel.desc(:number), :id).paged_each(:rows_per_fetch=>5, :strategy=>strategy){|row| rows << row}
146
+ rows.should == (1..100).map{|i| {:id=>i, :number=>i*10}}.reverse
147
+ end
148
+
127
149
  rows = []
128
- @ds.order(:number).paged_each(:rows_per_fetch=>5){|row| rows << row}
129
- rows.should == (1..100).map{|i| {:id=>i, :number=>i*10}}
150
+ @ds.order(:number).limit(50, 25).paged_each(:rows_per_fetch=>3){|row| rows << row}
151
+ rows.should == (26..75).map{|i| {:id=>i, :number=>i*10}}
130
152
 
131
153
  rows = []
132
- @ds.order(:number).paged_each(:rows_per_fetch=>3){|row| rows << row}
154
+ @ds.order(Sequel.*(:number, 2)).paged_each(:rows_per_fetch=>5){|row| rows << row}
133
155
  rows.should == (1..100).map{|i| {:id=>i, :number=>i*10}}
134
156
 
135
157
  rows = []
136
- @ds.order(:number).limit(50, 25).paged_each(:rows_per_fetch=>3){|row| rows << row}
137
- rows.should == (26..75).map{|i| {:id=>i, :number=>i*10}}
158
+ @ds.order(Sequel.*(:number, 2)).paged_each(:rows_per_fetch=>5, :strategy=>:filter, :filter_values=>proc{|row, _| [row[:number] * 2]}){|row| rows << row}
159
+ rows.should == (1..100).map{|i| {:id=>i, :number=>i*10}}
138
160
  end
139
161
 
140
162
  specify "should fetch all results correctly" do
@@ -1725,6 +1725,8 @@ describe Sequel::Model, "#eager_graph" do
1725
1725
  c1.many_to_many :a_genres, :class=>c2, :left_primary_key=>:id, :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:s__ag
1726
1726
  ds = c1.join(:s__t, [:b_id]).eager_graph(:a_genres)
1727
1727
  ds.sql.should == 'SELECT a.id, a_genres.id AS a_genres_id FROM (SELECT * FROM s.a INNER JOIN s.t USING (b_id)) AS a LEFT OUTER JOIN s.ag AS ag ON (ag.album_id = a.id) LEFT OUTER JOIN s.g AS a_genres ON (a_genres.id = ag.genre_id)'
1728
+ ds = c1.eager_graph(:a_genres)
1729
+ ds.sql.should == 'SELECT s.a.id, a_genres.id AS a_genres_id FROM s.a LEFT OUTER JOIN s.ag AS ag ON (ag.album_id = s.a.id) LEFT OUTER JOIN s.g AS a_genres ON (a_genres.id = ag.genre_id)'
1728
1730
  end
1729
1731
 
1730
1732
  it "should respect :after_load callbacks on associations when eager graphing" do
@@ -1868,3 +1870,33 @@ describe Sequel::Model, "#eager_graph" do
1868
1870
  a.album.tracks.should == [GraphTrack.load(:id => 3, :album_id => 1)]
1869
1871
  end
1870
1872
  end
1873
+
1874
+ describe "Sequel::Models with double underscores in table names" do
1875
+ before do
1876
+ @db = Sequel.mock(:fetch=>{:id=>1, :foo_id=>2})
1877
+ @Foo = Class.new(Sequel::Model(@db[Sequel.identifier(:fo__os)]))
1878
+ @Foo.columns :id, :foo_id
1879
+ @Foo.one_to_many :foos, :class=>@Foo
1880
+ @db.sqls
1881
+ end
1882
+
1883
+ it "should have working eager_graph implementations" do
1884
+ @db.fetch = {:id=>1, :foo_id=>1, :foos_id=>1, :foos_foo_id=>1}
1885
+ foos = @Foo.eager_graph(:foos).all
1886
+ @db.sqls.should == ["SELECT fo__os.id, fo__os.foo_id, foos.id AS foos_id, foos.foo_id AS foos_foo_id FROM fo__os LEFT OUTER JOIN (SELECT * FROM fo__os) AS foos ON (foos._id = fo__os.id)"]
1887
+ foos.should == [@Foo.load(:id=>1, :foo_id=>1)]
1888
+ foos.first.foos.should == [@Foo.load(:id=>1, :foo_id=>1)]
1889
+ end
1890
+
1891
+ it "should have working eager_graph implementations when qualified" do
1892
+ @Foo.dataset = Sequel.identifier(:fo__os).qualify(:s)
1893
+ @Foo.columns :id, :foo_id
1894
+ @db.sqls
1895
+ @db.fetch = {:id=>1, :foo_id=>1, :foos_id=>1, :foos_foo_id=>1}
1896
+ foos = @Foo.eager_graph(:foos).all
1897
+ @db.sqls.should == ["SELECT s.fo__os.id, s.fo__os.foo_id, foos.id AS foos_id, foos.foo_id AS foos_foo_id FROM s.fo__os LEFT OUTER JOIN (SELECT * FROM s.fo__os) AS foos ON (foos._id = s.fo__os.id)"]
1898
+ foos.should == [@Foo.load(:id=>1, :foo_id=>1)]
1899
+ foos.first.foos.should == [@Foo.load(:id=>1, :foo_id=>1)]
1900
+ end
1901
+ end
1902
+
@@ -418,6 +418,113 @@ describe Sequel::Model, ".find" do
418
418
  end
419
419
  end
420
420
 
421
+ describe Sequel::Model, ".finder" do
422
+ before do
423
+ @h = {:id=>1}
424
+ @db = Sequel.mock(:fetch=>@h)
425
+ @c = Class.new(Sequel::Model(@db[:items]))
426
+ @c.instance_eval do
427
+ def foo(a, b)
428
+ where(:bar=>a).order(b)
429
+ end
430
+ end
431
+ @o = @c.load(@h)
432
+ @db.sqls
433
+ end
434
+
435
+ specify "should create a method that calls the method given and returns the first instance" do
436
+ @c.finder :foo
437
+ @c.first_foo(1, 2).should == @o
438
+ @c.first_foo(3, 4).should == @o
439
+ @db.sqls.should == ["SELECT * FROM items WHERE (bar = 1) ORDER BY 2 LIMIT 1", "SELECT * FROM items WHERE (bar = 3) ORDER BY 4 LIMIT 1"]
440
+ end
441
+
442
+ specify "should work correctly when subclassing" do
443
+ @c.finder(:foo)
444
+ @sc = Class.new(@c)
445
+ @sc.set_dataset :foos
446
+ @db.sqls
447
+ @sc.first_foo(1, 2).should == @sc.load(@h)
448
+ @sc.first_foo(3, 4).should == @sc.load(@h)
449
+ @db.sqls.should == ["SELECT * FROM foos WHERE (bar = 1) ORDER BY 2 LIMIT 1", "SELECT * FROM foos WHERE (bar = 3) ORDER BY 4 LIMIT 1"]
450
+ end
451
+
452
+ specify "should work correctly when dataset is modified" do
453
+ @c.finder(:foo)
454
+ @c.first_foo(1, 2).should == @o
455
+ @c.set_dataset :foos
456
+ @c.first_foo(3, 4).should == @o
457
+ @db.sqls.should == ["SELECT * FROM items WHERE (bar = 1) ORDER BY 2 LIMIT 1", "SELECT * FROM foos LIMIT 1", "SELECT * FROM foos WHERE (bar = 3) ORDER BY 4 LIMIT 1"]
458
+ end
459
+
460
+ specify "should create a method based on the given block if no method symbol provided" do
461
+ @c.finder(:name=>:first_foo){|pl, ds| ds.where(pl.arg).limit(1)}
462
+ @c.first_foo(:id=>1).should == @o
463
+ @db.sqls.should == ["SELECT * FROM items WHERE (id = 1) LIMIT 1"]
464
+ end
465
+
466
+ specify "should raise an error if both a block and method symbol given" do
467
+ proc{@c.finder(:foo, :name=>:first_foo){|pl, ds| ds.where(pl.arg)}}.should raise_error(Sequel::Error)
468
+ end
469
+
470
+ specify "should raise an error if two option hashes are provided" do
471
+ proc{@c.finder({:name2=>:foo}, :name=>:first_foo){|pl, ds| ds.where(pl.arg)}}.should raise_error(Sequel::Error)
472
+ end
473
+
474
+ specify "should support :type option" do
475
+ @c.finder :foo, :type=>:all
476
+ @c.finder :foo, :type=>:each
477
+ @c.finder :foo, :type=>:get
478
+
479
+ a = []
480
+ @c.all_foo(1, 2){|r| a << r}.should == [@o]
481
+ a.should == [@o]
482
+
483
+ a = []
484
+ @c.each_foo(3, 4){|r| a << r}
485
+ a.should == [@o]
486
+
487
+ @c.get_foo(5, 6).should == [:id, 1]
488
+
489
+ @db.sqls.should == ["SELECT * FROM items WHERE (bar = 1) ORDER BY 2", "SELECT * FROM items WHERE (bar = 3) ORDER BY 4", "SELECT * FROM items WHERE (bar = 5) ORDER BY 6 LIMIT 1"]
490
+ end
491
+
492
+ specify "should support :name option" do
493
+ @c.finder :foo, :name=>:find_foo
494
+ @c.find_foo(1, 2).should == @o
495
+ @c.find_foo(3, 4).should == @o
496
+ @db.sqls.should == ["SELECT * FROM items WHERE (bar = 1) ORDER BY 2 LIMIT 1", "SELECT * FROM items WHERE (bar = 3) ORDER BY 4 LIMIT 1"]
497
+ end
498
+
499
+ specify "should support :arity option" do
500
+ def @c.foobar(*b)
501
+ ds = dataset
502
+ b.each_with_index do |a, i|
503
+ ds = ds.where(i=>a)
504
+ end
505
+ ds
506
+ end
507
+ @c.finder :foobar, :arity=>1, :name=>:find_foobar_1
508
+ @c.finder :foobar, :arity=>2, :name=>:find_foobar_2
509
+ @c.find_foobar_1(:a)
510
+ @c.find_foobar_2(:a, :b)
511
+ @db.sqls.should == ["SELECT * FROM items WHERE (0 = a) LIMIT 1", "SELECT * FROM items WHERE ((0 = a) AND (1 = b)) LIMIT 1"]
512
+ end
513
+
514
+ specify "should support :mod option" do
515
+ m = Module.new
516
+ @c.finder :foo, :mod=>m
517
+ proc{@c.first_foo}.should raise_error
518
+ @c.extend m
519
+ @c.first_foo(1, 2).should == @o
520
+ @c.first_foo(3, 4).should == @o
521
+ @db.sqls.should == ["SELECT * FROM items WHERE (bar = 1) ORDER BY 2 LIMIT 1", "SELECT * FROM items WHERE (bar = 3) ORDER BY 4 LIMIT 1"]
522
+ end
523
+
524
+ specify "should raise error when callign with the wrong arity" do
525
+ end
526
+ end
527
+
421
528
  describe Sequel::Model, ".fetch" do
422
529
  before do
423
530
  DB.reset
@@ -437,32 +544,35 @@ end
437
544
 
438
545
  describe Sequel::Model, ".find_or_create" do
439
546
  before do
440
- @c = Class.new(Sequel::Model(:items)) do
547
+ @db = Sequel.mock
548
+ @c = Class.new(Sequel::Model(@db[:items])) do
441
549
  set_primary_key :id
442
550
  columns :x
443
551
  end
444
- DB.reset
552
+ @db.sqls
445
553
  end
446
554
 
447
555
  it "should find the record" do
556
+ @db.fetch = [{:x=>1, :id=>1}]
557
+ @db.autoid = 1
448
558
  @c.find_or_create(:x => 1).should == @c.load(:x=>1, :id=>1)
449
- DB.sqls.should == ["SELECT * FROM items WHERE (x = 1) LIMIT 1"]
559
+ @db.sqls.should == ["SELECT * FROM items WHERE (x = 1) LIMIT 1"]
450
560
  end
451
561
 
452
562
  it "should create the record if not found" do
453
- @c.instance_dataset._fetch = @c.dataset._fetch = [[], {:x=>1, :id=>1}]
454
- @c.instance_dataset.autoid = @c.dataset.autoid = 1
563
+ @db.fetch = [[], {:x=>1, :id=>1}]
564
+ @db.autoid = 1
455
565
  @c.find_or_create(:x => 1).should == @c.load(:x=>1, :id=>1)
456
- DB.sqls.should == ["SELECT * FROM items WHERE (x = 1) LIMIT 1",
566
+ @db.sqls.should == ["SELECT * FROM items WHERE (x = 1) LIMIT 1",
457
567
  "INSERT INTO items (x) VALUES (1)",
458
568
  "SELECT * FROM items WHERE (id = 1) LIMIT 1"]
459
569
  end
460
570
 
461
571
  it "should pass the new record to be created to the block if no record is found" do
462
- @c.instance_dataset._fetch = @c.dataset._fetch = [[], {:x=>1, :id=>1}]
463
- @c.instance_dataset.autoid = @c.dataset.autoid = 1
572
+ @db.fetch = [[], {:x=>1, :id=>1}]
573
+ @db.autoid = 1
464
574
  @c.find_or_create(:x => 1){|x| x[:y] = 2}.should == @c.load(:x=>1, :id=>1)
465
- sqls = DB.sqls
575
+ sqls = @db.sqls
466
576
  sqls.first.should == "SELECT * FROM items WHERE (x = 1) LIMIT 1"
467
577
  ["INSERT INTO items (x, y) VALUES (1, 2)", "INSERT INTO items (y, x) VALUES (2, 1)"].should include(sqls[1])
468
578
  sqls.last.should == "SELECT * FROM items WHERE (id = 1) LIMIT 1"