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