sequel 0.4.4.2 → 0.4.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,80 @@
|
|
1
|
+
describe Sequel::Model, "table_exists?" do
|
2
|
+
|
3
|
+
before(:each) do
|
4
|
+
MODEL_DB.reset
|
5
|
+
@model = Class.new(Sequel::Model(:items))
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should get the table name and question the model's db if table_exists?" do
|
9
|
+
@model.should_receive(:table_name).and_return(:items)
|
10
|
+
@model.db.should_receive(:table_exists?)
|
11
|
+
@model.table_exists?
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
describe Sequel::Model, "create_table" do
|
17
|
+
|
18
|
+
before(:each) do
|
19
|
+
MODEL_DB.reset
|
20
|
+
@model = Class.new(Sequel::Model(:items))
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should get the create table SQL list from the db and execute it line by line" do
|
24
|
+
#db.create_table_sql_list(table_name, *schema.create_info).each {|s| db << s}
|
25
|
+
@model.should_receive(:table_name).and_return(:items)
|
26
|
+
@model.schema.should_receive(:create_info)
|
27
|
+
@model.db.should_receive(:create_table_sql_list)
|
28
|
+
pending("Finish specing this")
|
29
|
+
@model.create_table
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
describe Sequel::Model, "drop_table" do
|
35
|
+
|
36
|
+
before(:each) do
|
37
|
+
MODEL_DB.reset
|
38
|
+
@model = Class.new(Sequel::Model(:items))
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should get the drop table SQL for the associated table and then execute the SQL." do
|
42
|
+
@model.should_receive(:table_name).and_return(:items)
|
43
|
+
@model.db.should_receive(:drop_table_sql).with(:items)
|
44
|
+
@model.db.should_receive(:execute).and_return(:true)
|
45
|
+
@model.drop_table
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
describe Sequel::Model, "create_table!" do
|
51
|
+
|
52
|
+
before(:each) do
|
53
|
+
MODEL_DB.reset
|
54
|
+
@model = Class.new(Sequel::Model(:items))
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should drop table if it exists and then create the table" do
|
58
|
+
@model.should_receive(:table_exists?).and_return(true)
|
59
|
+
@model.should_receive(:drop_table).and_return(true)
|
60
|
+
@model.should_receive(:create_table).and_return(true)
|
61
|
+
|
62
|
+
@model.create_table!
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
describe Sequel::Model, "recreate_table" do
|
68
|
+
|
69
|
+
before(:each) do
|
70
|
+
MODEL_DB.reset
|
71
|
+
@model = Class.new(Sequel::Model(:items))
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should raise a depreciation warning and then call create_table!" do
|
75
|
+
@model.should_receive(:warn)
|
76
|
+
@model.should_receive(:create_table!).and_return(true)
|
77
|
+
@model.recreate_table
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
@@ -0,0 +1,292 @@
|
|
1
|
+
describe Sequel::Model, "Validations" do
|
2
|
+
|
3
|
+
before(:all) do
|
4
|
+
class Person < Sequel::Model(:people)
|
5
|
+
def columns
|
6
|
+
[:id,:name,:first_name,:last_name,:middle_name,:initials,:age, :terms]
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class Smurf < Person
|
11
|
+
end
|
12
|
+
|
13
|
+
class Cow < Sequel::Model(:cows)
|
14
|
+
def columns
|
15
|
+
[:id, :name, :got_milk]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class User < Sequel::Model(:users)
|
20
|
+
def columns
|
21
|
+
[:id, :username, :password]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Address < Sequel::Model(:addresses)
|
26
|
+
def columns
|
27
|
+
[:id, :zip_code]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should have a hook before validating" do
|
33
|
+
class Person < Sequel::Model(:people)
|
34
|
+
before_validation do
|
35
|
+
self.name = "default name"
|
36
|
+
end
|
37
|
+
validations.clear
|
38
|
+
validates_presence_of :name
|
39
|
+
end
|
40
|
+
|
41
|
+
@person = Person.new
|
42
|
+
@person.valid?.should be_true
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should include errors from other models" do
|
46
|
+
pending("Waiting for Wayne's amazing associations!")
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should validate the acceptance of a column" do
|
50
|
+
class Cow < Sequel::Model(:cows)
|
51
|
+
validations.clear
|
52
|
+
validates_acceptance_of :got_milk
|
53
|
+
end
|
54
|
+
|
55
|
+
@cow = Cow.new
|
56
|
+
@cow.valid?.should be_false
|
57
|
+
@cow.errors.on(:got_milk).should == "must be accepted"
|
58
|
+
|
59
|
+
@cow.got_milk = "true"
|
60
|
+
@cow.valid?.should be_true
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should validate the confirmation of a column" do
|
64
|
+
class User < Sequel::Model(:users)
|
65
|
+
def password_confirmation
|
66
|
+
"test"
|
67
|
+
end
|
68
|
+
|
69
|
+
validations.clear
|
70
|
+
validates_confirmation_of :password
|
71
|
+
end
|
72
|
+
|
73
|
+
@user = User.new
|
74
|
+
@user.valid?.should be_false
|
75
|
+
@user.errors.on(:password).should == "doesn't match confirmation"
|
76
|
+
|
77
|
+
@user.password = "test"
|
78
|
+
@user.valid?.should be_true
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should validate each with logic" do
|
82
|
+
class ZipCodeService; end
|
83
|
+
|
84
|
+
class Address < Sequel::Model(:addresses)
|
85
|
+
validations.clear
|
86
|
+
validates_each :zip_code, :logic => lambda { errors.add(:zip_code, "is not valid") unless ZipCodeService.allows(zip_code) }
|
87
|
+
end
|
88
|
+
|
89
|
+
@address = Address.new :zip_code => "48108"
|
90
|
+
ZipCodeService.should_receive(:allows).with("48108").and_return(false)
|
91
|
+
@address.valid?.should be_false
|
92
|
+
@address.errors.on(:zip_code).should == "is not valid"
|
93
|
+
|
94
|
+
@address2 = Address.new :zip_code => "48104"
|
95
|
+
ZipCodeService.should_receive(:allows).with("48104").and_return(true)
|
96
|
+
@address2.valid?.should be_true
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should validate format of column" do
|
100
|
+
class Person < Sequel::Model(:people)
|
101
|
+
validates_format_of :first_name, :with => /^[a-zA-Z]+$/
|
102
|
+
end
|
103
|
+
|
104
|
+
@person = Person.new :first_name => "Lancelot99"
|
105
|
+
@person.valid?.should be_false
|
106
|
+
@person = Person.new :first_name => "Anita"
|
107
|
+
@person.valid?.should be_true
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should allow for :with_exactly => /[a-zA-Z/, which wraps the supplied regex with ^<regex>$" do
|
111
|
+
pending("TODO: Add this option to Validatable#validates_format_of")
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should validate length of column" do
|
115
|
+
class Person < Sequel::Model(:people)
|
116
|
+
validations.clear
|
117
|
+
validates_length_of :first_name, :maximum => 30
|
118
|
+
validates_length_of :last_name, :minimum => 30
|
119
|
+
validates_length_of :middle_name, :within => 1..5
|
120
|
+
validates_length_of :initials, :is => 2
|
121
|
+
end
|
122
|
+
|
123
|
+
@person = Person.new(
|
124
|
+
:first_name => "Anamethatiswaytofreakinglongandwayoverthirtycharacters",
|
125
|
+
:last_name => "Alastnameunderthirtychars",
|
126
|
+
:initials => "LGC",
|
127
|
+
:middle_name => "danger"
|
128
|
+
)
|
129
|
+
|
130
|
+
@person.valid?.should be_false
|
131
|
+
@person.errors.on(:first_name).should == "is invalid"
|
132
|
+
@person.errors.on(:last_name).should == "is invalid"
|
133
|
+
@person.errors.on(:initials).should == "is invalid"
|
134
|
+
@person.errors.on(:middle_name).should == "is invalid"
|
135
|
+
|
136
|
+
@person.first_name = "Lancelot"
|
137
|
+
@person.last_name = "1234567890123456789012345678901"
|
138
|
+
@person.initials = "LC"
|
139
|
+
@person.middle_name = "Will"
|
140
|
+
@person.valid?.should be_true
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should validate numericality of column" do
|
144
|
+
class Person < Sequel::Model(:people)
|
145
|
+
validations.clear
|
146
|
+
validates_numericality_of :age
|
147
|
+
end
|
148
|
+
|
149
|
+
@person = Person.new :age => "Twenty"
|
150
|
+
@person.valid?.should be_false
|
151
|
+
@person.errors.on(:age).should == "must be a number"
|
152
|
+
|
153
|
+
@person.age = 20
|
154
|
+
@person.valid?.should be_true
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should validate the presence of a column" do
|
158
|
+
class Cow < Sequel::Model(:cows)
|
159
|
+
validations.clear
|
160
|
+
validates_presence_of :name
|
161
|
+
end
|
162
|
+
|
163
|
+
@cow = Cow.new
|
164
|
+
@cow.valid?.should be_false
|
165
|
+
@cow.errors.on(:name).should == "can't be empty"
|
166
|
+
@cow.errors.full_messages.first.should == "Name can't be empty"
|
167
|
+
|
168
|
+
@cow.name = "Betsy"
|
169
|
+
@cow.valid?.should be_true
|
170
|
+
end
|
171
|
+
|
172
|
+
it "should validate true for a column" do
|
173
|
+
class Person < Sequel::Model(:people)
|
174
|
+
validations.clear
|
175
|
+
validates_true_for :first_name, :logic => lambda { first_name == "Alison" }
|
176
|
+
end
|
177
|
+
|
178
|
+
@person = Person.new :first_name => "Nina"
|
179
|
+
@person.valid?.should be_false
|
180
|
+
@person.errors.on(:first_name).should == "is invalid"
|
181
|
+
|
182
|
+
@person.first_name = "Alison"
|
183
|
+
@person.valid?.should be_true
|
184
|
+
end
|
185
|
+
|
186
|
+
it "should have a validates block that calls multple validations" do
|
187
|
+
class Person < Sequel::Model(:people)
|
188
|
+
validations.clear
|
189
|
+
validates do
|
190
|
+
format_of :first_name, :with => /^[a-zA-Z]+$/
|
191
|
+
length_of :first_name, :maximum => 30
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
Person.validations.length.should eql(2)
|
196
|
+
|
197
|
+
@person = Person.new :first_name => "Lancelot99"
|
198
|
+
@person.valid?.should be_false
|
199
|
+
|
200
|
+
@person2 = Person.new :first_name => "Wayne"
|
201
|
+
@person2.valid?.should be_true
|
202
|
+
end
|
203
|
+
|
204
|
+
it "should require and include the validatable gem" do
|
205
|
+
Gem.loaded_specs["validatable"].should_not be_nil
|
206
|
+
Sequel::Model.should respond_to(:validates_format_of) # validatable gem
|
207
|
+
Sequel::Model.should respond_to(:validations) # Validations module
|
208
|
+
end
|
209
|
+
|
210
|
+
it "should description" do
|
211
|
+
Sequel::Model.should_receive(:require).with("validatable").and_raise(LoadError)
|
212
|
+
STDERR.should_receive(:puts)
|
213
|
+
load File.join(File.dirname(__FILE__), "../../lib/sequel/model/validations.rb")
|
214
|
+
end
|
215
|
+
|
216
|
+
it "should allow 'longhand' validations direcly within the model." do
|
217
|
+
lambda {
|
218
|
+
class Person < Sequel::Model(:people)
|
219
|
+
validations.clear
|
220
|
+
validates_length_of :first_name, :maximum => 30
|
221
|
+
end
|
222
|
+
}.should_not raise_error
|
223
|
+
Person.validations.length.should eql(1)
|
224
|
+
end
|
225
|
+
|
226
|
+
it "should validates do should allow shorthand method for every longhand validates_* method" do
|
227
|
+
class Person
|
228
|
+
validations.clear
|
229
|
+
validates do
|
230
|
+
format_of :first_name, :with => /^[a-zA-Z]+$/
|
231
|
+
length_of :first_name, :maximum => 30
|
232
|
+
presence_of :first_name
|
233
|
+
numericality_of :age
|
234
|
+
acceptance_of :terms
|
235
|
+
confirmation_of :password
|
236
|
+
true_for :first_name, :logic => lambda { first_name == "Alison" }
|
237
|
+
#validates_each :last_name, :logic => lambda { errors.add(:zip_code, "is not valid") unless ZipCodeService.allows(zip_code) }
|
238
|
+
#base
|
239
|
+
end
|
240
|
+
|
241
|
+
# Now check to make sure that each validation exists in the model's validations.
|
242
|
+
end
|
243
|
+
pending("finish this spec for each case")
|
244
|
+
end
|
245
|
+
|
246
|
+
it "should define a has_validations? method which returns true if the model has validations, false otherwise" do
|
247
|
+
class Person < Sequel::Model(:people)
|
248
|
+
validations.clear
|
249
|
+
validates do
|
250
|
+
format_of :first_name, :with => /\w+/
|
251
|
+
length_of :first_name, :maximum => 30
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
class Smurf < Person
|
256
|
+
validations.clear
|
257
|
+
end
|
258
|
+
|
259
|
+
Person.should have_validations
|
260
|
+
Smurf.should_not have_validations
|
261
|
+
end
|
262
|
+
|
263
|
+
end
|
264
|
+
|
265
|
+
describe Sequel::Model, "validates" do
|
266
|
+
|
267
|
+
|
268
|
+
before(:all) do
|
269
|
+
class Person < Sequel::Model(:people)
|
270
|
+
def columns
|
271
|
+
[:id,:name,:first_name,:last_name,:middle_name,:initials,:age]
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
it "should runs the validations block on the model & store as :default when only a validations block is passed in" do
|
277
|
+
pending
|
278
|
+
end
|
279
|
+
|
280
|
+
it "should store the block under the name passed in when both a name and a validations block are passed in" do
|
281
|
+
pending
|
282
|
+
end
|
283
|
+
|
284
|
+
it "should return the stored validations block corresponding to the name given, if only a name is given (no block)" do
|
285
|
+
pending
|
286
|
+
end
|
287
|
+
|
288
|
+
it "should return true or false based on if validations exist on the model if no arguments are given" do
|
289
|
+
pending
|
290
|
+
end
|
291
|
+
|
292
|
+
end
|
data/spec/model_spec.rb
CHANGED
@@ -1,11 +1,20 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__),
|
1
|
+
require File.join(File.dirname(__FILE__), "spec_helper")
|
2
2
|
|
3
3
|
Sequel::Model.db = MODEL_DB = MockDatabase.new
|
4
4
|
|
5
|
+
require File.join(File.dirname(__FILE__), "model/base_spec")
|
6
|
+
require File.join(File.dirname(__FILE__), "model/caching_spec")
|
7
|
+
require File.join(File.dirname(__FILE__), "model/hooks_spec")
|
8
|
+
require File.join(File.dirname(__FILE__), "model/record_spec")
|
9
|
+
require File.join(File.dirname(__FILE__), "model/schema_spec")
|
10
|
+
require File.join(File.dirname(__FILE__), "model/relations_spec")
|
11
|
+
require File.join(File.dirname(__FILE__), "model/plugins_spec")
|
12
|
+
require File.join(File.dirname(__FILE__), "model/validations_spec")
|
13
|
+
|
5
14
|
describe Sequel::Model do
|
6
15
|
|
7
16
|
it "should have class method aliased as model" do
|
8
|
-
Sequel::Model.instance_methods.should include(
|
17
|
+
Sequel::Model.instance_methods.should include("model")
|
9
18
|
|
10
19
|
model_a = Class.new Sequel::Model
|
11
20
|
model_a.new.model.should be(model_a)
|
@@ -13,12 +22,12 @@ describe Sequel::Model do
|
|
13
22
|
|
14
23
|
it "should be associated with a dataset" do
|
15
24
|
model_a = Class.new(Sequel::Model) { set_dataset MODEL_DB[:as] }
|
16
|
-
|
25
|
+
|
17
26
|
model_a.dataset.should be_a_kind_of(MockDataset)
|
18
27
|
model_a.dataset.opts[:from].should == [:as]
|
19
28
|
|
20
29
|
model_b = Class.new(Sequel::Model) { set_dataset MODEL_DB[:bs] }
|
21
|
-
|
30
|
+
|
22
31
|
model_b.dataset.should be_a_kind_of(MockDataset)
|
23
32
|
model_b.dataset.opts[:from].should == [:bs]
|
24
33
|
|
@@ -27,190 +36,30 @@ describe Sequel::Model do
|
|
27
36
|
|
28
37
|
end
|
29
38
|
|
30
|
-
describe Sequel::Model,
|
31
|
-
it "should default to ':id'" do
|
32
|
-
model_a = Class.new Sequel::Model
|
33
|
-
model_a.primary_key.should be_equal(:id)
|
34
|
-
end
|
35
|
-
|
36
|
-
it "should be changed through 'set_primary_key'" do
|
37
|
-
model_a = Class.new(Sequel::Model) { set_primary_key :a }
|
38
|
-
model_a.primary_key.should be_equal(:a)
|
39
|
-
end
|
40
|
-
|
41
|
-
it "should support multi argument composite keys" do
|
42
|
-
model_a = Class.new(Sequel::Model) { set_primary_key :a, :b }
|
43
|
-
model_a.primary_key.should be_eql([:a, :b])
|
44
|
-
end
|
45
|
-
|
46
|
-
it "should accept single argument composite keys" do
|
47
|
-
model_a = Class.new(Sequel::Model) { set_primary_key [:a, :b] }
|
48
|
-
model_a.primary_key.should be_eql([:a, :b])
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
describe Sequel::Model, 'w/o primary key' do
|
53
|
-
it "should return nil for primary key" do
|
54
|
-
Class.new(Sequel::Model) { no_primary_key }.primary_key.should be_nil
|
55
|
-
end
|
56
|
-
|
57
|
-
it "should raise a Sequel::Error on 'this'" do
|
58
|
-
instance = Class.new(Sequel::Model) { no_primary_key }.new
|
59
|
-
proc { instance.this }.should raise_error(Sequel::Error)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
describe Sequel::Model, 'with this' do
|
64
|
-
|
65
|
-
before { @example = Class.new Sequel::Model(:examples) }
|
66
|
-
|
67
|
-
it "should return a dataset identifying the record" do
|
68
|
-
instance = @example.new :id => 3
|
69
|
-
instance.this.sql.should be_eql("SELECT * FROM examples WHERE (id = 3) LIMIT 1")
|
70
|
-
end
|
71
|
-
|
72
|
-
it "should support arbitary primary keys" do
|
73
|
-
@example.set_primary_key :a
|
74
|
-
|
75
|
-
instance = @example.new :a => 3
|
76
|
-
instance.this.sql.should be_eql("SELECT * FROM examples WHERE (a = 3) LIMIT 1")
|
77
|
-
end
|
78
|
-
|
79
|
-
it "should support composite primary keys" do
|
80
|
-
@example.set_primary_key :x, :y
|
81
|
-
instance = @example.new :x => 4, :y => 5
|
82
|
-
|
83
|
-
parts = ['SELECT * FROM examples WHERE %s LIMIT 1',
|
84
|
-
'(x = 4) AND (y = 5)', '(y = 5) AND (x = 4)'
|
85
|
-
].map { |expr| Regexp.escape expr }
|
86
|
-
regexp = Regexp.new parts.first % "(?:#{parts[1]}|#{parts[2]})"
|
87
|
-
|
88
|
-
instance.this.sql.should match(regexp)
|
89
|
-
end
|
90
|
-
|
91
|
-
end
|
92
|
-
|
93
|
-
describe Sequel::Model, 'with hooks' do
|
94
|
-
|
95
|
-
before do
|
96
|
-
MODEL_DB.reset
|
97
|
-
Sequel::Model.hooks.clear
|
98
|
-
|
99
|
-
@hooks = %w{
|
100
|
-
before_save before_create before_update before_destroy
|
101
|
-
after_save after_create after_update after_destroy
|
102
|
-
}.select { |hook| !hook.empty? }
|
103
|
-
end
|
104
|
-
|
105
|
-
it "should have hooks for everything" do
|
106
|
-
Sequel::Model.methods.should include('hooks')
|
107
|
-
Sequel::Model.methods.should include(*@hooks)
|
108
|
-
@hooks.each do |hook|
|
109
|
-
Sequel::Model.hooks[hook.to_sym].should be_an_instance_of(Array)
|
110
|
-
end
|
111
|
-
end
|
112
|
-
it "should be inherited" do
|
113
|
-
pending 'soon'
|
114
|
-
|
115
|
-
@hooks.each do |hook|
|
116
|
-
Sequel::Model.send(hook.to_sym) { nil }
|
117
|
-
end
|
118
|
-
|
119
|
-
model = Class.new Sequel::Model(:models)
|
120
|
-
model.hooks.should == Sequel::Model.hooks
|
121
|
-
end
|
122
|
-
|
123
|
-
it "should run hooks" do
|
124
|
-
pending 'soon'
|
125
|
-
|
126
|
-
test = mock 'Test'
|
127
|
-
test.should_receive(:run).exactly(@hooks.length)
|
128
|
-
|
129
|
-
@hooks.each do |hook|
|
130
|
-
Sequel::Model.send(hook.to_sym) { test.run }
|
131
|
-
end
|
132
|
-
|
133
|
-
model = Class.new Sequel::Model(:models)
|
134
|
-
model.hooks.should == Sequel::Model.hooks
|
135
|
-
|
136
|
-
model_instance = model.new
|
137
|
-
@hooks.each { |hook| model_instance.run_hooks(hook) }
|
138
|
-
end
|
139
|
-
it "should run hooks around save and create" do
|
140
|
-
pending 'test execution'
|
141
|
-
end
|
142
|
-
it "should run hooks around save and update" do
|
143
|
-
pending 'test execution'
|
144
|
-
end
|
145
|
-
it "should run hooks around delete" do
|
146
|
-
pending 'test execution'
|
147
|
-
end
|
148
|
-
|
149
|
-
end
|
150
|
-
|
151
|
-
context "A new model instance" do
|
152
|
-
setup do
|
153
|
-
@m = Class.new(Sequel::Model) do
|
154
|
-
set_dataset MODEL_DB[:items]
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
specify "should be marked as new?" do
|
159
|
-
o = @m.new
|
160
|
-
o.should be_new
|
161
|
-
end
|
162
|
-
|
163
|
-
specify "should not be marked as new? once it is saved" do
|
164
|
-
o = @m.new(:x => 1)
|
165
|
-
o.should be_new
|
166
|
-
o.save
|
167
|
-
o.should_not be_new
|
168
|
-
end
|
169
|
-
|
170
|
-
specify "should use the last inserted id as primary key if not in values" do
|
171
|
-
d = @m.dataset
|
172
|
-
def d.insert(*args)
|
173
|
-
super
|
174
|
-
1234
|
175
|
-
end
|
176
|
-
|
177
|
-
def d.first
|
178
|
-
{:x => 1, :id => 1234}
|
179
|
-
end
|
180
|
-
|
181
|
-
o = @m.new(:x => 1)
|
182
|
-
o.save
|
183
|
-
o.id.should == 1234
|
184
|
-
|
185
|
-
o = @m.new(:x => 1, :id => 333)
|
186
|
-
o.save
|
187
|
-
o.id.should == 333
|
188
|
-
end
|
189
|
-
end
|
39
|
+
describe Sequel::Model, "dataset & schema" do
|
190
40
|
|
191
|
-
describe Sequel::Model do
|
192
41
|
before do
|
193
42
|
@model = Class.new(Sequel::Model(:items))
|
194
43
|
end
|
195
|
-
|
44
|
+
|
196
45
|
it "creates dynamic model subclass with set table name" do
|
197
46
|
@model.table_name.should == :items
|
198
47
|
end
|
199
|
-
|
48
|
+
|
200
49
|
it "defaults to primary key of id" do
|
201
50
|
@model.primary_key.should == :id
|
202
51
|
end
|
203
|
-
|
52
|
+
|
204
53
|
it "allow primary key change" do
|
205
54
|
@model.set_primary_key :ssn
|
206
55
|
@model.primary_key.should == :ssn
|
207
56
|
end
|
208
|
-
|
57
|
+
|
209
58
|
it "allows dataset change" do
|
210
59
|
@model.set_dataset(MODEL_DB[:foo])
|
211
60
|
@model.table_name.should == :foo
|
212
61
|
end
|
213
|
-
|
62
|
+
|
214
63
|
it "sets schema with implicit table name" do
|
215
64
|
@model.set_schema do
|
216
65
|
primary_key :ssn, :string
|
@@ -218,7 +67,7 @@ describe Sequel::Model do
|
|
218
67
|
@model.primary_key.should == :ssn
|
219
68
|
@model.table_name.should == :items
|
220
69
|
end
|
221
|
-
|
70
|
+
|
222
71
|
it "sets schema with explicit table name" do
|
223
72
|
@model.set_schema :foo do
|
224
73
|
primary_key :id
|
@@ -232,889 +81,158 @@ describe Sequel::Model do
|
|
232
81
|
end
|
233
82
|
end
|
234
83
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
specify "should allow reopening of descendant classes" do
|
240
|
-
proc do
|
241
|
-
eval "class DummyModelBased < Sequel::Model(:blog); end"
|
242
|
-
end.should_not raise_error
|
84
|
+
describe Sequel::Model, "constructor" do
|
85
|
+
|
86
|
+
before(:each) do
|
87
|
+
@m = Class.new(Sequel::Model)
|
243
88
|
end
|
244
|
-
end
|
245
89
|
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
90
|
+
it "should accept a hash" do
|
91
|
+
m = @m.new(:a => 1, :b => 2)
|
92
|
+
m.values.should == {:a => 1, :b => 2}
|
93
|
+
m.should be_new
|
250
94
|
end
|
251
95
|
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
96
|
+
it "should accept a block and yield itself to the block" do
|
97
|
+
block_called = false
|
98
|
+
m = @m.new {|i| block_called = true; i.should be_a_kind_of(@m); i.values[:a] = 1}
|
99
|
+
|
100
|
+
block_called.should be_true
|
101
|
+
m.values[:a].should == 1
|
256
102
|
end
|
257
103
|
|
258
|
-
specify "should be able to create rows without any values specified" do
|
259
|
-
o = @c.create
|
260
|
-
o.class.should == @c
|
261
|
-
MODEL_DB.sqls.should == ["INSERT INTO items DEFAULT VALUES", "SELECT * FROM items WHERE (id IN ('INSERT INTO items DEFAULT VALUES')) LIMIT 1"]
|
262
|
-
end
|
263
104
|
end
|
264
105
|
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
@
|
269
|
-
|
106
|
+
describe Sequel::Model, "new" do
|
107
|
+
|
108
|
+
before(:each) do
|
109
|
+
@m = Class.new(Sequel::Model) do
|
110
|
+
set_dataset MODEL_DB[:items]
|
270
111
|
end
|
271
112
|
end
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
i.values.to_hash.should == {:x => 1}
|
278
|
-
|
279
|
-
MODEL_DB.sqls.should == ['INSERT INTO items (x) VALUES (1)']
|
280
|
-
end
|
281
|
-
|
282
|
-
specify "should raise when deleting" do
|
283
|
-
o = @c.new
|
284
|
-
proc {o.delete}.should raise_error
|
113
|
+
|
114
|
+
it "should be marked as new?" do
|
115
|
+
o = @m.new
|
116
|
+
o.should be_new
|
117
|
+
o.should be_new_record
|
285
118
|
end
|
286
119
|
|
287
|
-
|
288
|
-
o = @
|
120
|
+
it "should not be marked as new? once it is saved" do
|
121
|
+
o = @m.new(:x => 1)
|
289
122
|
o.should be_new
|
290
123
|
o.save
|
291
|
-
|
124
|
+
o.should_not be_new
|
125
|
+
o.should_not be_new_record
|
292
126
|
end
|
293
|
-
end
|
294
127
|
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
specify "should translate values to YAML when creating records" do
|
301
|
-
@c = Class.new(Sequel::Model(:items)) do
|
302
|
-
no_primary_key
|
303
|
-
serialize :abc
|
128
|
+
it "should use the last inserted id as primary key if not in values" do
|
129
|
+
d = @m.dataset
|
130
|
+
def d.insert(*args)
|
131
|
+
super
|
132
|
+
1234
|
304
133
|
end
|
305
134
|
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
MODEL_DB.sqls.should == [ \
|
310
|
-
"INSERT INTO items (abc) VALUES ('--- 1\n')", \
|
311
|
-
"INSERT INTO items (abc) VALUES ('--- hello\n')", \
|
312
|
-
]
|
313
|
-
end
|
314
|
-
|
315
|
-
specify "should support calling after the class is defined" do
|
316
|
-
@c = Class.new(Sequel::Model(:items)) do
|
317
|
-
no_primary_key
|
135
|
+
def d.first
|
136
|
+
{:x => 1, :id => 1234}
|
318
137
|
end
|
319
|
-
|
320
|
-
@c.serialize :def
|
321
138
|
|
322
|
-
@
|
323
|
-
|
324
|
-
|
325
|
-
MODEL_DB.sqls.should == [ \
|
326
|
-
"INSERT INTO items (def) VALUES ('--- 1\n')", \
|
327
|
-
"INSERT INTO items (def) VALUES ('--- hello\n')", \
|
328
|
-
]
|
329
|
-
end
|
330
|
-
|
331
|
-
specify "should support using the Marshal format" do
|
332
|
-
@c = Class.new(Sequel::Model(:items)) do
|
333
|
-
no_primary_key
|
334
|
-
serialize :abc, :format => :marshal
|
335
|
-
end
|
139
|
+
o = @m.new(:x => 1)
|
140
|
+
o.save
|
141
|
+
o.id.should == 1234
|
336
142
|
|
337
|
-
@
|
338
|
-
|
339
|
-
|
340
|
-
MODEL_DB.sqls.should == [ \
|
341
|
-
"INSERT INTO items (abc) VALUES ('\004\bi\006')", \
|
342
|
-
"INSERT INTO items (abc) VALUES ('\004\b\"\nhello')", \
|
343
|
-
]
|
143
|
+
o = @m.new(:x => 1, :id => 333)
|
144
|
+
o.save
|
145
|
+
o.id.should == 333
|
344
146
|
end
|
345
|
-
|
346
|
-
specify "should translate values to and from YAML using accessor methods" do
|
347
|
-
@c = Class.new(Sequel::Model(:items)) do
|
348
|
-
serialize :abc, :def
|
349
|
-
end
|
350
|
-
|
351
|
-
ds = @c.dataset
|
352
|
-
ds.extend(Module.new {
|
353
|
-
attr_accessor :raw
|
354
|
-
|
355
|
-
def fetch_rows(sql, &block)
|
356
|
-
block.call(@raw)
|
357
|
-
end
|
358
|
-
|
359
|
-
@@sqls = nil
|
360
|
-
|
361
|
-
def insert(*args)
|
362
|
-
@@sqls = insert_sql(*args)
|
363
|
-
end
|
364
147
|
|
365
|
-
def update(*args)
|
366
|
-
@@sqls = update_sql(*args)
|
367
|
-
end
|
368
|
-
|
369
|
-
def sqls
|
370
|
-
@@sqls
|
371
|
-
end
|
372
|
-
|
373
|
-
def columns
|
374
|
-
[:id, :abc, :def]
|
375
|
-
end
|
376
|
-
})
|
377
|
-
|
378
|
-
ds.raw = {:id => 1, :abc => "--- 1\n", :def => "--- hello\n"}
|
379
|
-
o = @c.first
|
380
|
-
o.id.should == 1
|
381
|
-
o.abc.should == 1
|
382
|
-
o.def.should == "hello"
|
383
|
-
|
384
|
-
o.set(:abc => 23)
|
385
|
-
ds.sqls.should == "UPDATE items SET abc = '#{23.to_yaml}' WHERE (id = 1)"
|
386
|
-
|
387
|
-
ds.raw = {:id => 1, :abc => "--- 1\n", :def => "--- hello\n"}
|
388
|
-
o = @c.create(:abc => [1, 2, 3])
|
389
|
-
ds.sqls.should == "INSERT INTO items (abc) VALUES ('#{[1, 2, 3].to_yaml}')"
|
390
|
-
end
|
391
148
|
end
|
392
149
|
|
393
|
-
|
394
|
-
|
150
|
+
describe Sequel::Model, ".subset" do
|
151
|
+
|
152
|
+
before(:each) do
|
395
153
|
MODEL_DB.reset
|
396
154
|
|
397
|
-
@c = Class.new(Sequel::Model(:items))
|
398
|
-
def columns
|
399
|
-
[:id, :x, :y]
|
400
|
-
end
|
401
|
-
end
|
402
|
-
end
|
403
|
-
|
404
|
-
specify "should be created dynamically" do
|
405
|
-
o = @c.new
|
406
|
-
|
407
|
-
o.should_not be_respond_to(:x)
|
408
|
-
o.x.should be_nil
|
409
|
-
o.should be_respond_to(:x)
|
410
|
-
|
411
|
-
o.should_not be_respond_to(:x=)
|
412
|
-
o.x = 34
|
413
|
-
o.x.should == 34
|
414
|
-
o.should be_respond_to(:x=)
|
155
|
+
@c = Class.new(Sequel::Model(:items))
|
415
156
|
end
|
416
|
-
|
417
|
-
specify "should raise for a column that doesn't exist in the dataset" do
|
418
|
-
o = @c.new
|
419
|
-
|
420
|
-
proc {o.x}.should_not raise_error
|
421
|
-
proc {o.xx}.should raise_error(Sequel::Error)
|
422
|
-
|
423
|
-
proc {o.x = 3}.should_not raise_error
|
424
|
-
proc {o.yy = 4}.should raise_error(Sequel::Error)
|
425
157
|
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
specify "should not raise for a column not in the dataset, but for which there's a value" do
|
430
|
-
o = @c.new
|
431
|
-
|
432
|
-
proc {o.xx}.should raise_error(Sequel::Error)
|
433
|
-
proc {o.yy}.should raise_error(Sequel::Error)
|
158
|
+
it "should create a filter on the underlying dataset" do
|
159
|
+
proc {@c.new_only}.should raise_error(NoMethodError)
|
434
160
|
|
435
|
-
|
436
|
-
o.values[:yy] = nil
|
161
|
+
@c.subset(:new_only) {:age == 'new'}
|
437
162
|
|
438
|
-
|
163
|
+
@c.new_only.sql.should == "SELECT * FROM items WHERE (age = 'new')"
|
164
|
+
@c.dataset.new_only.sql.should == "SELECT * FROM items WHERE (age = 'new')"
|
439
165
|
|
440
|
-
|
441
|
-
o.yy.should == nil
|
166
|
+
@c.subset(:pricey) {:price > 100}
|
442
167
|
|
443
|
-
|
444
|
-
|
445
|
-
end
|
446
|
-
|
447
|
-
context "Model attribute setters" do
|
448
|
-
setup do
|
449
|
-
MODEL_DB.reset
|
450
|
-
|
451
|
-
@c = Class.new(Sequel::Model(:items)) do
|
452
|
-
def columns
|
453
|
-
[:id, :x, :y]
|
454
|
-
end
|
455
|
-
end
|
456
|
-
end
|
457
|
-
|
458
|
-
specify "should mark the column value as changed" do
|
459
|
-
o = @c.new
|
460
|
-
o.changed_columns.should == []
|
461
|
-
|
462
|
-
o.x = 2
|
463
|
-
o.changed_columns.should == [:x]
|
464
|
-
|
465
|
-
o.y = 3
|
466
|
-
o.changed_columns.should == [:x, :y]
|
467
|
-
|
468
|
-
o.changed_columns.clear
|
469
|
-
|
470
|
-
o[:x] = 2
|
471
|
-
o.changed_columns.should == [:x]
|
472
|
-
|
473
|
-
o[:y] = 3
|
474
|
-
o.changed_columns.should == [:x, :y]
|
475
|
-
end
|
476
|
-
end
|
477
|
-
|
478
|
-
context "Model#save" do
|
479
|
-
setup do
|
480
|
-
MODEL_DB.reset
|
481
|
-
|
482
|
-
@c = Class.new(Sequel::Model(:items)) do
|
483
|
-
def columns
|
484
|
-
[:id, :x, :y]
|
485
|
-
end
|
486
|
-
end
|
487
|
-
end
|
488
|
-
|
489
|
-
specify "should insert a record for a new model instance" do
|
490
|
-
o = @c.new(:x => 1)
|
491
|
-
o.save
|
492
|
-
|
493
|
-
MODEL_DB.sqls.first.should == "INSERT INTO items (x) VALUES (1)"
|
494
|
-
end
|
495
|
-
|
496
|
-
specify "should update a record for an existing model instance" do
|
497
|
-
o = @c.new(:id => 3, :x => 1)
|
498
|
-
o.save
|
499
|
-
|
500
|
-
MODEL_DB.sqls.first.should =~
|
501
|
-
/UPDATE items SET (id = 3, x = 1|x = 1, id = 3) WHERE \(id = 3\)/
|
502
|
-
end
|
503
|
-
|
504
|
-
specify "should update only the given columns if given" do
|
505
|
-
o = @c.new(:id => 3, :x => 1, :y => nil)
|
506
|
-
o.save(:y)
|
507
|
-
|
508
|
-
MODEL_DB.sqls.first.should == "UPDATE items SET y = NULL WHERE (id = 3)"
|
509
|
-
end
|
510
|
-
|
511
|
-
specify "should mark saved columns as not changed" do
|
512
|
-
o = @c.new(:id => 3, :x => 1, :y => nil)
|
513
|
-
o[:y] = 4
|
514
|
-
o.changed_columns.should == [:y]
|
515
|
-
o.save(:x)
|
516
|
-
o.changed_columns.should == [:y]
|
517
|
-
o.save(:y)
|
518
|
-
o.changed_columns.should == []
|
519
|
-
end
|
520
|
-
end
|
521
|
-
|
522
|
-
context "Model#save_changes" do
|
523
|
-
setup do
|
524
|
-
MODEL_DB.reset
|
525
|
-
|
526
|
-
@c = Class.new(Sequel::Model(:items)) do
|
527
|
-
def columns
|
528
|
-
[:id, :x, :y]
|
529
|
-
end
|
530
|
-
end
|
531
|
-
end
|
532
|
-
|
533
|
-
specify "should do nothing if no changed columns" do
|
534
|
-
o = @c.new(:id => 3, :x => 1, :y => nil)
|
535
|
-
o.save_changes
|
536
|
-
|
537
|
-
MODEL_DB.sqls.should be_empty
|
538
|
-
end
|
539
|
-
|
540
|
-
specify "should update only changed columns" do
|
541
|
-
o = @c.new(:id => 3, :x => 1, :y => nil)
|
542
|
-
o.x = 2
|
543
|
-
|
544
|
-
o.save_changes
|
545
|
-
MODEL_DB.sqls.should == ["UPDATE items SET x = 2 WHERE (id = 3)"]
|
546
|
-
o.save_changes
|
547
|
-
o.save_changes
|
548
|
-
MODEL_DB.sqls.should == ["UPDATE items SET x = 2 WHERE (id = 3)"]
|
549
|
-
MODEL_DB.reset
|
550
|
-
|
551
|
-
o.y = 4
|
552
|
-
o.save_changes
|
553
|
-
MODEL_DB.sqls.should == ["UPDATE items SET y = 4 WHERE (id = 3)"]
|
554
|
-
o.save_changes
|
555
|
-
o.save_changes
|
556
|
-
MODEL_DB.sqls.should == ["UPDATE items SET y = 4 WHERE (id = 3)"]
|
557
|
-
end
|
558
|
-
end
|
559
|
-
|
560
|
-
context "Model#new?" do
|
561
|
-
setup do
|
562
|
-
MODEL_DB.reset
|
563
|
-
|
564
|
-
@c = Class.new(Sequel::Model(:items)) do
|
565
|
-
end
|
566
|
-
end
|
567
|
-
|
568
|
-
specify "should be true for a new instance" do
|
569
|
-
n = @c.new(:x => 1)
|
570
|
-
n.should be_new
|
571
|
-
end
|
572
|
-
|
573
|
-
specify "should be false after saving" do
|
574
|
-
n = @c.new(:x => 1)
|
575
|
-
n.save
|
576
|
-
n.should_not be_new
|
577
|
-
end
|
578
|
-
end
|
579
|
-
|
580
|
-
context "Model.after_create" do
|
581
|
-
setup do
|
582
|
-
MODEL_DB.reset
|
583
|
-
|
584
|
-
@c = Class.new(Sequel::Model(:items)) do
|
585
|
-
def columns
|
586
|
-
[:id, :x, :y]
|
587
|
-
end
|
588
|
-
end
|
589
|
-
|
590
|
-
ds = @c.dataset
|
591
|
-
def ds.insert(*args)
|
592
|
-
super(*args)
|
593
|
-
1
|
594
|
-
end
|
595
|
-
end
|
596
|
-
|
597
|
-
specify "should be called after creation" do
|
598
|
-
s = []
|
599
|
-
|
600
|
-
@c.after_create do
|
601
|
-
s = MODEL_DB.sqls.dup
|
602
|
-
end
|
603
|
-
|
604
|
-
n = @c.create(:x => 1)
|
605
|
-
MODEL_DB.sqls.should == ["INSERT INTO items (x) VALUES (1)", "SELECT * FROM items WHERE (id = 1) LIMIT 1"]
|
606
|
-
s.should == ["INSERT INTO items (x) VALUES (1)", "SELECT * FROM items WHERE (id = 1) LIMIT 1"]
|
607
|
-
end
|
608
|
-
|
609
|
-
specify "should allow calling save in the hook" do
|
610
|
-
@c.after_create do
|
611
|
-
values.delete(:x)
|
612
|
-
self.id = 2
|
613
|
-
save
|
614
|
-
end
|
615
|
-
|
616
|
-
n = @c.create(:id => 1)
|
617
|
-
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)"]
|
618
|
-
end
|
619
|
-
end
|
620
|
-
|
621
|
-
context "Model.subset" do
|
622
|
-
setup do
|
623
|
-
MODEL_DB.reset
|
624
|
-
|
625
|
-
@c = Class.new(Sequel::Model(:items))
|
626
|
-
end
|
627
|
-
|
628
|
-
specify "should create a filter on the underlying dataset" do
|
629
|
-
proc {@c.new_only}.should raise_error(NoMethodError)
|
630
|
-
|
631
|
-
@c.subset(:new_only) {:age == 'new'}
|
632
|
-
|
633
|
-
@c.new_only.sql.should == "SELECT * FROM items WHERE (age = 'new')"
|
634
|
-
@c.dataset.new_only.sql.should == "SELECT * FROM items WHERE (age = 'new')"
|
635
|
-
|
636
|
-
@c.subset(:pricey) {:price > 100}
|
637
|
-
|
638
|
-
@c.pricey.sql.should == "SELECT * FROM items WHERE (price > 100)"
|
639
|
-
@c.dataset.pricey.sql.should == "SELECT * FROM items WHERE (price > 100)"
|
640
|
-
|
641
|
-
# check if subsets are composable
|
642
|
-
@c.pricey.new_only.sql.should == "SELECT * FROM items WHERE (price > 100) AND (age = 'new')"
|
643
|
-
@c.new_only.pricey.sql.should == "SELECT * FROM items WHERE (age = 'new') AND (price > 100)"
|
644
|
-
end
|
645
|
-
end
|
646
|
-
|
647
|
-
context "Model.find" do
|
648
|
-
setup do
|
649
|
-
MODEL_DB.reset
|
650
|
-
|
651
|
-
@c = Class.new(Sequel::Model(:items))
|
652
|
-
|
653
|
-
$cache_dataset_row = {:name => 'sharon', :id => 1}
|
654
|
-
@dataset = @c.dataset
|
655
|
-
$sqls = []
|
656
|
-
@dataset.extend(Module.new {
|
657
|
-
def fetch_rows(sql)
|
658
|
-
$sqls << sql
|
659
|
-
yield $cache_dataset_row
|
660
|
-
end
|
661
|
-
})
|
662
|
-
end
|
663
|
-
|
664
|
-
specify "should return the first record matching the given filter" do
|
665
|
-
@c.find(:name => 'sharon').should be_a_kind_of(@c)
|
666
|
-
$sqls.last.should == "SELECT * FROM items WHERE (name = 'sharon') LIMIT 1"
|
667
|
-
|
668
|
-
@c.find {"name LIKE 'abc%'".lit}.should be_a_kind_of(@c)
|
669
|
-
$sqls.last.should == "SELECT * FROM items WHERE name LIKE 'abc%' LIMIT 1"
|
670
|
-
end
|
671
|
-
|
672
|
-
specify "should accept filter blocks" do
|
673
|
-
@c.find {:id == 1}.should be_a_kind_of(@c)
|
674
|
-
$sqls.last.should == "SELECT * FROM items WHERE (id = 1) LIMIT 1"
|
675
|
-
|
676
|
-
@c.find {:x > 1 && :y < 2}.should be_a_kind_of(@c)
|
677
|
-
$sqls.last.should == "SELECT * FROM items WHERE ((x > 1) AND (y < 2)) LIMIT 1"
|
678
|
-
end
|
679
|
-
end
|
680
|
-
|
681
|
-
context "Model.[]" do
|
682
|
-
setup do
|
683
|
-
MODEL_DB.reset
|
684
|
-
|
685
|
-
@c = Class.new(Sequel::Model(:items))
|
686
|
-
|
687
|
-
$cache_dataset_row = {:name => 'sharon', :id => 1}
|
688
|
-
@dataset = @c.dataset
|
689
|
-
$sqls = []
|
690
|
-
@dataset.extend(Module.new {
|
691
|
-
def fetch_rows(sql)
|
692
|
-
$sqls << sql
|
693
|
-
yield $cache_dataset_row
|
694
|
-
end
|
695
|
-
})
|
696
|
-
end
|
697
|
-
|
698
|
-
specify "should return the first record for the given pk" do
|
699
|
-
@c[1].should be_a_kind_of(@c)
|
700
|
-
$sqls.last.should == "SELECT * FROM items WHERE (id = 1) LIMIT 1"
|
701
|
-
@c[9999].should be_a_kind_of(@c)
|
702
|
-
$sqls.last.should == "SELECT * FROM items WHERE (id = 9999) LIMIT 1"
|
703
|
-
end
|
704
|
-
|
705
|
-
specify "should raise for boolean argument (mistaken comparison)" do
|
706
|
-
# This in order to prevent stuff like Model[:a == 'b']
|
707
|
-
proc {@c[:a == 1]}.should raise_error(Sequel::Error)
|
708
|
-
proc {@c[:a != 1]}.should raise_error(Sequel::Error)
|
709
|
-
end
|
710
|
-
|
711
|
-
specify "should work correctly for custom primary key" do
|
712
|
-
@c.set_primary_key :name
|
713
|
-
@c['sharon'].should be_a_kind_of(@c)
|
714
|
-
$sqls.last.should == "SELECT * FROM items WHERE (name = 'sharon') LIMIT 1"
|
715
|
-
end
|
716
|
-
|
717
|
-
specify "should work correctly for composite primary key" do
|
718
|
-
@c.set_primary_key [:node_id, :kind]
|
719
|
-
@c[3921, 201].should be_a_kind_of(@c)
|
720
|
-
$sqls.last.should =~ \
|
721
|
-
/^SELECT \* FROM items WHERE (\(node_id = 3921\) AND \(kind = 201\))|(\(kind = 201\) AND \(node_id = 3921\)) LIMIT 1$/
|
722
|
-
end
|
723
|
-
|
724
|
-
specify "should act as shortcut to find if a hash is given" do
|
725
|
-
@c[:id => 1].should be_a_kind_of(@c)
|
726
|
-
$sqls.last.should == "SELECT * FROM items WHERE (id = 1) LIMIT 1"
|
727
|
-
|
728
|
-
@c[:name => ['abc', 'def']].should be_a_kind_of(@c)
|
729
|
-
$sqls.last.should == "SELECT * FROM items WHERE (name IN ('abc', 'def')) LIMIT 1"
|
730
|
-
end
|
731
|
-
end
|
732
|
-
|
733
|
-
context "Model.fetch" do
|
734
|
-
|
735
|
-
setup do
|
736
|
-
MODEL_DB.reset
|
737
|
-
@c = Class.new(Sequel::Model(:items))
|
738
|
-
end
|
739
|
-
|
740
|
-
specify "should return instances of Model" do
|
741
|
-
@c.fetch("SELECT * FROM items").first.should be_a_kind_of(@c)
|
742
|
-
end
|
743
|
-
|
744
|
-
specify "should return true for .empty? and not raise an error on empty selection" do
|
745
|
-
rows = @c.fetch("SELECT * FROM items WHERE FALSE")
|
746
|
-
@c.class_def(:fetch_rows) {|sql| yield({:count => 0})}
|
747
|
-
proc {rows.empty?}.should_not raise_error
|
748
|
-
end
|
749
|
-
end
|
750
|
-
|
751
|
-
context "A cached model" do
|
752
|
-
setup do
|
753
|
-
MODEL_DB.reset
|
754
|
-
|
755
|
-
@cache_class = Class.new(Hash) do
|
756
|
-
attr_accessor :ttl
|
757
|
-
def set(k, v, ttl); self[k] = v; @ttl = ttl; end
|
758
|
-
def get(k); self[k]; end
|
759
|
-
end
|
760
|
-
cache = @cache_class.new
|
761
|
-
@cache = cache
|
762
|
-
|
763
|
-
@c = Class.new(Sequel::Model(:items)) do
|
764
|
-
set_cache cache
|
765
|
-
|
766
|
-
def self.columns
|
767
|
-
[:name, :id]
|
768
|
-
end
|
769
|
-
end
|
770
|
-
|
771
|
-
$cache_dataset_row = {:name => 'sharon', :id => 1}
|
772
|
-
@dataset = @c.dataset
|
773
|
-
$sqls = []
|
774
|
-
@dataset.extend(Module.new {
|
775
|
-
def fetch_rows(sql)
|
776
|
-
$sqls << sql
|
777
|
-
yield $cache_dataset_row
|
778
|
-
end
|
779
|
-
|
780
|
-
def update(values)
|
781
|
-
$sqls << update_sql(values)
|
782
|
-
$cache_dataset_row.merge!(values)
|
783
|
-
end
|
784
|
-
|
785
|
-
def delete
|
786
|
-
$sqls << delete_sql
|
787
|
-
end
|
788
|
-
})
|
789
|
-
end
|
790
|
-
|
791
|
-
specify "should set the model's cache store" do
|
792
|
-
@c.cache_store.should be(@cache)
|
793
|
-
end
|
794
|
-
|
795
|
-
specify "should have a default ttl of 3600" do
|
796
|
-
@c.cache_ttl.should == 3600
|
797
|
-
end
|
798
|
-
|
799
|
-
specify "should take a ttl option" do
|
800
|
-
@c.set_cache @cache, :ttl => 1234
|
801
|
-
@c.cache_ttl.should == 1234
|
802
|
-
end
|
803
|
-
|
804
|
-
specify "should offer a set_cache_ttl method for setting the ttl" do
|
805
|
-
@c.cache_ttl.should == 3600
|
806
|
-
@c.set_cache_ttl 1234
|
807
|
-
@c.cache_ttl.should == 1234
|
808
|
-
end
|
809
|
-
|
810
|
-
specify "should generate a cache key appropriate to the class" do
|
811
|
-
m = @c.new
|
812
|
-
m.values[:id] = 1
|
813
|
-
m.cache_key.should == "#{m.class}:1"
|
814
|
-
|
815
|
-
# custom primary key
|
816
|
-
@c.set_primary_key :ttt
|
817
|
-
m = @c.new
|
818
|
-
m.values[:ttt] = 333
|
819
|
-
m.cache_key.should == "#{m.class}:333"
|
820
|
-
|
821
|
-
# composite primary key
|
822
|
-
@c.set_primary_key [:a, :b, :c]
|
823
|
-
m = @c.new
|
824
|
-
m.values[:a] = 123
|
825
|
-
m.values[:c] = 456
|
826
|
-
m.values[:b] = 789
|
827
|
-
m.cache_key.should == "#{m.class}:123,789,456"
|
828
|
-
end
|
829
|
-
|
830
|
-
specify "should raise error if attempting to generate cache_key and primary key value is null" do
|
831
|
-
m = @c.new
|
832
|
-
proc {m.cache_key}.should raise_error(Sequel::Error)
|
833
|
-
|
834
|
-
m.values[:id] = 1
|
835
|
-
proc {m.cache_key}.should_not raise_error(Sequel::Error)
|
836
|
-
end
|
837
|
-
|
838
|
-
specify "should set the cache when reading from the database" do
|
839
|
-
$sqls.should == []
|
840
|
-
@cache.should be_empty
|
841
|
-
|
842
|
-
m = @c[1]
|
843
|
-
$sqls.should == ['SELECT * FROM items WHERE (id = 1) LIMIT 1']
|
844
|
-
m.values.should == $cache_dataset_row
|
845
|
-
@cache[m.cache_key].should == m
|
846
|
-
|
847
|
-
# read from cache
|
848
|
-
m2 = @c[1]
|
849
|
-
$sqls.should == ['SELECT * FROM items WHERE (id = 1) LIMIT 1']
|
850
|
-
m2.should == m
|
851
|
-
m2.values.should == $cache_dataset_row
|
852
|
-
end
|
853
|
-
|
854
|
-
specify "should delete the cache when writing to the database" do
|
855
|
-
# fill the cache
|
856
|
-
m = @c[1]
|
857
|
-
@cache[m.cache_key].should == m
|
858
|
-
|
859
|
-
m.set(:name => 'tutu')
|
860
|
-
@cache.has_key?(m.cache_key).should be_false
|
861
|
-
$sqls.last.should == "UPDATE items SET name = 'tutu' WHERE (id = 1)"
|
862
|
-
|
863
|
-
m = @c[1]
|
864
|
-
@cache[m.cache_key].should == m
|
865
|
-
m.name = 'hey'
|
866
|
-
m.save
|
867
|
-
@cache.has_key?(m.cache_key).should be_false
|
868
|
-
$sqls.last.should == "UPDATE items SET name = 'hey', id = 1 WHERE (id = 1)"
|
869
|
-
end
|
870
|
-
|
871
|
-
specify "should delete the cache when deleting the record" do
|
872
|
-
# fill the cache
|
873
|
-
m = @c[1]
|
874
|
-
@cache[m.cache_key].should == m
|
875
|
-
|
876
|
-
m.delete
|
877
|
-
@cache.has_key?(m.cache_key).should be_false
|
878
|
-
$sqls.last.should == "DELETE FROM items WHERE (id = 1)"
|
879
|
-
end
|
880
|
-
|
881
|
-
specify "should support #[] as a shortcut to #find with hash" do
|
882
|
-
m = @c[:id => 3]
|
883
|
-
@cache[m.cache_key].should be_nil
|
884
|
-
$sqls.last.should == "SELECT * FROM items WHERE (id = 3) LIMIT 1"
|
885
|
-
|
886
|
-
m = @c[1]
|
887
|
-
@cache[m.cache_key].should == m
|
888
|
-
$sqls.should == ["SELECT * FROM items WHERE (id = 3) LIMIT 1", \
|
889
|
-
"SELECT * FROM items WHERE (id = 1) LIMIT 1"]
|
890
|
-
|
891
|
-
@c[:id => 4]
|
892
|
-
$sqls.should == ["SELECT * FROM items WHERE (id = 3) LIMIT 1", \
|
893
|
-
"SELECT * FROM items WHERE (id = 1) LIMIT 1", \
|
894
|
-
"SELECT * FROM items WHERE (id = 4) LIMIT 1"]
|
895
|
-
end
|
896
|
-
end
|
897
|
-
|
898
|
-
context "Model.one_to_one" do
|
899
|
-
setup do
|
900
|
-
MODEL_DB.reset
|
901
|
-
|
902
|
-
@c1 = Class.new(Sequel::Model(:attributes)) do
|
903
|
-
end
|
904
|
-
|
905
|
-
@c2 = Class.new(Sequel::Model(:nodes)) do
|
906
|
-
end
|
907
|
-
|
908
|
-
@dataset = @c2.dataset
|
909
|
-
|
910
|
-
$sqls = []
|
911
|
-
@dataset.extend(Module.new {
|
912
|
-
def fetch_rows(sql)
|
913
|
-
$sqls << sql
|
914
|
-
yield({:hey => 1})
|
915
|
-
end
|
916
|
-
|
917
|
-
def update(values)
|
918
|
-
$sqls << update_sql(values)
|
919
|
-
end
|
920
|
-
})
|
921
|
-
end
|
922
|
-
|
923
|
-
specify "should use implicit key if omitted" do
|
924
|
-
@c2.one_to_one :parent, :from => @c2
|
925
|
-
|
926
|
-
d = @c2.new(:id => 1, :parent_id => 234)
|
927
|
-
p = d.parent
|
928
|
-
p.class.should == @c2
|
929
|
-
p.values.should == {:hey => 1}
|
930
|
-
|
931
|
-
$sqls.should == ["SELECT * FROM nodes WHERE (id = 234) LIMIT 1"]
|
932
|
-
end
|
933
|
-
|
934
|
-
specify "should use explicit key if given" do
|
935
|
-
@c2.one_to_one :parent, :from => @c2, :key => :blah
|
936
|
-
|
937
|
-
d = @c2.new(:id => 1, :blah => 567)
|
938
|
-
p = d.parent
|
939
|
-
p.class.should == @c2
|
940
|
-
p.values.should == {:hey => 1}
|
941
|
-
|
942
|
-
$sqls.should == ["SELECT * FROM nodes WHERE (id = 567) LIMIT 1"]
|
943
|
-
end
|
944
|
-
|
945
|
-
specify "should support plain dataset in the from option" do
|
946
|
-
@c2.one_to_one :parent, :from => MODEL_DB[:xyz]
|
947
|
-
|
948
|
-
d = @c2.new(:id => 1, :parent_id => 789)
|
949
|
-
p = d.parent
|
950
|
-
p.class.should == Hash
|
951
|
-
|
952
|
-
MODEL_DB.sqls.should == ["SELECT * FROM xyz WHERE (id = 789) LIMIT 1"]
|
953
|
-
end
|
954
|
-
|
955
|
-
specify "should support table name in the from option" do
|
956
|
-
@c2.one_to_one :parent, :from => :abc
|
957
|
-
|
958
|
-
d = @c2.new(:id => 1, :parent_id => 789)
|
959
|
-
p = d.parent
|
960
|
-
p.class.should == Hash
|
961
|
-
|
962
|
-
MODEL_DB.sqls.should == ["SELECT * FROM abc WHERE (id = 789) LIMIT 1"]
|
963
|
-
end
|
964
|
-
|
965
|
-
specify "should return nil if key value is nil" do
|
966
|
-
@c2.one_to_one :parent, :from => @c2
|
967
|
-
|
968
|
-
d = @c2.new(:id => 1)
|
969
|
-
d.parent.should == nil
|
970
|
-
end
|
971
|
-
|
972
|
-
specify "should define a setter method" do
|
973
|
-
@c2.one_to_one :parent, :from => @c2
|
974
|
-
|
975
|
-
d = @c2.new(:id => 1)
|
976
|
-
d.parent = {:id => 4321}
|
977
|
-
d.values.should == {:id => 1, :parent_id => 4321}
|
978
|
-
$sqls.last.should == "UPDATE nodes SET parent_id = 4321 WHERE (id = 1)"
|
979
|
-
|
980
|
-
d.parent = nil
|
981
|
-
d.values.should == {:id => 1, :parent_id => nil}
|
982
|
-
$sqls.last.should == "UPDATE nodes SET parent_id = NULL WHERE (id = 1)"
|
983
|
-
|
984
|
-
e = @c2.new(:id => 6677)
|
985
|
-
d.parent = e
|
986
|
-
d.values.should == {:id => 1, :parent_id => 6677}
|
987
|
-
$sqls.last.should == "UPDATE nodes SET parent_id = 6677 WHERE (id = 1)"
|
988
|
-
end
|
989
|
-
end
|
990
|
-
|
991
|
-
context "Model.one_to_many" do
|
992
|
-
setup do
|
993
|
-
MODEL_DB.reset
|
994
|
-
|
995
|
-
@c1 = Class.new(Sequel::Model(:attributes)) do
|
996
|
-
end
|
997
|
-
|
998
|
-
@c2 = Class.new(Sequel::Model(:nodes)) do
|
999
|
-
end
|
1000
|
-
end
|
1001
|
-
|
1002
|
-
specify "should define a getter method" do
|
1003
|
-
@c2.one_to_many :attributes, :from => @c1, :key => :node_id
|
1004
|
-
|
1005
|
-
n = @c2.new(:id => 1234)
|
1006
|
-
a = n.attributes
|
1007
|
-
a.should be_a_kind_of(Sequel::Dataset)
|
1008
|
-
a.sql.should == 'SELECT * FROM attributes WHERE (node_id = 1234)'
|
1009
|
-
end
|
1010
|
-
|
1011
|
-
specify "should support plain dataset in the from option" do
|
1012
|
-
@c2.one_to_many :attributes, :from => MODEL_DB[:xyz], :key => :node_id
|
168
|
+
@c.pricey.sql.should == "SELECT * FROM items WHERE (price > 100)"
|
169
|
+
@c.dataset.pricey.sql.should == "SELECT * FROM items WHERE (price > 100)"
|
1013
170
|
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
a.sql.should == 'SELECT * FROM xyz WHERE (node_id = 1234)'
|
171
|
+
# check if subsets are composable
|
172
|
+
@c.pricey.new_only.sql.should == "SELECT * FROM items WHERE (price > 100) AND (age = 'new')"
|
173
|
+
@c.new_only.pricey.sql.should == "SELECT * FROM items WHERE (age = 'new') AND (price > 100)"
|
1018
174
|
end
|
1019
175
|
|
1020
|
-
specify "should support table name in the from option" do
|
1021
|
-
@c2.one_to_many :attributes, :from => :abc, :key => :node_id
|
1022
|
-
|
1023
|
-
n = @c2.new(:id => 1234)
|
1024
|
-
a = n.attributes
|
1025
|
-
a.should be_a_kind_of(Sequel::Dataset)
|
1026
|
-
a.sql.should == 'SELECT * FROM abc WHERE (node_id = 1234)'
|
1027
|
-
end
|
1028
176
|
end
|
1029
177
|
|
1030
|
-
|
1031
|
-
setup do
|
1032
|
-
@m = Class.new(Sequel::Model)
|
1033
|
-
end
|
1034
|
-
|
1035
|
-
specify "should be default return the value of the :id column" do
|
1036
|
-
m = @m.new(:id => 111, :x => 2, :y => 3)
|
1037
|
-
m.pk.should == 111
|
1038
|
-
end
|
1039
|
-
|
1040
|
-
specify "should be return the primary key value for custom primary key" do
|
1041
|
-
@m.set_primary_key :x
|
1042
|
-
m = @m.new(:id => 111, :x => 2, :y => 3)
|
1043
|
-
m.pk.should == 2
|
1044
|
-
end
|
1045
|
-
|
1046
|
-
specify "should be return the primary key value for composite primary key" do
|
1047
|
-
@m.set_primary_key [:y, :x]
|
1048
|
-
m = @m.new(:id => 111, :x => 2, :y => 3)
|
1049
|
-
m.pk.should == [3, 2]
|
1050
|
-
end
|
1051
|
-
|
1052
|
-
specify "should raise if no primary key" do
|
1053
|
-
@m.set_primary_key nil
|
1054
|
-
m = @m.new(:id => 111, :x => 2, :y => 3)
|
1055
|
-
proc {m.pk}.should raise_error(Sequel::Error)
|
178
|
+
describe Sequel::Model, ".find" do
|
1056
179
|
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
180
|
+
before(:each) do
|
181
|
+
MODEL_DB.reset
|
182
|
+
|
183
|
+
@c = Class.new(Sequel::Model(:items))
|
184
|
+
|
185
|
+
$cache_dataset_row = {:name => 'sharon', :id => 1}
|
186
|
+
@dataset = @c.dataset
|
187
|
+
$sqls = []
|
188
|
+
@dataset.extend(Module.new {
|
189
|
+
def fetch_rows(sql)
|
190
|
+
$sqls << sql
|
191
|
+
yield $cache_dataset_row
|
192
|
+
end
|
193
|
+
})
|
1060
194
|
end
|
1061
|
-
|
195
|
+
|
196
|
+
it "should return the first record matching the given filter" do
|
197
|
+
@c.find(:name => 'sharon').should be_a_kind_of(@c)
|
198
|
+
$sqls.last.should == "SELECT * FROM items WHERE (name = 'sharon') LIMIT 1"
|
1062
199
|
|
1063
|
-
|
1064
|
-
|
1065
|
-
@m = Class.new(Sequel::Model)
|
200
|
+
@c.find {"name LIKE 'abc%'".lit}.should be_a_kind_of(@c)
|
201
|
+
$sqls.last.should == "SELECT * FROM items WHERE name LIKE 'abc%' LIMIT 1"
|
1066
202
|
end
|
1067
203
|
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
end
|
204
|
+
it "should accept filter blocks" do
|
205
|
+
@c.find {:id == 1}.should be_a_kind_of(@c)
|
206
|
+
$sqls.last.should == "SELECT * FROM items WHERE (id = 1) LIMIT 1"
|
1072
207
|
|
1073
|
-
|
1074
|
-
|
1075
|
-
m = @m.new(:id => 111, :x => 2, :y => 3)
|
1076
|
-
m.pk_hash.should == {:x => 2}
|
208
|
+
@c.find {:x > 1 && :y < 2}.should be_a_kind_of(@c)
|
209
|
+
$sqls.last.should == "SELECT * FROM items WHERE ((x > 1) AND (y < 2)) LIMIT 1"
|
1077
210
|
end
|
1078
211
|
|
1079
|
-
|
1080
|
-
@m.set_primary_key [:y, :x]
|
1081
|
-
m = @m.new(:id => 111, :x => 2, :y => 3)
|
1082
|
-
m.pk_hash.should == {:y => 3, :x => 2}
|
1083
|
-
end
|
212
|
+
end
|
1084
213
|
|
1085
|
-
|
1086
|
-
@m.set_primary_key nil
|
1087
|
-
m = @m.new(:id => 111, :x => 2, :y => 3)
|
1088
|
-
proc {m.pk_hash}.should raise_error(Sequel::Error)
|
214
|
+
describe Sequel::Model, ".fetch" do
|
1089
215
|
|
1090
|
-
|
1091
|
-
|
1092
|
-
|
216
|
+
before(:each) do
|
217
|
+
MODEL_DB.reset
|
218
|
+
@c = Class.new(Sequel::Model(:items))
|
1093
219
|
end
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
setup do
|
1098
|
-
@m = Class.new(Sequel::Model)
|
220
|
+
|
221
|
+
it "should return instances of Model" do
|
222
|
+
@c.fetch("SELECT * FROM items").first.should be_a_kind_of(@c)
|
1099
223
|
end
|
1100
224
|
|
1101
|
-
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
225
|
+
it "should return true for .empty? and not raise an error on empty selection" do
|
226
|
+
rows = @c.fetch("SELECT * FROM items WHERE FALSE")
|
227
|
+
@c.class_def(:fetch_rows) {|sql| yield({:count => 0})}
|
228
|
+
proc {rows.empty?}.should_not raise_error
|
1105
229
|
end
|
1106
230
|
|
1107
|
-
specify "should accept a block and yield itself to the block" do
|
1108
|
-
block_called = false
|
1109
|
-
m = @m.new {|i| block_called = true; i.should be_a_kind_of(@m); i.values[:a] = 1}
|
1110
|
-
|
1111
|
-
block_called.should be_true
|
1112
|
-
m.values[:a].should == 1
|
1113
|
-
end
|
1114
231
|
end
|
1115
232
|
|
1116
|
-
|
1117
|
-
|
233
|
+
describe Sequel::Model, "magic methods" do
|
234
|
+
|
235
|
+
before(:each) do
|
1118
236
|
@c = Class.new(Sequel::Dataset) do
|
1119
237
|
@@sqls = []
|
1120
238
|
|
@@ -1129,27 +247,27 @@ context "Model magic methods" do
|
|
1129
247
|
@m = Class.new(Sequel::Model(@c.new(nil).from(:items)))
|
1130
248
|
end
|
1131
249
|
|
1132
|
-
|
250
|
+
it "should support order_by_xxx" do
|
1133
251
|
@m.order_by_name.should be_a_kind_of(@c)
|
1134
252
|
@m.order_by_name.sql.should == "SELECT * FROM items ORDER BY name"
|
1135
253
|
end
|
1136
254
|
|
1137
|
-
|
255
|
+
it "should support group_by_xxx" do
|
1138
256
|
@m.group_by_name.should be_a_kind_of(@c)
|
1139
257
|
@m.group_by_name.sql.should == "SELECT * FROM items GROUP BY name"
|
1140
258
|
end
|
1141
259
|
|
1142
|
-
|
260
|
+
it "should support count_by_xxx" do
|
1143
261
|
@m.count_by_name.should be_a_kind_of(@c)
|
1144
262
|
@m.count_by_name.sql.should == "SELECT name, count(name) AS count FROM items GROUP BY name ORDER BY count"
|
1145
263
|
end
|
1146
264
|
|
1147
|
-
|
265
|
+
it "should support filter_by_xxx" do
|
1148
266
|
@m.filter_by_name('sharon').should be_a_kind_of(@c)
|
1149
267
|
@m.filter_by_name('sharon').sql.should == "SELECT * FROM items WHERE (name = 'sharon')"
|
1150
268
|
end
|
1151
269
|
|
1152
|
-
|
270
|
+
it "should support all_by_xxx" do
|
1153
271
|
all = @m.all_by_name('sharon')
|
1154
272
|
all.class.should == Array
|
1155
273
|
all.size.should == 1
|
@@ -1158,120 +276,43 @@ context "Model magic methods" do
|
|
1158
276
|
@c.sqls.should == ["SELECT * FROM items WHERE (name = 'sharon')"]
|
1159
277
|
end
|
1160
278
|
|
1161
|
-
|
279
|
+
it "should support find_by_xxx" do
|
1162
280
|
@m.find_by_name('sharon').should be_a_kind_of(@m)
|
1163
281
|
@m.find_by_name('sharon').values.should == {:id => 123, :name => 'hey'}
|
1164
282
|
@c.sqls.should == ["SELECT * FROM items WHERE (name = 'sharon') LIMIT 1"] * 2
|
1165
283
|
end
|
1166
284
|
|
1167
|
-
|
285
|
+
it "should support first_by_xxx" do
|
1168
286
|
@m.first_by_name('sharon').should be_a_kind_of(@m)
|
1169
287
|
@m.first_by_name('sharon').values.should == {:id => 123, :name => 'hey'}
|
1170
288
|
@c.sqls.should == ["SELECT * FROM items ORDER BY name LIMIT 1"] * 2
|
1171
289
|
end
|
1172
290
|
|
1173
|
-
|
291
|
+
it "should support last_by_xxx" do
|
1174
292
|
@m.last_by_name('sharon').should be_a_kind_of(@m)
|
1175
293
|
@m.last_by_name('sharon').values.should == {:id => 123, :name => 'hey'}
|
1176
294
|
@c.sqls.should == ["SELECT * FROM items ORDER BY name DESC LIMIT 1"] * 2
|
1177
295
|
end
|
1178
|
-
end
|
1179
|
-
|
1180
|
-
module Sequel::Plugins
|
1181
|
-
module Timestamped
|
1182
|
-
def self.apply(m, opts)
|
1183
|
-
m.class_def(:get_stamp) {@values[:stamp]}
|
1184
|
-
m.meta_def(:stamp_opts) {opts}
|
1185
|
-
m.before_save {@values[:stamp] = Time.now}
|
1186
|
-
end
|
1187
|
-
|
1188
|
-
module InstanceMethods
|
1189
|
-
def abc; timestamped_opts; end
|
1190
|
-
end
|
1191
|
-
|
1192
|
-
module ClassMethods
|
1193
|
-
def deff; timestamped_opts; end
|
1194
|
-
end
|
1195
|
-
end
|
1196
|
-
end
|
1197
|
-
|
1198
|
-
context "A model using a plugin" do
|
1199
|
-
specify "should fail if the plugin is not found" do
|
1200
|
-
proc do
|
1201
|
-
c = Class.new(Sequel::Model) do
|
1202
|
-
is :something_or_other
|
1203
|
-
end
|
1204
|
-
end.should raise_error(LoadError)
|
1205
|
-
end
|
1206
296
|
|
1207
|
-
specify "should apply the plugin to the class" do
|
1208
|
-
c = nil
|
1209
|
-
proc do
|
1210
|
-
c = Class.new(Sequel::Model) do
|
1211
|
-
is :timestamped, :a => 1, :b => 2
|
1212
|
-
end
|
1213
|
-
end.should_not raise_error(LoadError)
|
1214
|
-
|
1215
|
-
c.should respond_to(:stamp_opts)
|
1216
|
-
c.stamp_opts.should == {:a => 1, :b => 2}
|
1217
|
-
|
1218
|
-
m = c.new
|
1219
|
-
m.should respond_to(:get_stamp)
|
1220
|
-
m.should respond_to(:abc)
|
1221
|
-
m.abc.should == {:a => 1, :b => 2}
|
1222
|
-
t = Time.now
|
1223
|
-
m[:stamp] = t
|
1224
|
-
m.get_stamp.should == t
|
1225
|
-
|
1226
|
-
c.should respond_to(:deff)
|
1227
|
-
c.deff.should == {:a => 1, :b => 2}
|
1228
|
-
end
|
1229
297
|
end
|
1230
298
|
|
1231
|
-
|
1232
|
-
setup do
|
1233
|
-
MODEL_DB.reset
|
1234
|
-
|
1235
|
-
@c = Class.new(Sequel::Model(:items)) do
|
1236
|
-
def self.columns; [:x, :y]; end
|
1237
|
-
end
|
1238
|
-
end
|
1239
|
-
|
1240
|
-
specify "should filter the given params using the model columns" do
|
1241
|
-
@c.create_with_params(:x => 1, :z => 2)
|
1242
|
-
MODEL_DB.sqls.first.should == "INSERT INTO items (x) VALUES (1)"
|
1243
|
-
|
1244
|
-
MODEL_DB.reset
|
1245
|
-
@c.create_with_params(:y => 1, :abc => 2)
|
1246
|
-
MODEL_DB.sqls.first.should == "INSERT INTO items (y) VALUES (1)"
|
1247
|
-
end
|
1248
|
-
|
1249
|
-
specify "should be aliased by create_with" do
|
1250
|
-
@c.create_with(:x => 1, :z => 2)
|
1251
|
-
MODEL_DB.sqls.first.should == "INSERT INTO items (x) VALUES (1)"
|
1252
|
-
|
1253
|
-
MODEL_DB.reset
|
1254
|
-
@c.create_with(:y => 1, :abc => 2)
|
1255
|
-
MODEL_DB.sqls.first.should == "INSERT INTO items (y) VALUES (1)"
|
1256
|
-
end
|
1257
|
-
end
|
299
|
+
describe Sequel::Model, ".find_or_create" do
|
1258
300
|
|
1259
|
-
|
1260
|
-
setup do
|
301
|
+
before(:each) do
|
1261
302
|
MODEL_DB.reset
|
1262
303
|
@c = Class.new(Sequel::Model(:items)) do
|
1263
304
|
no_primary_key
|
1264
305
|
end
|
1265
306
|
end
|
1266
307
|
|
1267
|
-
|
308
|
+
it "should find the record" do
|
1268
309
|
@c.find_or_create(:x => 1)
|
1269
310
|
MODEL_DB.sqls.should == ["SELECT * FROM items WHERE (x = 1) LIMIT 1"]
|
1270
311
|
|
1271
312
|
MODEL_DB.reset
|
1272
313
|
end
|
1273
314
|
|
1274
|
-
|
315
|
+
it "should create the record if not found" do
|
1275
316
|
@c.meta_def(:find) do |*args|
|
1276
317
|
dataset.filter(*args).first
|
1277
318
|
nil
|
@@ -1285,8 +326,9 @@ context "Model.find_or_create" do
|
|
1285
326
|
end
|
1286
327
|
end
|
1287
328
|
|
1288
|
-
|
1289
|
-
|
329
|
+
describe Sequel::Model, ".delete_all" do
|
330
|
+
|
331
|
+
before(:each) do
|
1290
332
|
MODEL_DB.reset
|
1291
333
|
@c = Class.new(Sequel::Model(:items)) do
|
1292
334
|
no_primary_key
|
@@ -1295,14 +337,16 @@ context "Model.delete_all" do
|
|
1295
337
|
@c.dataset.meta_def(:delete) {MODEL_DB << delete_sql}
|
1296
338
|
end
|
1297
339
|
|
1298
|
-
|
340
|
+
it "should delete all records in the dataset" do
|
1299
341
|
@c.delete_all
|
1300
342
|
MODEL_DB.sqls.should == ["DELETE FROM items"]
|
1301
343
|
end
|
344
|
+
|
1302
345
|
end
|
1303
346
|
|
1304
|
-
|
1305
|
-
|
347
|
+
describe Sequel::Model, ".destroy_all" do
|
348
|
+
|
349
|
+
before(:each) do
|
1306
350
|
MODEL_DB.reset
|
1307
351
|
@c = Class.new(Sequel::Model(:items)) do
|
1308
352
|
no_primary_key
|
@@ -1311,28 +355,45 @@ context "Model.destroy_all" do
|
|
1311
355
|
@c.dataset.meta_def(:delete) {MODEL_DB << delete_sql}
|
1312
356
|
end
|
1313
357
|
|
1314
|
-
|
358
|
+
it "should delete all records in the dataset" do
|
1315
359
|
@c.destroy_all
|
1316
360
|
MODEL_DB.sqls.should == ["DELETE FROM items"]
|
1317
361
|
end
|
362
|
+
|
363
|
+
it "should call dataset destroy method if *_destroy hooks exist" do
|
364
|
+
@c.dataset.stub!(:destroy).and_return(true)
|
365
|
+
@c.should_receive(:has_hooks?).with(:before_destroy).and_return(true)
|
366
|
+
@c.destroy_all
|
367
|
+
end
|
368
|
+
|
369
|
+
it "should call dataset delete method if no hooks are present" do
|
370
|
+
@c.dataset.stub!(:delete).and_return(true)
|
371
|
+
@c.should_receive(:has_hooks?).with(:before_destroy).and_return(false)
|
372
|
+
@c.should_receive(:has_hooks?).with(:after_destroy).and_return(false)
|
373
|
+
@c.destroy_all
|
374
|
+
end
|
375
|
+
|
1318
376
|
end
|
1319
377
|
|
1320
|
-
|
1321
|
-
|
1322
|
-
|
378
|
+
describe Sequel::Model, ".join" do
|
379
|
+
|
380
|
+
before(:each) do
|
381
|
+
MODEL_DB.reset
|
1323
382
|
@c = Class.new(Sequel::Model(:items)) do
|
1324
383
|
no_primary_key
|
1325
384
|
end
|
1326
385
|
end
|
1327
386
|
|
1328
|
-
|
387
|
+
it "should format proper SQL" do
|
1329
388
|
@c.join(:atts, :item_id => :id).sql.should == \
|
1330
389
|
"SELECT items.* FROM items INNER JOIN atts ON (atts.item_id = items.id)"
|
1331
390
|
end
|
391
|
+
|
1332
392
|
end
|
1333
393
|
|
1334
|
-
|
1335
|
-
|
394
|
+
describe Sequel::Model, ".all" do
|
395
|
+
|
396
|
+
before(:each) do
|
1336
397
|
MODEL_DB.reset
|
1337
398
|
@c = Class.new(Sequel::Model(:items)) do
|
1338
399
|
no_primary_key
|
@@ -1341,7 +402,175 @@ context "Model.all" do
|
|
1341
402
|
@c.dataset.meta_def(:all) {1234}
|
1342
403
|
end
|
1343
404
|
|
1344
|
-
|
405
|
+
it "should return all records in the dataset" do
|
1345
406
|
@c.all.should == 1234
|
1346
407
|
end
|
1347
|
-
|
408
|
+
|
409
|
+
end
|
410
|
+
|
411
|
+
class DummyModelBased < Sequel::Model(:blog)
|
412
|
+
end
|
413
|
+
|
414
|
+
describe Sequel::Model, "(:tablename)" do
|
415
|
+
|
416
|
+
it "should allow reopening of descendant classes" do
|
417
|
+
proc do
|
418
|
+
eval "class DummyModelBased < Sequel::Model(:blog); end"
|
419
|
+
end.should_not raise_error
|
420
|
+
end
|
421
|
+
|
422
|
+
end
|
423
|
+
|
424
|
+
describe Sequel::Model, ".create" do
|
425
|
+
|
426
|
+
before(:each) do
|
427
|
+
MODEL_DB.reset
|
428
|
+
@c = Class.new(Sequel::Model(:items))
|
429
|
+
end
|
430
|
+
|
431
|
+
it "should be able to create rows in the associated table" do
|
432
|
+
o = @c.create(:x => 1)
|
433
|
+
o.class.should == @c
|
434
|
+
MODEL_DB.sqls.should == ['INSERT INTO items (x) VALUES (1)', "SELECT * FROM items WHERE (id IN ('INSERT INTO items (x) VALUES (1)')) LIMIT 1"]
|
435
|
+
end
|
436
|
+
|
437
|
+
it "should be able to create rows without any values specified" do
|
438
|
+
o = @c.create
|
439
|
+
o.class.should == @c
|
440
|
+
MODEL_DB.sqls.should == ["INSERT INTO items DEFAULT VALUES", "SELECT * FROM items WHERE (id IN ('INSERT INTO items DEFAULT VALUES')) LIMIT 1"]
|
441
|
+
end
|
442
|
+
|
443
|
+
end
|
444
|
+
|
445
|
+
describe Sequel::Model, "A model class without a primary key" do
|
446
|
+
|
447
|
+
before(:each) do
|
448
|
+
MODEL_DB.reset
|
449
|
+
@c = Class.new(Sequel::Model(:items)) do
|
450
|
+
no_primary_key
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
it "should be able to insert records without selecting them back" do
|
455
|
+
i = nil
|
456
|
+
proc {i = @c.create(:x => 1)}.should_not raise_error
|
457
|
+
i.class.should be(@c)
|
458
|
+
i.values.to_hash.should == {:x => 1}
|
459
|
+
|
460
|
+
MODEL_DB.sqls.should == ['INSERT INTO items (x) VALUES (1)']
|
461
|
+
end
|
462
|
+
|
463
|
+
it "should raise when deleting" do
|
464
|
+
o = @c.new
|
465
|
+
proc {o.delete}.should raise_error
|
466
|
+
end
|
467
|
+
|
468
|
+
it "should insert a record when saving" do
|
469
|
+
o = @c.new(:x => 2)
|
470
|
+
o.should be_new
|
471
|
+
o.save
|
472
|
+
MODEL_DB.sqls.should == ['INSERT INTO items (x) VALUES (2)']
|
473
|
+
end
|
474
|
+
|
475
|
+
end
|
476
|
+
|
477
|
+
describe Sequel::Model, "attribute accessors" do
|
478
|
+
|
479
|
+
before(:each) do
|
480
|
+
MODEL_DB.reset
|
481
|
+
|
482
|
+
@c = Class.new(Sequel::Model(:items)) do
|
483
|
+
def columns
|
484
|
+
[:id, :x, :y]
|
485
|
+
end
|
486
|
+
end
|
487
|
+
end
|
488
|
+
|
489
|
+
it "should be created dynamically" do
|
490
|
+
o = @c.new
|
491
|
+
|
492
|
+
o.should_not be_respond_to(:x)
|
493
|
+
o.x.should be_nil
|
494
|
+
o.should be_respond_to(:x)
|
495
|
+
|
496
|
+
o.should_not be_respond_to(:x=)
|
497
|
+
o.x = 34
|
498
|
+
o.x.should == 34
|
499
|
+
o.should be_respond_to(:x=)
|
500
|
+
end
|
501
|
+
|
502
|
+
it "should raise for a column that doesn't exist in the dataset" do
|
503
|
+
o = @c.new
|
504
|
+
|
505
|
+
proc {o.x}.should_not raise_error
|
506
|
+
proc {o.xx}.should raise_error(Sequel::Error)
|
507
|
+
|
508
|
+
proc {o.x = 3}.should_not raise_error
|
509
|
+
proc {o.yy = 4}.should raise_error(Sequel::Error)
|
510
|
+
|
511
|
+
proc {o.yy?}.should raise_error(NoMethodError)
|
512
|
+
end
|
513
|
+
|
514
|
+
it "should not raise for a column not in the dataset, but for which there's a value" do
|
515
|
+
o = @c.new
|
516
|
+
|
517
|
+
proc {o.xx}.should raise_error(Sequel::Error)
|
518
|
+
proc {o.yy}.should raise_error(Sequel::Error)
|
519
|
+
|
520
|
+
o.values[:xx] = 123
|
521
|
+
o.values[:yy] = nil
|
522
|
+
|
523
|
+
proc {o.xx; o.yy}.should_not raise_error(Sequel::Error)
|
524
|
+
|
525
|
+
o.xx.should == 123
|
526
|
+
o.yy.should == nil
|
527
|
+
|
528
|
+
proc {o.xx = 3}.should raise_error(Sequel::Error)
|
529
|
+
end
|
530
|
+
|
531
|
+
end
|
532
|
+
|
533
|
+
describe Sequel::Model, ".[]" do
|
534
|
+
|
535
|
+
before(:each) do
|
536
|
+
MODEL_DB.reset
|
537
|
+
|
538
|
+
@c = Class.new(Sequel::Model(:items))
|
539
|
+
|
540
|
+
$cache_dataset_row = {:name => 'sharon', :id => 1}
|
541
|
+
@dataset = @c.dataset
|
542
|
+
$sqls = []
|
543
|
+
@dataset.extend(Module.new {
|
544
|
+
def fetch_rows(sql)
|
545
|
+
$sqls << sql
|
546
|
+
yield $cache_dataset_row
|
547
|
+
end
|
548
|
+
})
|
549
|
+
end
|
550
|
+
|
551
|
+
it "should return the first record for the given pk" do
|
552
|
+
@c[1].should be_a_kind_of(@c)
|
553
|
+
$sqls.last.should == "SELECT * FROM items WHERE (id = 1) LIMIT 1"
|
554
|
+
@c[9999].should be_a_kind_of(@c)
|
555
|
+
$sqls.last.should == "SELECT * FROM items WHERE (id = 9999) LIMIT 1"
|
556
|
+
end
|
557
|
+
|
558
|
+
it "should raise for boolean argument (mistaken comparison)" do
|
559
|
+
# This in order to prevent stuff like Model[:a == 'b']
|
560
|
+
proc {@c[:a == 1]}.should raise_error(Sequel::Error)
|
561
|
+
proc {@c[:a != 1]}.should raise_error(Sequel::Error)
|
562
|
+
end
|
563
|
+
|
564
|
+
it "should work correctly for custom primary key" do
|
565
|
+
@c.set_primary_key :name
|
566
|
+
@c['sharon'].should be_a_kind_of(@c)
|
567
|
+
$sqls.last.should == "SELECT * FROM items WHERE (name = 'sharon') LIMIT 1"
|
568
|
+
end
|
569
|
+
|
570
|
+
it "should work correctly for composite primary key" do
|
571
|
+
@c.set_primary_key [:node_id, :kind]
|
572
|
+
@c[3921, 201].should be_a_kind_of(@c)
|
573
|
+
$sqls.last.should =~ \
|
574
|
+
/^SELECT \* FROM items WHERE (\(node_id = 3921\) AND \(kind = 201\))|(\(kind = 201\) AND \(node_id = 3921\)) LIMIT 1$/
|
575
|
+
end
|
576
|
+
end
|