sequel 0.4.4.2 → 0.4.5
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.
- data/CHANGELOG +971 -953
- data/Rakefile +58 -44
- data/lib/sequel/database.rb +60 -2
- data/lib/sequel/dataset.rb +1 -0
- data/lib/sequel/dataset/sql.rb +7 -2
- data/lib/sequel/model.rb +22 -14
- data/lib/sequel/model/base.rb +1 -0
- data/lib/sequel/model/record.rb +2 -0
- data/lib/sequel/model/schema.rb +1 -1
- data/lib/sequel/model/validations.rb +117 -0
- data/lib/sequel/schema/schema_generator.rb +8 -0
- data/lib/sequel/schema/schema_sql.rb +2 -0
- data/spec/database_spec.rb +15 -0
- data/spec/dataset_spec.rb +9 -0
- data/spec/model/base_spec.rb +148 -0
- data/spec/model/caching_spec.rb +148 -0
- data/spec/model/hooks_spec.rb +105 -0
- data/spec/model/plugins_spec.rb +59 -0
- data/spec/model/record_spec.rb +360 -0
- data/spec/model/relations_spec.rb +148 -0
- data/spec/model/schema_spec.rb +80 -0
- data/spec/model/validations_spec.rb +292 -0
- data/spec/model_spec.rb +331 -1102
- data/spec/rcov.opts +4 -0
- data/spec/spec.opts +1 -4
- data/spec/spec_helper.rb +3 -2
- metadata +15 -4
@@ -102,6 +102,14 @@ module Sequel
|
|
102
102
|
}
|
103
103
|
end
|
104
104
|
|
105
|
+
def set_column_default(name, default)
|
106
|
+
@operations << {
|
107
|
+
:op => :set_column_default,
|
108
|
+
:name => name,
|
109
|
+
:default => default
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
105
113
|
def add_index(columns, opts = {})
|
106
114
|
columns = [columns] unless columns.is_a?(Array)
|
107
115
|
@operations << {
|
@@ -115,6 +115,8 @@ module Sequel
|
|
115
115
|
"ALTER TABLE #{table} RENAME COLUMN #{literal(op[:name])} TO #{literal(op[:new_name])}"
|
116
116
|
when :set_column_type
|
117
117
|
"ALTER TABLE #{table} ALTER COLUMN #{literal(op[:name])} TYPE #{op[:type]}"
|
118
|
+
when :set_column_default
|
119
|
+
"ALTER TABLE #{table} ALTER COLUMN #{literal(op[:name])} SET DEFAULT #{literal(op[:default])}"
|
118
120
|
when :add_index
|
119
121
|
index_definition_sql(table, op)
|
120
122
|
when :drop_index
|
data/spec/database_spec.rb
CHANGED
@@ -240,6 +240,7 @@ context "Database#alter_table" do
|
|
240
240
|
drop_column :bbb
|
241
241
|
rename_column :ccc, :ddd
|
242
242
|
set_column_type :eee, :integer
|
243
|
+
set_column_default :hhh, 'abcd'
|
243
244
|
|
244
245
|
add_index :fff, :unique => true
|
245
246
|
drop_index :ggg
|
@@ -250,6 +251,7 @@ context "Database#alter_table" do
|
|
250
251
|
'ALTER TABLE xyz DROP COLUMN bbb',
|
251
252
|
'ALTER TABLE xyz RENAME COLUMN ccc TO ddd',
|
252
253
|
'ALTER TABLE xyz ALTER COLUMN eee TYPE integer',
|
254
|
+
"ALTER TABLE xyz ALTER COLUMN hhh SET DEFAULT 'abcd'",
|
253
255
|
|
254
256
|
'CREATE UNIQUE INDEX xyz_fff_index ON xyz (fff)',
|
255
257
|
'DROP INDEX xyz_ggg_index'
|
@@ -309,6 +311,19 @@ context "Database#set_column_type" do
|
|
309
311
|
end
|
310
312
|
end
|
311
313
|
|
314
|
+
context "Database#set_column_default" do
|
315
|
+
setup do
|
316
|
+
@db = DummyDatabase.new
|
317
|
+
end
|
318
|
+
|
319
|
+
specify "should construct proper SQL" do
|
320
|
+
@db.set_column_default :test, :name, 'zyx'
|
321
|
+
@db.sqls.should == [
|
322
|
+
"ALTER TABLE test ALTER COLUMN name SET DEFAULT 'zyx'"
|
323
|
+
]
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
312
327
|
context "Database#add_index" do
|
313
328
|
setup do
|
314
329
|
@db = DummyDatabase.new
|
data/spec/dataset_spec.rb
CHANGED
@@ -2375,4 +2375,13 @@ context "Dataset#update_sql" do
|
|
2375
2375
|
specify "should accept strings" do
|
2376
2376
|
@ds.update_sql("a = b").should == "UPDATE items SET a = b"
|
2377
2377
|
end
|
2378
|
+
|
2379
|
+
specify "should accept hash with string keys" do
|
2380
|
+
@ds.update_sql('c' => 'd').should == "UPDATE items SET c = 'd'"
|
2381
|
+
end
|
2382
|
+
|
2383
|
+
specify "should accept array subscript references" do
|
2384
|
+
@ds.update_sql((:day|1) => 'd').should == "UPDATE items SET day[1] = 'd'"
|
2385
|
+
end
|
2386
|
+
|
2378
2387
|
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
describe "Model attribute setters" do
|
2
|
+
|
3
|
+
before(:each) do
|
4
|
+
MODEL_DB.reset
|
5
|
+
|
6
|
+
@c = Class.new(Sequel::Model(:items)) do
|
7
|
+
def columns
|
8
|
+
[:id, :x, :y]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should mark the column value as changed" do
|
14
|
+
o = @c.new
|
15
|
+
o.changed_columns.should == []
|
16
|
+
|
17
|
+
o.x = 2
|
18
|
+
o.changed_columns.should == [:x]
|
19
|
+
|
20
|
+
o.y = 3
|
21
|
+
o.changed_columns.should == [:x, :y]
|
22
|
+
|
23
|
+
o.changed_columns.clear
|
24
|
+
|
25
|
+
o[:x] = 2
|
26
|
+
o.changed_columns.should == [:x]
|
27
|
+
|
28
|
+
o[:y] = 3
|
29
|
+
o.changed_columns.should == [:x, :y]
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "Model#serialize" do
|
35
|
+
|
36
|
+
before(:each) do
|
37
|
+
MODEL_DB.reset
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should translate values to YAML when creating records" do
|
41
|
+
@c = Class.new(Sequel::Model(:items)) do
|
42
|
+
no_primary_key
|
43
|
+
serialize :abc
|
44
|
+
end
|
45
|
+
|
46
|
+
@c.create(:abc => 1)
|
47
|
+
@c.create(:abc => "hello")
|
48
|
+
|
49
|
+
MODEL_DB.sqls.should == [ \
|
50
|
+
"INSERT INTO items (abc) VALUES ('--- 1\n')", \
|
51
|
+
"INSERT INTO items (abc) VALUES ('--- hello\n')", \
|
52
|
+
]
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should support calling after the class is defined" do
|
56
|
+
@c = Class.new(Sequel::Model(:items)) do
|
57
|
+
no_primary_key
|
58
|
+
end
|
59
|
+
|
60
|
+
@c.serialize :def
|
61
|
+
|
62
|
+
@c.create(:def => 1)
|
63
|
+
@c.create(:def => "hello")
|
64
|
+
|
65
|
+
MODEL_DB.sqls.should == [ \
|
66
|
+
"INSERT INTO items (def) VALUES ('--- 1\n')", \
|
67
|
+
"INSERT INTO items (def) VALUES ('--- hello\n')", \
|
68
|
+
]
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should support using the Marshal format" do
|
72
|
+
@c = Class.new(Sequel::Model(:items)) do
|
73
|
+
no_primary_key
|
74
|
+
serialize :abc, :format => :marshal
|
75
|
+
end
|
76
|
+
|
77
|
+
@c.create(:abc => 1)
|
78
|
+
@c.create(:abc => "hello")
|
79
|
+
|
80
|
+
MODEL_DB.sqls.should == [ \
|
81
|
+
"INSERT INTO items (abc) VALUES ('\004\bi\006')", \
|
82
|
+
"INSERT INTO items (abc) VALUES ('\004\b\"\nhello')", \
|
83
|
+
]
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should translate values to and from YAML using accessor methods" do
|
87
|
+
@c = Class.new(Sequel::Model(:items)) do
|
88
|
+
serialize :abc, :def
|
89
|
+
end
|
90
|
+
|
91
|
+
ds = @c.dataset
|
92
|
+
ds.extend(Module.new {
|
93
|
+
attr_accessor :raw
|
94
|
+
|
95
|
+
def fetch_rows(sql, &block)
|
96
|
+
block.call(@raw)
|
97
|
+
end
|
98
|
+
|
99
|
+
@@sqls = nil
|
100
|
+
|
101
|
+
def insert(*args)
|
102
|
+
@@sqls = insert_sql(*args)
|
103
|
+
end
|
104
|
+
|
105
|
+
def update(*args)
|
106
|
+
@@sqls = update_sql(*args)
|
107
|
+
end
|
108
|
+
|
109
|
+
def sqls
|
110
|
+
@@sqls
|
111
|
+
end
|
112
|
+
|
113
|
+
def columns
|
114
|
+
[:id, :abc, :def]
|
115
|
+
end
|
116
|
+
}
|
117
|
+
)
|
118
|
+
|
119
|
+
ds.raw = {:id => 1, :abc => "--- 1\n", :def => "--- hello\n"}
|
120
|
+
o = @c.first
|
121
|
+
o.id.should == 1
|
122
|
+
o.abc.should == 1
|
123
|
+
o.def.should == "hello"
|
124
|
+
|
125
|
+
o.set(:abc => 23)
|
126
|
+
ds.sqls.should == "UPDATE items SET abc = '#{23.to_yaml}' WHERE (id = 1)"
|
127
|
+
|
128
|
+
ds.raw = {:id => 1, :abc => "--- 1\n", :def => "--- hello\n"}
|
129
|
+
o = @c.create(:abc => [1, 2, 3])
|
130
|
+
ds.sqls.should == "INSERT INTO items (abc) VALUES ('#{[1, 2, 3].to_yaml}')"
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
describe Sequel::Model, "super_dataset" do
|
136
|
+
|
137
|
+
before(:each) do
|
138
|
+
MODEL_DB.reset
|
139
|
+
class SubClass < Sequel::Model(:items) ; end
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should call the superclass's dataset" do
|
143
|
+
SubClass.should_receive(:superclass).exactly(3).times.and_return(Sequel::Model(:items))
|
144
|
+
Sequel::Model(:items).should_receive(:dataset)
|
145
|
+
SubClass.super_dataset
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
describe Sequel::Model, "caching" do
|
2
|
+
|
3
|
+
before(:each) do
|
4
|
+
MODEL_DB.reset
|
5
|
+
|
6
|
+
@cache_class = Class.new(Hash) do
|
7
|
+
attr_accessor :ttl
|
8
|
+
def set(k, v, ttl); self[k] = v; @ttl = ttl; end
|
9
|
+
def get(k); self[k]; end
|
10
|
+
end
|
11
|
+
cache = @cache_class.new
|
12
|
+
@cache = cache
|
13
|
+
|
14
|
+
@c = Class.new(Sequel::Model(:items)) do
|
15
|
+
set_cache cache
|
16
|
+
|
17
|
+
def self.columns
|
18
|
+
[:name, :id]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
$cache_dataset_row = {:name => 'sharon', :id => 1}
|
23
|
+
@dataset = @c.dataset
|
24
|
+
$sqls = []
|
25
|
+
@dataset.extend(Module.new {
|
26
|
+
def fetch_rows(sql)
|
27
|
+
$sqls << sql
|
28
|
+
yield $cache_dataset_row
|
29
|
+
end
|
30
|
+
|
31
|
+
def update(values)
|
32
|
+
$sqls << update_sql(values)
|
33
|
+
$cache_dataset_row.merge!(values)
|
34
|
+
end
|
35
|
+
|
36
|
+
def delete
|
37
|
+
$sqls << delete_sql
|
38
|
+
end
|
39
|
+
})
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should set the model's cache store" do
|
43
|
+
@c.cache_store.should be(@cache)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should have a default ttl of 3600" do
|
47
|
+
@c.cache_ttl.should == 3600
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should take a ttl option" do
|
51
|
+
@c.set_cache @cache, :ttl => 1234
|
52
|
+
@c.cache_ttl.should == 1234
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should offer a set_cache_ttl method for setting the ttl" do
|
56
|
+
@c.cache_ttl.should == 3600
|
57
|
+
@c.set_cache_ttl 1234
|
58
|
+
@c.cache_ttl.should == 1234
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should generate a cache key appropriate to the class" do
|
62
|
+
m = @c.new
|
63
|
+
m.values[:id] = 1
|
64
|
+
m.cache_key.should == "#{m.class}:1"
|
65
|
+
|
66
|
+
# custom primary key
|
67
|
+
@c.set_primary_key :ttt
|
68
|
+
m = @c.new
|
69
|
+
m.values[:ttt] = 333
|
70
|
+
m.cache_key.should == "#{m.class}:333"
|
71
|
+
|
72
|
+
# composite primary key
|
73
|
+
@c.set_primary_key [:a, :b, :c]
|
74
|
+
m = @c.new
|
75
|
+
m.values[:a] = 123
|
76
|
+
m.values[:c] = 456
|
77
|
+
m.values[:b] = 789
|
78
|
+
m.cache_key.should == "#{m.class}:123,789,456"
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should raise error if attempting to generate cache_key and primary key value is null" do
|
82
|
+
m = @c.new
|
83
|
+
proc {m.cache_key}.should raise_error(Sequel::Error)
|
84
|
+
|
85
|
+
m.values[:id] = 1
|
86
|
+
proc {m.cache_key}.should_not raise_error(Sequel::Error)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should set the cache when reading from the database" do
|
90
|
+
$sqls.should == []
|
91
|
+
@cache.should be_empty
|
92
|
+
|
93
|
+
m = @c[1]
|
94
|
+
$sqls.should == ['SELECT * FROM items WHERE (id = 1) LIMIT 1']
|
95
|
+
m.values.should == $cache_dataset_row
|
96
|
+
@cache[m.cache_key].should == m
|
97
|
+
|
98
|
+
# read from cache
|
99
|
+
m2 = @c[1]
|
100
|
+
$sqls.should == ['SELECT * FROM items WHERE (id = 1) LIMIT 1']
|
101
|
+
m2.should == m
|
102
|
+
m2.values.should == $cache_dataset_row
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should delete the cache when writing to the database" do
|
106
|
+
# fill the cache
|
107
|
+
m = @c[1]
|
108
|
+
@cache[m.cache_key].should == m
|
109
|
+
|
110
|
+
m.set(:name => 'tutu')
|
111
|
+
@cache.has_key?(m.cache_key).should be_false
|
112
|
+
$sqls.last.should == "UPDATE items SET name = 'tutu' WHERE (id = 1)"
|
113
|
+
|
114
|
+
m = @c[1]
|
115
|
+
@cache[m.cache_key].should == m
|
116
|
+
m.name = 'hey'
|
117
|
+
m.save
|
118
|
+
@cache.has_key?(m.cache_key).should be_false
|
119
|
+
$sqls.last.should == "UPDATE items SET name = 'hey', id = 1 WHERE (id = 1)"
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should delete the cache when deleting the record" do
|
123
|
+
# fill the cache
|
124
|
+
m = @c[1]
|
125
|
+
@cache[m.cache_key].should == m
|
126
|
+
|
127
|
+
m.delete
|
128
|
+
@cache.has_key?(m.cache_key).should be_false
|
129
|
+
$sqls.last.should == "DELETE FROM items WHERE (id = 1)"
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should support #[] as a shortcut to #find with hash" do
|
133
|
+
m = @c[:id => 3]
|
134
|
+
@cache[m.cache_key].should be_nil
|
135
|
+
$sqls.last.should == "SELECT * FROM items WHERE (id = 3) LIMIT 1"
|
136
|
+
|
137
|
+
m = @c[1]
|
138
|
+
@cache[m.cache_key].should == m
|
139
|
+
$sqls.should == ["SELECT * FROM items WHERE (id = 3) LIMIT 1", \
|
140
|
+
"SELECT * FROM items WHERE (id = 1) LIMIT 1"]
|
141
|
+
|
142
|
+
@c[:id => 4]
|
143
|
+
$sqls.should == ["SELECT * FROM items WHERE (id = 3) LIMIT 1", \
|
144
|
+
"SELECT * FROM items WHERE (id = 1) LIMIT 1", \
|
145
|
+
"SELECT * FROM items WHERE (id = 4) LIMIT 1"]
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
describe Sequel::Model, "hooks" do
|
2
|
+
|
3
|
+
before do
|
4
|
+
MODEL_DB.reset
|
5
|
+
Sequel::Model.hooks.clear
|
6
|
+
|
7
|
+
@hooks = %w[
|
8
|
+
before_save before_create before_update before_destroy
|
9
|
+
after_save after_create after_update after_destroy
|
10
|
+
].select { |hook| !hook.empty? }
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should have hooks for everything" do
|
14
|
+
Sequel::Model.methods.should include('hooks')
|
15
|
+
Sequel::Model.methods.should include(*@hooks)
|
16
|
+
@hooks.each do |hook|
|
17
|
+
Sequel::Model.hooks[hook.to_sym].should be_an_instance_of(Array)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should be inherited" do
|
22
|
+
pending 'soon'
|
23
|
+
|
24
|
+
@hooks.each do |hook|
|
25
|
+
Sequel::Model.send(hook.to_sym) { nil }
|
26
|
+
end
|
27
|
+
|
28
|
+
model = Class.new Sequel::Model(:models)
|
29
|
+
model.hooks.should == Sequel::Model.hooks
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should run hooks" do
|
33
|
+
pending 'soon'
|
34
|
+
|
35
|
+
test = mock 'Test'
|
36
|
+
test.should_receive(:run).exactly(@hooks.length)
|
37
|
+
|
38
|
+
@hooks.each do |hook|
|
39
|
+
Sequel::Model.send(hook.to_sym) { test.run }
|
40
|
+
end
|
41
|
+
|
42
|
+
model = Class.new Sequel::Model(:models)
|
43
|
+
model.hooks.should == Sequel::Model.hooks
|
44
|
+
|
45
|
+
model_instance = model.new
|
46
|
+
@hooks.each { |hook| model_instance.run_hooks(hook) }
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should run hooks around save and create" do
|
50
|
+
pending 'test execution'
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should run hooks around save and update" do
|
54
|
+
pending 'test execution'
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should run hooks around delete" do
|
58
|
+
pending 'test execution'
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "Model.after_create" do
|
64
|
+
|
65
|
+
before(:each) do
|
66
|
+
MODEL_DB.reset
|
67
|
+
|
68
|
+
@c = Class.new(Sequel::Model(:items)) do
|
69
|
+
def columns
|
70
|
+
[:id, :x, :y]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
ds = @c.dataset
|
75
|
+
def ds.insert(*args)
|
76
|
+
super(*args)
|
77
|
+
1
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should be called after creation" do
|
82
|
+
s = []
|
83
|
+
|
84
|
+
@c.after_create do
|
85
|
+
s = MODEL_DB.sqls.dup
|
86
|
+
end
|
87
|
+
|
88
|
+
n = @c.create(:x => 1)
|
89
|
+
MODEL_DB.sqls.should == ["INSERT INTO items (x) VALUES (1)", "SELECT * FROM items WHERE (id = 1) LIMIT 1"]
|
90
|
+
s.should == ["INSERT INTO items (x) VALUES (1)", "SELECT * FROM items WHERE (id = 1) LIMIT 1"]
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should allow calling save in the hook" do
|
94
|
+
@c.after_create do
|
95
|
+
values.delete(:x)
|
96
|
+
self.id = 2
|
97
|
+
save
|
98
|
+
end
|
99
|
+
|
100
|
+
n = @c.create(:id => 1)
|
101
|
+
MODEL_DB.sqls.should == ["INSERT INTO items (id) VALUES (1)", "SELECT * FROM items WHERE (id = 1) LIMIT 1", "UPDATE items SET id = 2 WHERE (id = 1)"]
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|