sequel 3.4.0 → 3.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|