sequel 4.8.0 → 4.9.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.
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"