sequel 3.4.0 → 3.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. data/CHANGELOG +84 -0
  2. data/Rakefile +1 -1
  3. data/doc/cheat_sheet.rdoc +5 -2
  4. data/doc/opening_databases.rdoc +2 -0
  5. data/doc/release_notes/3.5.0.txt +510 -0
  6. data/lib/sequel/adapters/ado.rb +3 -1
  7. data/lib/sequel/adapters/ado/mssql.rb +2 -2
  8. data/lib/sequel/adapters/do.rb +2 -11
  9. data/lib/sequel/adapters/do/mysql.rb +7 -0
  10. data/lib/sequel/adapters/do/postgres.rb +2 -2
  11. data/lib/sequel/adapters/firebird.rb +3 -3
  12. data/lib/sequel/adapters/informix.rb +3 -3
  13. data/lib/sequel/adapters/jdbc/h2.rb +3 -3
  14. data/lib/sequel/adapters/jdbc/mssql.rb +7 -0
  15. data/lib/sequel/adapters/mysql.rb +60 -21
  16. data/lib/sequel/adapters/odbc.rb +1 -1
  17. data/lib/sequel/adapters/openbase.rb +3 -3
  18. data/lib/sequel/adapters/oracle.rb +1 -5
  19. data/lib/sequel/adapters/postgres.rb +3 -3
  20. data/lib/sequel/adapters/shared/mssql.rb +142 -33
  21. data/lib/sequel/adapters/shared/mysql.rb +54 -31
  22. data/lib/sequel/adapters/shared/oracle.rb +17 -6
  23. data/lib/sequel/adapters/shared/postgres.rb +7 -7
  24. data/lib/sequel/adapters/shared/progress.rb +3 -3
  25. data/lib/sequel/adapters/shared/sqlite.rb +3 -17
  26. data/lib/sequel/connection_pool.rb +4 -6
  27. data/lib/sequel/core.rb +29 -113
  28. data/lib/sequel/database.rb +14 -12
  29. data/lib/sequel/dataset.rb +8 -21
  30. data/lib/sequel/dataset/convenience.rb +1 -1
  31. data/lib/sequel/dataset/graph.rb +9 -2
  32. data/lib/sequel/dataset/sql.rb +170 -104
  33. data/lib/sequel/exceptions.rb +3 -0
  34. data/lib/sequel/extensions/looser_typecasting.rb +21 -0
  35. data/lib/sequel/extensions/named_timezones.rb +61 -0
  36. data/lib/sequel/extensions/schema_dumper.rb +7 -1
  37. data/lib/sequel/extensions/sql_expr.rb +122 -0
  38. data/lib/sequel/extensions/string_date_time.rb +4 -4
  39. data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
  40. data/lib/sequel/model/associations.rb +105 -45
  41. data/lib/sequel/model/base.rb +37 -28
  42. data/lib/sequel/plugins/active_model.rb +35 -0
  43. data/lib/sequel/plugins/association_dependencies.rb +96 -0
  44. data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
  45. data/lib/sequel/plugins/force_encoding.rb +61 -0
  46. data/lib/sequel/plugins/many_through_many.rb +32 -11
  47. data/lib/sequel/plugins/nested_attributes.rb +7 -2
  48. data/lib/sequel/plugins/subclasses.rb +45 -0
  49. data/lib/sequel/plugins/touch.rb +118 -0
  50. data/lib/sequel/plugins/typecast_on_load.rb +61 -0
  51. data/lib/sequel/sql.rb +31 -30
  52. data/lib/sequel/timezones.rb +161 -0
  53. data/lib/sequel/version.rb +1 -1
  54. data/spec/adapters/mssql_spec.rb +262 -0
  55. data/spec/adapters/mysql_spec.rb +46 -8
  56. data/spec/adapters/postgres_spec.rb +6 -3
  57. data/spec/adapters/spec_helper.rb +21 -0
  58. data/spec/adapters/sqlite_spec.rb +1 -1
  59. data/spec/core/connection_pool_spec.rb +1 -1
  60. data/spec/core/database_spec.rb +27 -1
  61. data/spec/core/dataset_spec.rb +63 -1
  62. data/spec/core/object_graph_spec.rb +1 -1
  63. data/spec/core/schema_spec.rb +1 -0
  64. data/spec/extensions/active_model_spec.rb +47 -0
  65. data/spec/extensions/association_dependencies_spec.rb +108 -0
  66. data/spec/extensions/class_table_inheritance_spec.rb +252 -0
  67. data/spec/extensions/force_encoding_spec.rb +75 -0
  68. data/spec/extensions/looser_typecasting_spec.rb +39 -0
  69. data/spec/extensions/many_through_many_spec.rb +60 -2
  70. data/spec/extensions/named_timezones_spec.rb +72 -0
  71. data/spec/extensions/nested_attributes_spec.rb +29 -1
  72. data/spec/extensions/schema_dumper_spec.rb +10 -0
  73. data/spec/extensions/spec_helper.rb +1 -1
  74. data/spec/extensions/sql_expr_spec.rb +89 -0
  75. data/spec/extensions/subclasses_spec.rb +52 -0
  76. data/spec/extensions/thread_local_timezones_spec.rb +45 -0
  77. data/spec/extensions/touch_spec.rb +155 -0
  78. data/spec/extensions/typecast_on_load_spec.rb +60 -0
  79. data/spec/integration/database_test.rb +8 -0
  80. data/spec/integration/dataset_test.rb +9 -9
  81. data/spec/integration/plugin_test.rb +139 -0
  82. data/spec/integration/schema_test.rb +7 -7
  83. data/spec/integration/spec_helper.rb +32 -1
  84. data/spec/integration/timezone_test.rb +3 -3
  85. data/spec/integration/transaction_test.rb +1 -1
  86. data/spec/integration/type_test.rb +6 -6
  87. data/spec/model/association_reflection_spec.rb +18 -0
  88. data/spec/model/associations_spec.rb +169 -9
  89. data/spec/model/base_spec.rb +2 -0
  90. data/spec/model/eager_loading_spec.rb +82 -2
  91. data/spec/model/model_spec.rb +8 -1
  92. data/spec/model/record_spec.rb +52 -9
  93. 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 AS albums_0 ON (albums_0.id = albums_tags_0.album_id) LEFT OUTER JOIN tracks ON (tracks.album_id = albums_0.id)']
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 AS albums_0 ON (albums_0.id = albums_tags_0.album_id) LEFT OUTER JOIN tracks ON (tracks.album_id = albums_0.id)']
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 validate and should include nested attribute validation errors in the main object's validation errors" do
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