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