sequel 4.8.0 → 4.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +48 -0
- data/doc/association_basics.rdoc +1 -1
- data/doc/opening_databases.rdoc +4 -0
- data/doc/postgresql.rdoc +27 -3
- data/doc/release_notes/4.9.0.txt +190 -0
- data/doc/security.rdoc +1 -1
- data/doc/testing.rdoc +2 -2
- data/doc/validations.rdoc +8 -0
- data/lib/sequel/adapters/jdbc.rb +5 -3
- data/lib/sequel/adapters/jdbc/derby.rb +2 -8
- data/lib/sequel/adapters/jdbc/h2.rb +2 -13
- data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -16
- data/lib/sequel/adapters/mysql2.rb +11 -1
- data/lib/sequel/adapters/postgres.rb +33 -10
- data/lib/sequel/adapters/shared/db2.rb +2 -10
- data/lib/sequel/adapters/shared/mssql.rb +10 -8
- data/lib/sequel/adapters/shared/oracle.rb +9 -24
- data/lib/sequel/adapters/shared/postgres.rb +32 -9
- data/lib/sequel/adapters/shared/sqlanywhere.rb +2 -4
- data/lib/sequel/adapters/shared/sqlite.rb +4 -7
- data/lib/sequel/database/schema_methods.rb +15 -0
- data/lib/sequel/dataset.rb +1 -1
- data/lib/sequel/dataset/actions.rb +159 -27
- data/lib/sequel/dataset/graph.rb +29 -7
- data/lib/sequel/dataset/misc.rb +6 -0
- data/lib/sequel/dataset/placeholder_literalizer.rb +164 -0
- data/lib/sequel/dataset/query.rb +2 -0
- data/lib/sequel/dataset/sql.rb +103 -91
- data/lib/sequel/extensions/current_datetime_timestamp.rb +57 -0
- data/lib/sequel/extensions/pg_array.rb +68 -106
- data/lib/sequel/extensions/pg_hstore.rb +5 -5
- data/lib/sequel/extensions/schema_dumper.rb +49 -49
- data/lib/sequel/model.rb +4 -2
- data/lib/sequel/model/associations.rb +1 -1
- data/lib/sequel/model/base.rb +136 -3
- data/lib/sequel/model/errors.rb +6 -0
- data/lib/sequel/plugins/defaults_setter.rb +1 -1
- data/lib/sequel/plugins/eager_each.rb +9 -0
- data/lib/sequel/plugins/nested_attributes.rb +2 -2
- data/lib/sequel/plugins/timestamps.rb +2 -2
- data/lib/sequel/plugins/touch.rb +2 -2
- data/lib/sequel/sql.rb +20 -15
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +70 -8
- data/spec/core/dataset_spec.rb +172 -27
- data/spec/core/expression_filters_spec.rb +3 -3
- data/spec/core/object_graph_spec.rb +17 -1
- data/spec/core/placeholder_literalizer_spec.rb +128 -0
- data/spec/core/schema_spec.rb +54 -0
- data/spec/extensions/current_datetime_timestamp_spec.rb +27 -0
- data/spec/extensions/defaults_setter_spec.rb +12 -0
- data/spec/extensions/eager_each_spec.rb +6 -0
- data/spec/extensions/nested_attributes_spec.rb +14 -2
- data/spec/extensions/pg_array_spec.rb +15 -7
- data/spec/extensions/shared_caching_spec.rb +5 -5
- data/spec/extensions/timestamps_spec.rb +9 -0
- data/spec/extensions/touch_spec.rb +9 -0
- data/spec/integration/database_test.rb +1 -1
- data/spec/integration/dataset_test.rb +27 -5
- data/spec/model/eager_loading_spec.rb +32 -0
- data/spec/model/model_spec.rb +119 -9
- 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
|
-
|
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, :
|
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
|
-
@
|
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
|
-
@
|
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
|
-
@
|
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
|
-
@
|
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
|
-
@
|
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, :
|
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=>
|
129
|
-
rows.should == (
|
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=>
|
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
|
137
|
-
rows.should == (
|
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
|
+
|
data/spec/model/model_spec.rb
CHANGED
@@ -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
|
-
@
|
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
|
-
|
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
|
-
|
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
|
-
@
|
454
|
-
@
|
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
|
-
|
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
|
-
@
|
463
|
-
@
|
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 =
|
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"
|