sequel 3.3.0 → 3.4.0

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