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.
- 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"
|