sequel 3.3.0 → 3.4.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 (58) hide show
  1. data/CHANGELOG +62 -0
  2. data/README.rdoc +4 -4
  3. data/doc/release_notes/3.3.0.txt +1 -1
  4. data/doc/release_notes/3.4.0.txt +325 -0
  5. data/doc/sharding.rdoc +3 -3
  6. data/lib/sequel/adapters/amalgalite.rb +1 -1
  7. data/lib/sequel/adapters/firebird.rb +4 -9
  8. data/lib/sequel/adapters/jdbc.rb +21 -7
  9. data/lib/sequel/adapters/mysql.rb +2 -1
  10. data/lib/sequel/adapters/odbc.rb +7 -21
  11. data/lib/sequel/adapters/oracle.rb +1 -1
  12. data/lib/sequel/adapters/postgres.rb +6 -1
  13. data/lib/sequel/adapters/shared/mssql.rb +11 -0
  14. data/lib/sequel/adapters/shared/mysql.rb +8 -12
  15. data/lib/sequel/adapters/shared/oracle.rb +13 -0
  16. data/lib/sequel/adapters/shared/postgres.rb +5 -10
  17. data/lib/sequel/adapters/shared/sqlite.rb +21 -1
  18. data/lib/sequel/adapters/sqlite.rb +2 -2
  19. data/lib/sequel/core.rb +147 -11
  20. data/lib/sequel/database.rb +21 -9
  21. data/lib/sequel/dataset.rb +31 -6
  22. data/lib/sequel/dataset/convenience.rb +1 -1
  23. data/lib/sequel/dataset/sql.rb +76 -18
  24. data/lib/sequel/extensions/inflector.rb +2 -51
  25. data/lib/sequel/model.rb +16 -10
  26. data/lib/sequel/model/associations.rb +4 -1
  27. data/lib/sequel/model/base.rb +13 -6
  28. data/lib/sequel/model/default_inflections.rb +46 -0
  29. data/lib/sequel/model/inflections.rb +1 -51
  30. data/lib/sequel/plugins/boolean_readers.rb +52 -0
  31. data/lib/sequel/plugins/instance_hooks.rb +57 -0
  32. data/lib/sequel/plugins/lazy_attributes.rb +13 -1
  33. data/lib/sequel/plugins/nested_attributes.rb +171 -0
  34. data/lib/sequel/plugins/serialization.rb +35 -16
  35. data/lib/sequel/plugins/timestamps.rb +87 -0
  36. data/lib/sequel/plugins/validation_helpers.rb +8 -1
  37. data/lib/sequel/sql.rb +33 -0
  38. data/lib/sequel/version.rb +1 -1
  39. data/spec/adapters/sqlite_spec.rb +11 -6
  40. data/spec/core/core_sql_spec.rb +29 -0
  41. data/spec/core/database_spec.rb +16 -7
  42. data/spec/core/dataset_spec.rb +264 -20
  43. data/spec/extensions/boolean_readers_spec.rb +86 -0
  44. data/spec/extensions/inflector_spec.rb +67 -4
  45. data/spec/extensions/instance_hooks_spec.rb +133 -0
  46. data/spec/extensions/lazy_attributes_spec.rb +45 -5
  47. data/spec/extensions/nested_attributes_spec.rb +272 -0
  48. data/spec/extensions/serialization_spec.rb +64 -1
  49. data/spec/extensions/timestamps_spec.rb +150 -0
  50. data/spec/extensions/validation_helpers_spec.rb +18 -0
  51. data/spec/integration/dataset_test.rb +79 -2
  52. data/spec/integration/schema_test.rb +17 -0
  53. data/spec/integration/timezone_test.rb +55 -0
  54. data/spec/model/associations_spec.rb +19 -7
  55. data/spec/model/model_spec.rb +29 -0
  56. data/spec/model/record_spec.rb +36 -0
  57. data/spec/spec_config.rb +1 -1
  58. metadata +14 -2
@@ -1,6 +1,7 @@
1
1
  require File.join(File.dirname(__FILE__), "spec_helper")
2
2
 
3
3
  require 'yaml'
4
+ require 'json'
4
5
 
5
6
  describe "Serialization plugin" do
6
7
  before do
@@ -10,7 +11,7 @@ describe "Serialization plugin" do
10
11
  end
11
12
  end)
12
13
  no_primary_key
13
- columns :id, :abc, :def
14
+ columns :id, :abc, :def, :ghi
14
15
  end
15
16
  MODEL_DB.reset
16
17
  end
@@ -23,6 +24,10 @@ describe "Serialization plugin" do
23
24
  @c.plugin :serialization, :marshal, :def
24
25
  @c.create(:abc => 1, :def=> 1)
25
26
  MODEL_DB.sqls.last.should =~ /INSERT INTO items \((abc, def|def, abc)\) VALUES \(('--- 1\n', 'BAhpBg==\n'|'BAhpBg==\n', '--- 1\n')\)/
27
+
28
+ @c.plugin :serialization, :json, :ghi
29
+ @c.create(:ghi => [123])
30
+ MODEL_DB.sqls.last.should =~ /INSERT INTO items \((ghi)\) VALUES \('\[123\]'\)/
26
31
  end
27
32
 
28
33
  it "should allow serializing attributes to yaml" do
@@ -57,6 +62,18 @@ describe "Serialization plugin" do
57
62
  "INSERT INTO items (abc) VALUES ('#{x}')", \
58
63
  ]
59
64
  end
65
+
66
+ it "should allow serializing attributes to json" do
67
+ @c.plugin :serialization, :json, :ghi
68
+ @c.create(:ghi => [1])
69
+ @c.create(:ghi => ["hello"])
70
+
71
+ x = JSON.generate ["hello"]
72
+ MODEL_DB.sqls.should == [ \
73
+ "INSERT INTO items (ghi) VALUES ('[1]')", \
74
+ "INSERT INTO items (ghi) VALUES ('#{x}')", \
75
+ ]
76
+ end
60
77
 
61
78
  it "should translate values to and from yaml serialization format using accessor methods" do
62
79
  @c.set_primary_key :id
@@ -102,6 +119,29 @@ describe "Serialization plugin" do
102
119
  MODEL_DB.sqls.should == ["UPDATE items SET abc = '#{[Marshal.dump(23)].pack('m')}' WHERE (id = 1)",
103
120
  "INSERT INTO items (abc) VALUES ('#{[Marshal.dump([1, 2, 3])].pack('m')}')"]
104
121
  end
122
+
123
+ it "should translate values to and from json serialization format using accessor methods" do
124
+ @c.set_primary_key :id
125
+ @c.plugin :serialization, :json, :abc, :def
126
+
127
+ ds = @c.dataset
128
+ def ds.fetch_rows(sql, &block)
129
+ block.call(:id => 1, :abc => JSON.generate([1]), :def => JSON.generate(["hello"]))
130
+ end
131
+
132
+ o = @c.first
133
+ o.id.should == 1
134
+ o.abc.should == [1]
135
+ o.abc.should == [1]
136
+ o.def.should == ["hello"]
137
+ o.def.should == ["hello"]
138
+
139
+ o.update(:abc => [23])
140
+ @c.create(:abc => [1,2,3])
141
+
142
+ MODEL_DB.sqls.should == ["UPDATE items SET abc = '#{JSON.generate([23])}' WHERE (id = 1)",
143
+ "INSERT INTO items (abc) VALUES ('#{JSON.generate([1,2,3])}')"]
144
+ end
105
145
 
106
146
  it "should copy serialization formats and columns to subclasses" do
107
147
  @c.set_primary_key :id
@@ -143,4 +183,27 @@ describe "Serialization plugin" do
143
183
  lambda{o.send(:serialize_value, :abc, 1)}.should raise_error(Sequel::Error)
144
184
  lambda{o.send(:deserialize_value, :abc, "--- hello\n")}.should raise_error(Sequel::Error)
145
185
  end
186
+
187
+ it "should add the accessors to a module included in the class, so they can be easily overridden" do
188
+ @c.class_eval do
189
+ def abc
190
+ "#{super}-blah"
191
+ end
192
+ end
193
+ @c.plugin :serialization, :yaml, :abc
194
+ o = @c.load(:abc => "--- 1\n")
195
+ o.abc.should == "1-blah"
196
+ end
197
+
198
+ it "should call super to get the deserialized value from a previous accessor" do
199
+ m = Module.new do
200
+ def abc
201
+ "--- #{@values[:abc]*3}\n"
202
+ end
203
+ end
204
+ @c.send(:include, m)
205
+ @c.plugin :serialization, :yaml, :abc
206
+ o = @c.load(:abc => 3)
207
+ o.abc.should == 9
208
+ end
146
209
  end
@@ -0,0 +1,150 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ describe "Sequel::Plugins::Timestamps" do
4
+ before do
5
+ dc = Object.new
6
+ dc.instance_eval do
7
+ def now
8
+ '2009-08-01'
9
+ end
10
+ end
11
+ Sequel.datetime_class = dc
12
+ @c = Class.new(Sequel::Model(:t))
13
+ @c.class_eval do
14
+ columns :id, :created_at, :updated_at
15
+ plugin :timestamps
16
+ db.reset
17
+ def _refresh(ds); self end
18
+ end
19
+ end
20
+ after do
21
+ Sequel.datetime_class = Time
22
+ end
23
+
24
+ it "should set the create timestamp field on creation" do
25
+ o = @c.create
26
+ @c.db.sqls.should == ["INSERT INTO t (created_at) VALUES ('2009-08-01')"]
27
+ o.created_at.should == '2009-08-01'
28
+ end
29
+
30
+ it "should set the update timestamp field on update" do
31
+ o = @c.load(:id=>1).save
32
+ @c.db.sqls.should == ["UPDATE t SET updated_at = '2009-08-01' WHERE (id = 1)"]
33
+ o.updated_at.should == '2009-08-01'
34
+ end
35
+
36
+ it "should not update the update timestamp on creation" do
37
+ @c.create.updated_at.should == nil
38
+ end
39
+
40
+ it "should use the same value for the creation and update timestamps when creating if the :update_on_create option is given" do
41
+ @c.plugin :timestamps, :update_on_create=>true
42
+ o = @c.create
43
+ @c.db.sqls.length.should == 1
44
+ @c.db.sqls.first.should =~ /INSERT INTO t \((creat|updat)ed_at, (creat|updat)ed_at\) VALUES \('2009-08-01', '2009-08-01'\)/
45
+ o.created_at.should === o.updated_at
46
+ end
47
+
48
+ it "should allow specifying the create timestamp field via the :create option" do
49
+ c = Class.new(Sequel::Model(:t))
50
+ c.class_eval do
51
+ columns :id, :c
52
+ plugin :timestamps, :create=>:c
53
+ db.reset
54
+ def _refresh(ds); self end
55
+ end
56
+ o = c.create
57
+ c.db.sqls.should == ["INSERT INTO t (c) VALUES ('2009-08-01')"]
58
+ o.c.should == '2009-08-01'
59
+ end
60
+
61
+ it "should allow specifying the update timestamp field via the :update option" do
62
+ c = Class.new(Sequel::Model(:t))
63
+ c.class_eval do
64
+ columns :id, :u
65
+ plugin :timestamps, :update=>:u
66
+ db.reset
67
+ def _refresh(ds); self end
68
+ end
69
+ o = c.load(:id=>1).save
70
+ c.db.sqls.should == ["UPDATE t SET u = '2009-08-01' WHERE (id = 1)"]
71
+ o.u.should == '2009-08-01'
72
+ end
73
+
74
+ it "should not raise an error if the model doesn't have the timestamp columns" do
75
+ c = Class.new(Sequel::Model(:t))
76
+ c.class_eval do
77
+ columns :id, :x
78
+ plugin :timestamps
79
+ db.reset
80
+ def _refresh(ds); self end
81
+ end
82
+ c.create(:x=>2)
83
+ c.load(:id=>1, :x=>2).save
84
+ c.db.sqls.should == ["INSERT INTO t (x) VALUES (2)", "UPDATE t SET x = 2 WHERE (id = 1)"]
85
+ end
86
+
87
+ it "should not overwrite an existing create timestamp" do
88
+ o = @c.create(:created_at=>'2009-08-03')
89
+ @c.db.sqls.should == ["INSERT INTO t (created_at) VALUES ('2009-08-03')"]
90
+ o.created_at.should == '2009-08-03'
91
+ end
92
+
93
+ it "should overwrite an existing create timestamp if the :force option is used" do
94
+ @c.plugin :timestamps, :force=>true
95
+ o = @c.create(:created_at=>'2009-08-03')
96
+ @c.db.sqls.should == ["INSERT INTO t (created_at) VALUES ('2009-08-01')"]
97
+ o.created_at.should == '2009-08-01'
98
+ end
99
+
100
+ it "should have create_timestamp_field give the create timestamp field" do
101
+ @c.create_timestamp_field.should == :created_at
102
+ @c.plugin :timestamps, :create=>:c
103
+ @c.create_timestamp_field.should == :c
104
+ end
105
+
106
+ it "should have update_timestamp_field give the update timestamp field" do
107
+ @c.update_timestamp_field.should == :updated_at
108
+ @c.plugin :timestamps, :update=>:u
109
+ @c.update_timestamp_field.should == :u
110
+ end
111
+
112
+ it "should have create_timestamp_overwrite? give the whether to overwrite an existing create timestamp" do
113
+ @c.create_timestamp_overwrite?.should == false
114
+ @c.plugin :timestamps, :force=>true
115
+ @c.create_timestamp_overwrite?.should == true
116
+ end
117
+
118
+ it "should have set_update_timestamp_on_create? give whether to set the update timestamp on create" do
119
+ @c.set_update_timestamp_on_create?.should == false
120
+ @c.plugin :timestamps, :update_on_create=>true
121
+ @c.set_update_timestamp_on_create?.should == true
122
+ end
123
+
124
+ it "should work with subclasses" do
125
+ c = Class.new(@c)
126
+ o = c.create
127
+ o.created_at.should == '2009-08-01'
128
+ o.updated_at.should == nil
129
+ o = c.load(:id=>1).save
130
+ o.updated_at.should == '2009-08-01'
131
+ c.db.sqls.should == ["INSERT INTO t (created_at) VALUES ('2009-08-01')", "UPDATE t SET updated_at = '2009-08-01' WHERE (id = 1)"]
132
+ c.create(:created_at=>'2009-08-03').created_at.should == '2009-08-03'
133
+
134
+ c.class_eval do
135
+ columns :id, :c, :u
136
+ plugin :timestamps, :create=>:c, :update=>:u, :force=>true, :update_on_create=>true
137
+ end
138
+ c2 = Class.new(c)
139
+ c2.db.reset
140
+ o = c2.create
141
+ o.c.should == '2009-08-01'
142
+ o.u.should === o.c
143
+ c2.db.sqls.first.should =~ /INSERT INTO t \([cu], [cu]\) VALUES \('2009-08-01', '2009-08-01'\)/
144
+ c2.db.reset
145
+ o = c2.load(:id=>1).save
146
+ o.u.should == '2009-08-01'
147
+ c2.db.sqls.should == ["UPDATE t SET u = '2009-08-01' WHERE (id = 1)"]
148
+ c2.create(:c=>'2009-08-03').c.should == '2009-08-01'
149
+ end
150
+ end
@@ -288,4 +288,22 @@ describe "Sequel::Plugins::ValidationHelpers" do
288
288
  @user.should be_valid
289
289
  MODEL_DB.sqls.last.should == "SELECT COUNT(*) AS count FROM items WHERE ((username = '0records') AND (password = 'anothertest')) LIMIT 1"
290
290
  end
291
+
292
+ it "should support validates_unique with a block" do
293
+ @c.columns(:id, :username, :password)
294
+ @c.set_dataset MODEL_DB[:items]
295
+ @c.set_validations{validates_unique(:username){|ds| ds.filter(:active)}}
296
+ @c.dataset.extend(Module.new {
297
+ def fetch_rows (sql)
298
+ @db << sql
299
+ yield({:v => 0})
300
+ end
301
+ })
302
+
303
+ MODEL_DB.reset
304
+ @c.new(:username => "0records", :password => "anothertest").should be_valid
305
+ @c.load(:id=>3, :username => "0records", :password => "anothertest").should be_valid
306
+ MODEL_DB.sqls.should == ["SELECT COUNT(*) AS count FROM items WHERE ((username = '0records') AND active) LIMIT 1",
307
+ "SELECT COUNT(*) AS count FROM items WHERE (((username = '0records') AND active) AND (id != 3)) LIMIT 1"]
308
+ end
291
309
  end
@@ -135,6 +135,15 @@ describe Sequel::Dataset do
135
135
  @d.count.should == 1
136
136
  @d.first[:name].should == 'def'
137
137
  end
138
+
139
+ specify "should be able to truncate the table" do
140
+ @d << {:name => 'abc', :value => 123}
141
+ @d << {:name => 'abc', :value => 456}
142
+ @d << {:name => 'def', :value => 789}
143
+ @d.count.should == 3
144
+ @d.truncate.should == nil
145
+ @d.count.should == 0
146
+ end
138
147
 
139
148
  specify "should be able to literalize booleans" do
140
149
  proc {@d.literal(true)}.should_not raise_error
@@ -164,7 +173,7 @@ describe Sequel::Database do
164
173
  end
165
174
  end
166
175
 
167
- context "An SQLite dataset" do
176
+ context Sequel::Dataset do
168
177
  before do
169
178
  INTEGRATION_DB.create_table! :items do
170
179
  primary_key :id
@@ -418,4 +427,72 @@ if INTEGRATION_DB.dataset.supports_window_functions?
418
427
  [{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1111, :id=>4}, {:sum=>11111, :id=>5}, {:sum=>111111, :id=>6}]
419
428
  end
420
429
  end
421
- end
430
+ end
431
+
432
+ describe Sequel::SQL::Constants do
433
+ before do
434
+ @db = INTEGRATION_DB
435
+ @ds = @db[:constants]
436
+ @c = proc do |v|
437
+ case v
438
+ when Time
439
+ v
440
+ when DateTime, String
441
+ Time.parse(v.to_s)
442
+ else
443
+ v
444
+ end
445
+ end
446
+ @c2 = proc{|v| v.is_a?(Date) ? v : Date.parse(v) }
447
+ end
448
+ after do
449
+ @db.drop_table(:constants)
450
+ end
451
+
452
+ it "should have working CURRENT_DATE" do
453
+ @db.create_table!(:constants){Date :d}
454
+ @ds.insert(:d=>Sequel::CURRENT_DATE)
455
+ Date.today.should == @c2[@ds.get(:d)]
456
+ end
457
+
458
+ it "should have working CURRENT_TIME" do
459
+ @db.create_table!(:constants){Time :t, :only_time=>true}
460
+ @ds.insert(:t=>Sequel::CURRENT_TIME)
461
+ (Time.now - @c[@ds.get(:t)]).should be_close(0, 1)
462
+ end
463
+
464
+ it "should have working CURRENT_TIMESTAMP" do
465
+ @db.create_table!(:constants){DateTime :ts}
466
+ @ds.insert(:ts=>Sequel::CURRENT_TIMESTAMP)
467
+ (Time.now - @c[@ds.get(:ts)]).should be_close(0, 1)
468
+ end
469
+ end
470
+
471
+ describe "Sequel::Dataset#import and #multi_insert" do
472
+ before do
473
+ @db = INTEGRATION_DB
474
+ @db.create_table!(:imp){Integer :i}
475
+ @db.create_table!(:exp2){Integer :i}
476
+ @ids = @db[:imp].order(:i)
477
+ @eds = @db[:exp2]
478
+ end
479
+ after do
480
+ @db.drop_table(:imp, :exp2)
481
+ end
482
+
483
+ it "should import with multi_insert and an array of hashes" do
484
+ @ids.multi_insert([{:i=>10}, {:i=>20}])
485
+ @ids.all.should == [{:i=>10}, {:i=>20}]
486
+ end
487
+
488
+ it "should import with an array of arrays of values" do
489
+ @ids.import([:i], [[10], [20]])
490
+ @ids.all.should == [{:i=>10}, {:i=>20}]
491
+ end
492
+
493
+ it "should import with a dataset" do
494
+ @eds.import([:i], [[10], [20]])
495
+ @ids.import([:i], @eds)
496
+ @ids.all.should == [{:i=>10}, {:i=>20}]
497
+ end
498
+ end
@@ -293,6 +293,23 @@ describe "Database schema modifiers" do
293
293
  @db.schema(:items, :reload=>true).map{|x| x.first}.should == [:id]
294
294
  @ds.columns!.should == [:id]
295
295
  end
296
+
297
+ specify "should remove multiple columns in a single alter_table block" do
298
+ @db.create_table!(:items) do
299
+ primary_key :id
300
+ String :name
301
+ Integer :number
302
+ end
303
+ @ds.insert(:number=>10)
304
+ @db.schema(:items, :reload=>true).map{|x| x.first}.should == [:id, :name, :number]
305
+ @ds.columns!.should == [:id, :name, :number]
306
+ @db.alter_table(:items) do
307
+ drop_column :name
308
+ drop_column :number
309
+ end
310
+ @db.schema(:items, :reload=>true).map{|x| x.first}.should == [:id]
311
+ @ds.columns!.should == [:id]
312
+ end
296
313
  end
297
314
 
298
315
  if INTEGRATION_DB.respond_to?(:tables)
@@ -0,0 +1,55 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper.rb')
2
+
3
+ describe "Sequel timezone support" do
4
+ def test_timezone
5
+ t = Time.now
6
+ @db[:t].insert(t)
7
+ t2 = @db[:t].single_value
8
+ t2 = Sequel.database_to_application_timestamp(t2.to_s) unless t2.is_a?(Time)
9
+ (t2 - t).should be_close(0, 2)
10
+ t2.utc_offset.should == 0 if Sequel.application_timezone == :utc
11
+ t2.utc_offset.should == t.getlocal.utc_offset if Sequel.application_timezone == :local
12
+ @db[:t].delete
13
+
14
+ dt = DateTime.now
15
+ Sequel.datetime_class = DateTime
16
+ @db[:t].insert(dt)
17
+ dt2 = @db[:t].single_value
18
+ dt2 = Sequel.database_to_application_timestamp(dt2.to_s) unless dt2.is_a?(DateTime)
19
+ (dt2 - dt).should be_close(0, 0.00002)
20
+ dt2.offset.should == 0 if Sequel.application_timezone == :utc
21
+ dt2.offset.should == dt.offset if Sequel.application_timezone == :local
22
+ end
23
+
24
+ before do
25
+ @db = INTEGRATION_DB
26
+ @db.create_table!(:t){DateTime :t}
27
+ end
28
+ after do
29
+ @db.drop_table(:t)
30
+ Sequel.default_timezone = nil
31
+ Sequel.datetime_class = Time
32
+ end
33
+
34
+ specify "should support using UTC for database storage and local time for the application" do
35
+ Sequel.database_timezone = :utc
36
+ Sequel.application_timezone = :local
37
+ test_timezone
38
+ end
39
+
40
+ specify "should support using local time for database storage and UTC for the application" do
41
+ Sequel.database_timezone = :local
42
+ Sequel.application_timezone = :utc
43
+ test_timezone
44
+ end
45
+
46
+ specify "should support using UTC for both database storage and for application" do
47
+ Sequel.default_timezone = :utc
48
+ test_timezone
49
+ end
50
+
51
+ specify "should support using local time for both database storage and for application" do
52
+ Sequel.default_timezone = :local
53
+ test_timezone
54
+ end
55
+ end