sequel 3.4.0 → 3.5.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.
- data/CHANGELOG +84 -0
- data/Rakefile +1 -1
- data/doc/cheat_sheet.rdoc +5 -2
- data/doc/opening_databases.rdoc +2 -0
- data/doc/release_notes/3.5.0.txt +510 -0
- data/lib/sequel/adapters/ado.rb +3 -1
- data/lib/sequel/adapters/ado/mssql.rb +2 -2
- data/lib/sequel/adapters/do.rb +2 -11
- data/lib/sequel/adapters/do/mysql.rb +7 -0
- data/lib/sequel/adapters/do/postgres.rb +2 -2
- data/lib/sequel/adapters/firebird.rb +3 -3
- data/lib/sequel/adapters/informix.rb +3 -3
- data/lib/sequel/adapters/jdbc/h2.rb +3 -3
- data/lib/sequel/adapters/jdbc/mssql.rb +7 -0
- data/lib/sequel/adapters/mysql.rb +60 -21
- data/lib/sequel/adapters/odbc.rb +1 -1
- data/lib/sequel/adapters/openbase.rb +3 -3
- data/lib/sequel/adapters/oracle.rb +1 -5
- data/lib/sequel/adapters/postgres.rb +3 -3
- data/lib/sequel/adapters/shared/mssql.rb +142 -33
- data/lib/sequel/adapters/shared/mysql.rb +54 -31
- data/lib/sequel/adapters/shared/oracle.rb +17 -6
- data/lib/sequel/adapters/shared/postgres.rb +7 -7
- data/lib/sequel/adapters/shared/progress.rb +3 -3
- data/lib/sequel/adapters/shared/sqlite.rb +3 -17
- data/lib/sequel/connection_pool.rb +4 -6
- data/lib/sequel/core.rb +29 -113
- data/lib/sequel/database.rb +14 -12
- data/lib/sequel/dataset.rb +8 -21
- data/lib/sequel/dataset/convenience.rb +1 -1
- data/lib/sequel/dataset/graph.rb +9 -2
- data/lib/sequel/dataset/sql.rb +170 -104
- data/lib/sequel/exceptions.rb +3 -0
- data/lib/sequel/extensions/looser_typecasting.rb +21 -0
- data/lib/sequel/extensions/named_timezones.rb +61 -0
- data/lib/sequel/extensions/schema_dumper.rb +7 -1
- data/lib/sequel/extensions/sql_expr.rb +122 -0
- data/lib/sequel/extensions/string_date_time.rb +4 -4
- data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
- data/lib/sequel/model/associations.rb +105 -45
- data/lib/sequel/model/base.rb +37 -28
- data/lib/sequel/plugins/active_model.rb +35 -0
- data/lib/sequel/plugins/association_dependencies.rb +96 -0
- data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
- data/lib/sequel/plugins/force_encoding.rb +61 -0
- data/lib/sequel/plugins/many_through_many.rb +32 -11
- data/lib/sequel/plugins/nested_attributes.rb +7 -2
- data/lib/sequel/plugins/subclasses.rb +45 -0
- data/lib/sequel/plugins/touch.rb +118 -0
- data/lib/sequel/plugins/typecast_on_load.rb +61 -0
- data/lib/sequel/sql.rb +31 -30
- data/lib/sequel/timezones.rb +161 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +262 -0
- data/spec/adapters/mysql_spec.rb +46 -8
- data/spec/adapters/postgres_spec.rb +6 -3
- data/spec/adapters/spec_helper.rb +21 -0
- data/spec/adapters/sqlite_spec.rb +1 -1
- data/spec/core/connection_pool_spec.rb +1 -1
- data/spec/core/database_spec.rb +27 -1
- data/spec/core/dataset_spec.rb +63 -1
- data/spec/core/object_graph_spec.rb +1 -1
- data/spec/core/schema_spec.rb +1 -0
- data/spec/extensions/active_model_spec.rb +47 -0
- data/spec/extensions/association_dependencies_spec.rb +108 -0
- data/spec/extensions/class_table_inheritance_spec.rb +252 -0
- data/spec/extensions/force_encoding_spec.rb +75 -0
- data/spec/extensions/looser_typecasting_spec.rb +39 -0
- data/spec/extensions/many_through_many_spec.rb +60 -2
- data/spec/extensions/named_timezones_spec.rb +72 -0
- data/spec/extensions/nested_attributes_spec.rb +29 -1
- data/spec/extensions/schema_dumper_spec.rb +10 -0
- data/spec/extensions/spec_helper.rb +1 -1
- data/spec/extensions/sql_expr_spec.rb +89 -0
- data/spec/extensions/subclasses_spec.rb +52 -0
- data/spec/extensions/thread_local_timezones_spec.rb +45 -0
- data/spec/extensions/touch_spec.rb +155 -0
- data/spec/extensions/typecast_on_load_spec.rb +60 -0
- data/spec/integration/database_test.rb +8 -0
- data/spec/integration/dataset_test.rb +9 -9
- data/spec/integration/plugin_test.rb +139 -0
- data/spec/integration/schema_test.rb +7 -7
- data/spec/integration/spec_helper.rb +32 -1
- data/spec/integration/timezone_test.rb +3 -3
- data/spec/integration/transaction_test.rb +1 -1
- data/spec/integration/type_test.rb +6 -6
- data/spec/model/association_reflection_spec.rb +18 -0
- data/spec/model/associations_spec.rb +169 -9
- data/spec/model/base_spec.rb +2 -0
- data/spec/model/eager_loading_spec.rb +82 -2
- data/spec/model/model_spec.rb +8 -1
- data/spec/model/record_spec.rb +52 -9
- metadata +33 -23
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), "spec_helper")
|
|
2
|
+
if RUBY_VERSION >= '1.9.0'
|
|
3
|
+
describe "force_encoding plugin" do
|
|
4
|
+
before do
|
|
5
|
+
@c = Class.new(Sequel::Model) do
|
|
6
|
+
end
|
|
7
|
+
@c.columns :id, :x
|
|
8
|
+
@c.plugin :force_encoding, 'UTF-8'
|
|
9
|
+
@e1 = Encoding.find('UTF-8')
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
specify "should force encoding to given encoding on load" do
|
|
13
|
+
s = 'blah'
|
|
14
|
+
s.force_encoding('US-ASCII')
|
|
15
|
+
o = @c.load(:id=>1, :x=>s)
|
|
16
|
+
o.x.should == 'blah'
|
|
17
|
+
o.x.encoding.should == @e1
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
specify "should force encoding to given encoding when setting column values" do
|
|
21
|
+
s = 'blah'
|
|
22
|
+
s.force_encoding('US-ASCII')
|
|
23
|
+
o = @c.new(:x=>s)
|
|
24
|
+
o.x.should == 'blah'
|
|
25
|
+
o.x.encoding.should == @e1
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
specify "should have a forced_encoding class accessor" do
|
|
29
|
+
s = 'blah'
|
|
30
|
+
s.force_encoding('US-ASCII')
|
|
31
|
+
@c.forced_encoding = 'Windows-1258'
|
|
32
|
+
o = @c.load(:id=>1, :x=>s)
|
|
33
|
+
o.x.should == 'blah'
|
|
34
|
+
o.x.encoding.should == Encoding.find('Windows-1258')
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
specify "should not force encoding if forced_encoding is nil" do
|
|
38
|
+
s = 'blah'
|
|
39
|
+
s.force_encoding('US-ASCII')
|
|
40
|
+
@c.forced_encoding = nil
|
|
41
|
+
o = @c.load(:id=>1, :x=>s)
|
|
42
|
+
o.x.should == 'blah'
|
|
43
|
+
o.x.encoding.should == Encoding.find('US-ASCII')
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
specify "should work correctly when subclassing" do
|
|
47
|
+
c = Class.new(@c)
|
|
48
|
+
s = 'blah'
|
|
49
|
+
s.force_encoding('US-ASCII')
|
|
50
|
+
o = c.load(:id=>1, :x=>s)
|
|
51
|
+
o.x.should == 'blah'
|
|
52
|
+
o.x.encoding.should == @e1
|
|
53
|
+
|
|
54
|
+
c.plugin :force_encoding, 'UTF-16LE'
|
|
55
|
+
s = ''
|
|
56
|
+
s.force_encoding('US-ASCII')
|
|
57
|
+
o = c.load(:id=>1, :x=>s)
|
|
58
|
+
o.x.should == ''
|
|
59
|
+
o.x.encoding.should == Encoding.find('UTF-16LE')
|
|
60
|
+
|
|
61
|
+
@c.plugin :force_encoding, 'UTF-32LE'
|
|
62
|
+
s = ''
|
|
63
|
+
s.force_encoding('US-ASCII')
|
|
64
|
+
o = @c.load(:id=>1, :x=>s)
|
|
65
|
+
o.x.should == ''
|
|
66
|
+
o.x.encoding.should == Encoding.find('UTF-32LE')
|
|
67
|
+
|
|
68
|
+
s = ''
|
|
69
|
+
s.force_encoding('US-ASCII')
|
|
70
|
+
o = c.load(:id=>1, :x=>s)
|
|
71
|
+
o.x.should == ''
|
|
72
|
+
o.x.encoding.should == Encoding.find('UTF-16LE')
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), "spec_helper")
|
|
2
|
+
|
|
3
|
+
context "LooserTypecasting Extension" do
|
|
4
|
+
before do
|
|
5
|
+
@db = Sequel::Database.new({})
|
|
6
|
+
def @db.schema(*args)
|
|
7
|
+
[[:id, {}], [:y, {:type=>:float}], [:b, {:type=>:integer}]]
|
|
8
|
+
end
|
|
9
|
+
@c = Class.new(Sequel::Model(@db[:items]))
|
|
10
|
+
@c.instance_eval do
|
|
11
|
+
@columns = [:id, :b, :y]
|
|
12
|
+
def columns; @columns; end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
specify "Should use to_i instead of Integer() for typecasting integers" do
|
|
17
|
+
proc{@c.new(:b=>'a')}.should raise_error(Sequel::InvalidValue)
|
|
18
|
+
@db.extend(Sequel::LooserTypecasting)
|
|
19
|
+
@c.new(:b=>'a').b.should == 0
|
|
20
|
+
|
|
21
|
+
o = Object.new
|
|
22
|
+
def o.to_i
|
|
23
|
+
1
|
|
24
|
+
end
|
|
25
|
+
@c.new(:b=>o).b.should == 1
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
specify "Should use to_f instead of Float() for typecasting floats" do
|
|
29
|
+
proc{@c.new(:y=>'a')}.should raise_error(Sequel::InvalidValue)
|
|
30
|
+
@db.extend(Sequel::LooserTypecasting)
|
|
31
|
+
@c.new(:y=>'a').y.should == 0.0
|
|
32
|
+
|
|
33
|
+
o = Object.new
|
|
34
|
+
def o.to_f
|
|
35
|
+
1.0
|
|
36
|
+
end
|
|
37
|
+
@c.new(:y=>o).y.should == 1.0
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -82,6 +82,16 @@ describe Sequel::Model, "many_through_many" do
|
|
|
82
82
|
n.tags.should == [@c2.load(:id=>1)]
|
|
83
83
|
end
|
|
84
84
|
|
|
85
|
+
it "should handle composite keys" do
|
|
86
|
+
@c1.many_through_many :tags, [[:albums_artists, [:b1, :b2], [:c1, :c2]], [:albums, [:d1, :d2], [:e1, :e2]], [:albums_tags, [:f1, :f2], [:g1, :g2]]], :right_primary_key=>[:h1, :h2], :left_primary_key=>[:id, :yyy]
|
|
87
|
+
n = @c1.load(:id => 1234)
|
|
88
|
+
n.yyy = 85
|
|
89
|
+
a = n.tags_dataset
|
|
90
|
+
a.should be_a_kind_of(Sequel::Dataset)
|
|
91
|
+
a.sql.should == 'SELECT tags.* FROM tags INNER JOIN albums_tags ON ((albums_tags.g1 = tags.h1) AND (albums_tags.g2 = tags.h2)) INNER JOIN albums ON ((albums.e1 = albums_tags.f1) AND (albums.e2 = albums_tags.f2)) INNER JOIN albums_artists ON ((albums_artists.c1 = albums.d1) AND (albums_artists.c2 = albums.d2) AND (albums_artists.b1 = 1234) AND (albums_artists.b2 = 85))'
|
|
92
|
+
n.tags.should == [@c2.load(:id=>1)]
|
|
93
|
+
end
|
|
94
|
+
|
|
85
95
|
it "should support a :conditions option" do
|
|
86
96
|
@c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :conditions=>{:a=>32}
|
|
87
97
|
n = @c1.load(:id => 1234)
|
|
@@ -287,10 +297,20 @@ describe 'Sequel::Plugins::ManyThroughMany::ManyThroughManyAssociationReflection
|
|
|
287
297
|
@ar.edges.should == [{:conditions=>[], :left=>:id, :right=>:artist_id, :table=>:albums_artists, :join_type=>:left_outer, :block=>nil}, {:conditions=>[], :left=>:album_id, :right=>:id, :table=>:albums, :join_type=>:left_outer, :block=>nil}, {:conditions=>[], :left=>:id, :right=>:album_id, :table=>:albums_tags, :join_type=>:left_outer, :block=>nil}]
|
|
288
298
|
end
|
|
289
299
|
|
|
300
|
+
it "#edges should handle composite keys" do
|
|
301
|
+
Artist.many_through_many :tags, [[:albums_artists, [:b1, :b2], [:c1, :c2]], [:albums, [:d1, :d2], [:e1, :e2]], [:albums_tags, [:f1, :f2], [:g1, :g2]]], :right_primary_key=>[:h1, :h2], :left_primary_key=>[:id, :yyy]
|
|
302
|
+
Artist.association_reflection(:tags).edges.should == [{:conditions=>[], :left=>[:id, :yyy], :right=>[:b1, :b2], :table=>:albums_artists, :join_type=>:left_outer, :block=>nil}, {:conditions=>[], :left=>[:c1, :c2], :right=>[:d1, :d2], :table=>:albums, :join_type=>:left_outer, :block=>nil}, {:conditions=>[], :left=>[:e1, :e2], :right=>[:f1, :f2], :table=>:albums_tags, :join_type=>:left_outer, :block=>nil}]
|
|
303
|
+
end
|
|
304
|
+
|
|
290
305
|
it "#reverse_edges should be an array of joins to make when lazy loading or eager loading" do
|
|
291
306
|
@ar.reverse_edges.should == [{:alias=>:albums_tags, :left=>:tag_id, :right=>:id, :table=>:albums_tags}, {:alias=>:albums, :left=>:id, :right=>:album_id, :table=>:albums}]
|
|
292
307
|
end
|
|
293
308
|
|
|
309
|
+
it "#reverse_edges should handle composite keys" do
|
|
310
|
+
Artist.many_through_many :tags, [[:albums_artists, [:b1, :b2], [:c1, :c2]], [:albums, [:d1, :d2], [:e1, :e2]], [:albums_tags, [:f1, :f2], [:g1, :g2]]], :right_primary_key=>[:h1, :h2], :left_primary_key=>[:id, :yyy]
|
|
311
|
+
Artist.association_reflection(:tags).reverse_edges.should == [{:alias=>:albums_tags, :left=>[:g1, :g2], :right=>[:h1, :h2], :table=>:albums_tags}, {:alias=>:albums, :left=>[:e1, :e2], :right=>[:f1, :f2], :table=>:albums}]
|
|
312
|
+
end
|
|
313
|
+
|
|
294
314
|
it "#reciprocal should be nil" do
|
|
295
315
|
@ar.reciprocal.should == nil
|
|
296
316
|
end
|
|
@@ -339,6 +359,8 @@ describe "Sequel::Plugins::ManyThroughMany eager loading methods" do
|
|
|
339
359
|
h = {:id => 2}
|
|
340
360
|
if sql =~ /albums_artists.artist_id IN \(([18])\)/
|
|
341
361
|
h.merge!(:x_foreign_key_x=>$1.to_i)
|
|
362
|
+
elsif sql =~ /\(\(albums_artists.b1, albums_artists.b2\) IN \(\(1, 8\)\)\)/
|
|
363
|
+
h.merge!(:x_foreign_key_0_x=>1, :x_foreign_key_1_x=>8)
|
|
342
364
|
end
|
|
343
365
|
h[:tag_id] = h.delete(:id) if sql =~ /albums_artists.artist_id IN \(8\)/
|
|
344
366
|
yield h
|
|
@@ -453,7 +475,7 @@ describe "Sequel::Plugins::ManyThroughMany eager loading methods" do
|
|
|
453
475
|
a = @c1.eager(:tags).all
|
|
454
476
|
a.should == [@c1.load(:id=>1)]
|
|
455
477
|
MODEL_DB.sqls.should == ['SELECT * FROM artists',
|
|
456
|
-
'SELECT tags.id, tracks.id AS tracks_id, albums_artists.artist_id AS x_foreign_key_x FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id IN (1))) LEFT OUTER JOIN albums_tags AS albums_tags_0 ON (albums_tags_0.tag_id = tags.id) LEFT OUTER JOIN albums
|
|
478
|
+
'SELECT tags.id, tracks.id AS tracks_id, albums_artists.artist_id AS x_foreign_key_x FROM (SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id IN (1)))) AS tags LEFT OUTER JOIN albums_tags AS albums_tags_0 ON (albums_tags_0.tag_id = tags.id) LEFT OUTER JOIN albums ON (albums.id = albums_tags_0.album_id) LEFT OUTER JOIN tracks ON (tracks.album_id = albums.id)']
|
|
457
479
|
a = a.first
|
|
458
480
|
a.tags.should == [Tag.load(:id=>2)]
|
|
459
481
|
a.tags.first.tracks.should == [Track.load(:id=>4)]
|
|
@@ -473,7 +495,7 @@ describe "Sequel::Plugins::ManyThroughMany eager loading methods" do
|
|
|
473
495
|
@c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :eager_graph=>:tracks
|
|
474
496
|
a = @c1.load(:id=>1)
|
|
475
497
|
a.tags
|
|
476
|
-
MODEL_DB.sqls.should == [ 'SELECT tags.id, tracks.id AS tracks_id FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id = 1)) LEFT OUTER JOIN albums_tags AS albums_tags_0 ON (albums_tags_0.tag_id = tags.id) LEFT OUTER JOIN albums
|
|
498
|
+
MODEL_DB.sqls.should == [ 'SELECT tags.id, tracks.id AS tracks_id FROM (SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id = 1))) AS tags LEFT OUTER JOIN albums_tags AS albums_tags_0 ON (albums_tags_0.tag_id = tags.id) LEFT OUTER JOIN albums ON (albums.id = albums_tags_0.album_id) LEFT OUTER JOIN tracks ON (tracks.album_id = albums.id)']
|
|
477
499
|
a.tags.should == [Tag.load(:id=>2)]
|
|
478
500
|
a.tags.first.tracks.should == [Track.load(:id=>4)]
|
|
479
501
|
MODEL_DB.sqls.length.should == 1
|
|
@@ -554,6 +576,26 @@ describe "Sequel::Plugins::ManyThroughMany eager loading methods" do
|
|
|
554
576
|
a.first.tags.should == [Tag.load(:tag_id=>2)]
|
|
555
577
|
MODEL_DB.sqls.length.should == 2
|
|
556
578
|
end
|
|
579
|
+
|
|
580
|
+
it "should handle composite keys" do
|
|
581
|
+
@c1.send(:define_method, :yyy){values[:yyy]}
|
|
582
|
+
@c1.dataset.extend(Module.new {
|
|
583
|
+
def columns
|
|
584
|
+
[:id, :yyy]
|
|
585
|
+
end
|
|
586
|
+
def fetch_rows(sql)
|
|
587
|
+
@db << sql
|
|
588
|
+
yield({:id=>1, :yyy=>8})
|
|
589
|
+
end
|
|
590
|
+
})
|
|
591
|
+
@c1.many_through_many :tags, [[:albums_artists, [:b1, :b2], [:c1, :c2]], [:albums, [:d1, :d2], [:e1, :e2]], [:albums_tags, [:f1, :f2], [:g1, :g2]]], :right_primary_key=>[:h1, :h2], :left_primary_key=>[:id, :yyy]
|
|
592
|
+
a = @c1.eager(:tags).all
|
|
593
|
+
a.should == [@c1.load(:id=>1, :yyy=>8)]
|
|
594
|
+
MODEL_DB.sqls.should == ['SELECT * FROM artists',
|
|
595
|
+
'SELECT tags.*, albums_artists.b1 AS x_foreign_key_0_x, albums_artists.b2 AS x_foreign_key_1_x FROM tags INNER JOIN albums_tags ON ((albums_tags.g1 = tags.h1) AND (albums_tags.g2 = tags.h2)) INNER JOIN albums ON ((albums.e1 = albums_tags.f1) AND (albums.e2 = albums_tags.f2)) INNER JOIN albums_artists ON ((albums_artists.c1 = albums.d1) AND (albums_artists.c2 = albums.d2) AND ((albums_artists.b1, albums_artists.b2) IN ((1, 8))))']
|
|
596
|
+
a.first.tags.should == [Tag.load(:id=>2)]
|
|
597
|
+
MODEL_DB.sqls.length.should == 2
|
|
598
|
+
end
|
|
557
599
|
|
|
558
600
|
it "should respect :after_load callbacks on associations when eager loading" do
|
|
559
601
|
@c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :after_load=>lambda{|o, as| o[:id] *= 2; as.each{|a| a[:id] *= 3}}
|
|
@@ -725,6 +767,22 @@ describe "Sequel::Plugins::ManyThroughMany eager loading methods" do
|
|
|
725
767
|
a.first.tags.should == [Tag.load(:id=>2, :tag_id=>4)]
|
|
726
768
|
MODEL_DB.sqls.length.should == 1
|
|
727
769
|
end
|
|
770
|
+
|
|
771
|
+
it "eager graphing should respect composite keys" do
|
|
772
|
+
@c1.many_through_many :tags, [[:albums_artists, [:b1, :b2], [:c1, :c2]], [:albums, [:d1, :d2], [:e1, :e2]], [:albums_tags, [:f1, :f2], [:g1, :g2]]], :right_primary_key=>[:id, :tag_id], :left_primary_key=>[:id, :yyy]
|
|
773
|
+
@c1.dataset.meta_def(:columns){[:id, :yyy]}
|
|
774
|
+
Tag.dataset.meta_def(:columns){[:id, :tag_id]}
|
|
775
|
+
ds = @c1.eager_graph(:tags)
|
|
776
|
+
def ds.fetch_rows(sql)
|
|
777
|
+
@db << sql
|
|
778
|
+
yield({:id=>1, :yyy=>8, :tags_id=>2, :tag_id=>4})
|
|
779
|
+
end
|
|
780
|
+
a = ds.all
|
|
781
|
+
a.should == [@c1.load(:id=>1, :yyy=>8)]
|
|
782
|
+
MODEL_DB.sqls.should == ['SELECT artists.id, artists.yyy, tags.id AS tags_id, tags.tag_id FROM artists LEFT OUTER JOIN albums_artists ON ((albums_artists.b1 = artists.id) AND (albums_artists.b2 = artists.yyy)) LEFT OUTER JOIN albums ON ((albums.d1 = albums_artists.c1) AND (albums.d2 = albums_artists.c2)) LEFT OUTER JOIN albums_tags ON ((albums_tags.f1 = albums.e1) AND (albums_tags.f2 = albums.e2)) LEFT OUTER JOIN tags ON ((tags.id = albums_tags.g1) AND (tags.tag_id = albums_tags.g2))']
|
|
783
|
+
a.first.tags.should == [Tag.load(:id=>2, :tag_id=>4)]
|
|
784
|
+
MODEL_DB.sqls.length.should == 1
|
|
785
|
+
end
|
|
728
786
|
|
|
729
787
|
it "should respect the association's :graph_select option" do
|
|
730
788
|
@c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :graph_select=>:b
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), "spec_helper")
|
|
2
|
+
|
|
3
|
+
if (begin
|
|
4
|
+
require 'tzinfo'
|
|
5
|
+
true
|
|
6
|
+
rescue LoadError
|
|
7
|
+
end)
|
|
8
|
+
|
|
9
|
+
Sequel.extension :named_timezones
|
|
10
|
+
Sequel.datetime_class = Time
|
|
11
|
+
|
|
12
|
+
describe "Sequel named_timezones extension" do
|
|
13
|
+
before do
|
|
14
|
+
@tz_in = TZInfo::Timezone.get('America/Los_Angeles')
|
|
15
|
+
@tz_out = TZInfo::Timezone.get('America/New_York')
|
|
16
|
+
@db = MockDatabase.new
|
|
17
|
+
@dt = DateTime.civil(2009,6,1,10,20,30,0)
|
|
18
|
+
Sequel.application_timezone = 'America/Los_Angeles'
|
|
19
|
+
Sequel.database_timezone = 'America/New_York'
|
|
20
|
+
Sequel.datetime_class = DateTime
|
|
21
|
+
end
|
|
22
|
+
after do
|
|
23
|
+
Sequel.default_timezone = nil
|
|
24
|
+
Sequel.datetime_class = Time
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "should convert string arguments to *_timezone= to TZInfo::Timezone instances" do
|
|
28
|
+
Sequel.application_timezone.should == @tz_in
|
|
29
|
+
Sequel.database_timezone.should == @tz_out
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "should accept TZInfo::Timezone instances in *_timezone=" do
|
|
33
|
+
Sequel.application_timezone = @tz_in
|
|
34
|
+
Sequel.database_timezone = @tz_out
|
|
35
|
+
Sequel.application_timezone.should == @tz_in
|
|
36
|
+
Sequel.database_timezone.should == @tz_out
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "should convert datetimes going into the database to named database_timezone" do
|
|
40
|
+
ds = @db[:a]
|
|
41
|
+
def ds.supports_timestamp_timezones?; true; end
|
|
42
|
+
def ds.supports_timestamp_usecs?; false; end
|
|
43
|
+
ds.insert([@dt, DateTime.civil(2009,6,1,3,20,30,Rational(-7, 24)), DateTime.civil(2009,6,1,6,20,30,Rational(-1, 6))])
|
|
44
|
+
@db.sqls.should == ["INSERT INTO a VALUES ('2009-06-01 06:20:30-0400', '2009-06-01 06:20:30-0400', '2009-06-01 06:20:30-0400')"]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "should convert datetimes coming out of the database from database_timezone to application_timezone" do
|
|
48
|
+
dt = Sequel.database_to_application_timestamp('2009-06-01 06:20:30-0400')
|
|
49
|
+
dt.should == @dt
|
|
50
|
+
dt.offset.should == Rational(-7, 24)
|
|
51
|
+
|
|
52
|
+
dt = Sequel.database_to_application_timestamp('2009-06-01 10:20:30+0000')
|
|
53
|
+
dt.should == @dt
|
|
54
|
+
dt.offset.should == Rational(-7, 24)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "should assume datetimes coming out of the database that don't have an offset as coming from database_timezone" do
|
|
58
|
+
dt = Sequel.database_to_application_timestamp('2009-06-01 06:20:30')
|
|
59
|
+
dt.should == @dt
|
|
60
|
+
dt.offset.should == Rational(-7, 24)
|
|
61
|
+
|
|
62
|
+
dt = Sequel.database_to_application_timestamp('2009-06-01 10:20:30')
|
|
63
|
+
dt.should == @dt + Rational(1, 6)
|
|
64
|
+
dt.offset.should == Rational(-7, 24)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "should work with the thread_local_timezones extension" do
|
|
68
|
+
[Thread.new{Sequel.thread_application_timezone = 'America/New_York'; sleep 0.03; Sequel.application_timezone.should == @tz_out},
|
|
69
|
+
Thread.new{sleep 0.01; Sequel.thread_application_timezone = 'America/Los_Angeles'; sleep 0.01; Sequel.application_timezone.should == @tz_in}].each{|x| x.join}
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -220,7 +220,7 @@ describe "NestedAttributes plugin" do
|
|
|
220
220
|
@mods.should == [[:u, :artists, {:name=>"Ar"}, '(id = 20)']]
|
|
221
221
|
end
|
|
222
222
|
|
|
223
|
-
it "should not save if nested attribute is not
|
|
223
|
+
it "should not save if nested attribute is not valid and should include nested attribute validation errors in the main object's validation errors" do
|
|
224
224
|
@Artist.class_eval do
|
|
225
225
|
def validate
|
|
226
226
|
super
|
|
@@ -234,6 +234,34 @@ describe "NestedAttributes plugin" do
|
|
|
234
234
|
@mods.should == []
|
|
235
235
|
end
|
|
236
236
|
|
|
237
|
+
it "should not attempt to validate nested attributes if the :validate=>false association option is used" do
|
|
238
|
+
@Album.many_to_one :artist, :class=>@Artist, :validate=>false
|
|
239
|
+
@Album.nested_attributes :artist, :tags, :destroy=>true, :remove=>true
|
|
240
|
+
@Artist.class_eval do
|
|
241
|
+
def validate
|
|
242
|
+
super
|
|
243
|
+
errors.add(:name, 'cannot be Ar') if name == 'Ar'
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
a = @Album.new(:name=>'Al', :artist_attributes=>{:name=>'Ar'})
|
|
247
|
+
@mods.should == []
|
|
248
|
+
a.save
|
|
249
|
+
@mods.should == [[:is, :artists, {:name=>"Ar"}, 1], [:is, :albums, {:name=>"Al", :artist_id=>1}, 2]]
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
it "should not attempt to validate nested attributes if the :validate=>false option is passed to save" do
|
|
253
|
+
@Artist.class_eval do
|
|
254
|
+
def validate
|
|
255
|
+
super
|
|
256
|
+
errors.add(:name, 'cannot be Ar') if name == 'Ar'
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
a = @Album.new(:name=>'Al', :artist_attributes=>{:name=>'Ar'})
|
|
260
|
+
@mods.should == []
|
|
261
|
+
a.save(:validate=>false)
|
|
262
|
+
@mods.should == [[:is, :artists, {:name=>"Ar"}, 1], [:is, :albums, {:name=>"Al", :artist_id=>1}, 2]]
|
|
263
|
+
end
|
|
264
|
+
|
|
237
265
|
it "should not accept nested attributes unless explicitly specified" do
|
|
238
266
|
@Artist.many_to_many :tags, :class=>@Tag, :left_key=>:album_id, :right_key=>:tag_id, :join_table=>:at
|
|
239
267
|
proc{@Artist.create({:name=>'Ar', :tags_attributes=>[{:name=>'T'}]})}.should raise_error(Sequel::Error)
|
|
@@ -246,6 +246,16 @@ END_MIG
|
|
|
246
246
|
@d.dump_table_schema(:t4).gsub(/[+-]\d\d:\d\d"\)/, '")').should == "create_table(:t4) do\n TrueClass :c1, :default=>false\n String :c2, :default=>\"blah\"\n Integer :c3, :default=>-1\n Float :c4, :default=>1.0\n BigDecimal :c5, :default=>BigDecimal.new(\"0.1005E3\")\n File :c6, :default=>Sequel::SQL::Blob.new(\"blah\")\n Date :c7, :default=>Date.parse(\"2008-10-29\")\n DateTime :c8, :default=>DateTime.parse(\"2008-10-29T10:20:30\")\n Time :c9, :default=>Time.parse(\"10:20:30\"), :only_time=>true\n String :c10\nend"
|
|
247
247
|
@d.dump_table_schema(:t4, :same_db=>true).gsub(/[+-]\d\d:\d\d"\)/, '")').should == "create_table(:t4) do\n column :c1, \"boolean\", :default=>false\n column :c2, \"varchar\", :default=>\"blah\"\n column :c3, \"integer\", :default=>-1\n column :c4, \"float\", :default=>1.0\n column :c5, \"decimal\", :default=>BigDecimal.new(\"0.1005E3\")\n column :c6, \"blob\", :default=>Sequel::SQL::Blob.new(\"blah\")\n column :c7, \"date\", :default=>Date.parse(\"2008-10-29\")\n column :c8, \"datetime\", :default=>DateTime.parse(\"2008-10-29T10:20:30\")\n column :c9, \"time\", :default=>Time.parse(\"10:20:30\")\n column :c10, \"interval\", :default=>\"'6 weeks'\".lit\nend"
|
|
248
248
|
end
|
|
249
|
+
|
|
250
|
+
it "should not use a '...'.lit as a fallback if using MySQL with the :same_db option" do
|
|
251
|
+
@d.meta_def(:database_type){:mysql}
|
|
252
|
+
@d.meta_def(:schema) do |t, *os|
|
|
253
|
+
s = [[:c10, {:db_type=>'interval', :default=>"'6 weeks'", :type=>:interval, :allow_null=>true}]]
|
|
254
|
+
s.each{|_, c| c[:ruby_default] = column_schema_to_ruby_default(c[:default], c[:type])}
|
|
255
|
+
s
|
|
256
|
+
end
|
|
257
|
+
@d.dump_table_schema(:t5, :same_db=>true).should == "create_table(:t5) do\n column :c10, \"interval\"\nend"
|
|
258
|
+
end
|
|
249
259
|
|
|
250
260
|
it "should convert unknown database types to strings" do
|
|
251
261
|
@d.dump_table_schema(:t5).should == "create_table(:t5) do\n String :c1\nend"
|
|
@@ -8,7 +8,7 @@ unless Sequel.const_defined?('Model')
|
|
|
8
8
|
require 'sequel/model'
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
Sequel.extension(*%w'string_date_time inflector pagination query pretty_table blank migration schema_dumper')
|
|
11
|
+
Sequel.extension(*%w'string_date_time inflector pagination query pretty_table blank migration schema_dumper looser_typecasting sql_expr thread_local_timezones')
|
|
12
12
|
{:hook_class_methods=>[], :schema=>[], :validation_class_methods=>[]}.each{|p, opts| Sequel::Model.plugin(p, *opts)}
|
|
13
13
|
|
|
14
14
|
class MockDataset < Sequel::Dataset
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
|
2
|
+
|
|
3
|
+
context "Sequel sql_expr extension" do
|
|
4
|
+
specify "Object#sql_expr should wrap the object in a GenericComplexExpression" do
|
|
5
|
+
o = Object.new
|
|
6
|
+
s = o.sql_expr
|
|
7
|
+
s.should be_a_kind_of(Sequel::SQL::GenericComplexExpression)
|
|
8
|
+
s.op.should == :NOOP
|
|
9
|
+
s.args.should == [o]
|
|
10
|
+
(s+1).should be_a_kind_of(Sequel::SQL::NumericExpression)
|
|
11
|
+
(s & true).should be_a_kind_of(Sequel::SQL::BooleanExpression)
|
|
12
|
+
(s < 1).should be_a_kind_of(Sequel::SQL::BooleanExpression)
|
|
13
|
+
s.sql_subscript(1).should be_a_kind_of(Sequel::SQL::Subscript)
|
|
14
|
+
s.like('a').should be_a_kind_of(Sequel::SQL::BooleanExpression)
|
|
15
|
+
s.as(:a).should be_a_kind_of(Sequel::SQL::AliasedExpression)
|
|
16
|
+
s.cast(Integer).should be_a_kind_of(Sequel::SQL::Cast)
|
|
17
|
+
s.desc.should be_a_kind_of(Sequel::SQL::OrderedExpression)
|
|
18
|
+
s.sql_string.should be_a_kind_of(Sequel::SQL::StringExpression)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
specify "Numeric#sql_expr should wrap the object in a NumericExpression" do
|
|
22
|
+
[1, 2.0, 2^40, BigDecimal.new('1.0')].each do |o|
|
|
23
|
+
s = o.sql_expr
|
|
24
|
+
s.should be_a_kind_of(Sequel::SQL::NumericExpression)
|
|
25
|
+
s.op.should == :NOOP
|
|
26
|
+
s.args.should == [o]
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
specify "String#sql_expr should wrap the object in a StringExpression" do
|
|
31
|
+
o = ""
|
|
32
|
+
s = o.sql_expr
|
|
33
|
+
s.should be_a_kind_of(Sequel::SQL::StringExpression)
|
|
34
|
+
s.op.should == :NOOP
|
|
35
|
+
s.args.should == [o]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
specify "NilClass, TrueClass, and FalseClass#sql_expr should wrap the object in a BooleanExpression" do
|
|
39
|
+
[nil, true, false].each do |o|
|
|
40
|
+
s = o.sql_expr
|
|
41
|
+
s.should be_a_kind_of(Sequel::SQL::BooleanExpression)
|
|
42
|
+
s.op.should == :NOOP
|
|
43
|
+
s.args.should == [o]
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
specify "Proc#sql_expr should should treat the object as a virtual row block" do
|
|
48
|
+
s = proc{a}.sql_expr
|
|
49
|
+
s.should be_a_kind_of(Sequel::SQL::Identifier)
|
|
50
|
+
s.value.should == :a
|
|
51
|
+
|
|
52
|
+
s = proc{a__b}.sql_expr
|
|
53
|
+
s.should be_a_kind_of(Sequel::SQL::QualifiedIdentifier)
|
|
54
|
+
s.table.should == "a"
|
|
55
|
+
s.column.should == "b"
|
|
56
|
+
|
|
57
|
+
s = proc{a(b)}.sql_expr
|
|
58
|
+
s.should be_a_kind_of(Sequel::SQL::Function)
|
|
59
|
+
s.f.should == :a
|
|
60
|
+
s.args.length.should == 1
|
|
61
|
+
s.args.first.should be_a_kind_of(Sequel::SQL::Identifier)
|
|
62
|
+
s.args.first.value.should == :b
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
specify "Proc#sql_expr should should wrap the object in a GenericComplexExpression if the object is not already an expression" do
|
|
66
|
+
s = proc{1}.sql_expr
|
|
67
|
+
s.should be_a_kind_of(Sequel::SQL::GenericComplexExpression)
|
|
68
|
+
s.op.should == :NOOP
|
|
69
|
+
s.args.should == [1]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
specify "Proc#sql_expr should should convert a hash or array of two element arrays to a BooleanExpression" do
|
|
73
|
+
s = proc{{a=>b}}.sql_expr
|
|
74
|
+
s.should be_a_kind_of(Sequel::SQL::BooleanExpression)
|
|
75
|
+
s.op.should == :"="
|
|
76
|
+
s.args.first.should be_a_kind_of(Sequel::SQL::Identifier)
|
|
77
|
+
s.args.first.value.should == :a
|
|
78
|
+
s.args.last.should be_a_kind_of(Sequel::SQL::Identifier)
|
|
79
|
+
s.args.last.value.should == :b
|
|
80
|
+
|
|
81
|
+
s = proc{[[a,b]]}.sql_expr
|
|
82
|
+
s.should be_a_kind_of(Sequel::SQL::BooleanExpression)
|
|
83
|
+
s.op.should == :"="
|
|
84
|
+
s.args.first.should be_a_kind_of(Sequel::SQL::Identifier)
|
|
85
|
+
s.args.first.value.should == :a
|
|
86
|
+
s.args.last.should be_a_kind_of(Sequel::SQL::Identifier)
|
|
87
|
+
s.args.last.value.should == :b
|
|
88
|
+
end
|
|
89
|
+
end
|