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.
@@ -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__), 'spec_helper')
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('model')
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, 'w/ primary key' do
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
- class DummyModelBased < Sequel::Model(:blog)
236
- end
237
-
238
- context "Sequel::Model()" do
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
- context "A model class" do
247
- setup do
248
- MODEL_DB.reset
249
- @c = Class.new(Sequel::Model(:items))
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
- specify "should be able to create rows in the associated table" do
253
- o = @c.create(:x => 1)
254
- o.class.should == @c
255
- MODEL_DB.sqls.should == ['INSERT INTO items (x) VALUES (1)', "SELECT * FROM items WHERE (id IN ('INSERT INTO items (x) VALUES (1)')) LIMIT 1"]
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
- context "A model class without a primary key" do
266
- setup do
267
- MODEL_DB.reset
268
- @c = Class.new(Sequel::Model(:items)) do
269
- no_primary_key
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
- specify "should be able to insert records without selecting them back" do
274
- i = nil
275
- proc {i = @c.create(:x => 1)}.should_not raise_error
276
- i.class.should be(@c)
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
- specify "should insert a record when saving" do
288
- o = @c.new(:x => 2)
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
- MODEL_DB.sqls.should == ['INSERT INTO items (x) VALUES (2)']
124
+ o.should_not be_new
125
+ o.should_not be_new_record
292
126
  end
293
- end
294
127
 
295
- context "Model#serialize" do
296
- setup do
297
- MODEL_DB.reset
298
- end
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
- @c.create(:abc => 1)
307
- @c.create(:abc => "hello")
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
- @c.create(:def => 1)
323
- @c.create(:def => "hello")
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
- @c.create(:abc => 1)
338
- @c.create(:abc => "hello")
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
- context "Model attribute accessors" do
394
- setup do
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)) do
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
- proc {o.yy?}.should raise_error(NoMethodError)
427
- end
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
- o.values[:xx] = 123
436
- o.values[:yy] = nil
161
+ @c.subset(:new_only) {:age == 'new'}
437
162
 
438
- proc {o.xx; o.yy}.should_not raise_error(Sequel::Error)
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
- o.xx.should == 123
441
- o.yy.should == nil
166
+ @c.subset(:pricey) {:price > 100}
442
167
 
443
- proc {o.xx = 3}.should raise_error(Sequel::Error)
444
- end
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
- n = @c2.new(:id => 1234)
1015
- a = n.attributes
1016
- a.should be_a_kind_of(Sequel::Dataset)
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
- context "Model#pk" do
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
- @m.no_primary_key
1058
- m = @m.new(:id => 111, :x => 2, :y => 3)
1059
- proc {m.pk}.should raise_error(Sequel::Error)
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
- end
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
- context "Model#pk_hash" do
1064
- setup do
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
- specify "should be default return the value of the :id column" do
1069
- m = @m.new(:id => 111, :x => 2, :y => 3)
1070
- m.pk_hash.should == {:id => 111}
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
- specify "should be return the primary key value for custom primary key" do
1074
- @m.set_primary_key :x
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
- specify "should be return the primary key value for composite primary key" do
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
- specify "should raise if no primary key" do
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
- @m.no_primary_key
1091
- m = @m.new(:id => 111, :x => 2, :y => 3)
1092
- proc {m.pk_hash}.should raise_error(Sequel::Error)
216
+ before(:each) do
217
+ MODEL_DB.reset
218
+ @c = Class.new(Sequel::Model(:items))
1093
219
  end
1094
- end
1095
-
1096
- context "A Model constructor" do
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
- specify "should accept a hash" do
1102
- m = @m.new(:a => 1, :b => 2)
1103
- m.values.should == {:a => 1, :b => 2}
1104
- m.should be_new
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
- context "Model magic methods" do
1117
- setup do
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
- specify "should support order_by_xxx" do
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
- specify "should support group_by_xxx" do
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
- specify "should support count_by_xxx" do
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
- specify "should support filter_by_xxx" do
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
- specify "should support all_by_xxx" do
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
- specify "should support find_by_xxx" do
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
- specify "should support first_by_xxx" do
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
- specify "should support last_by_xxx" do
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
- context "Model#create_with_params" do
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
- context "Model.find_or_create" do
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
- specify "should find the record" do
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
- specify "should create the record if not found" do
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
- context "Model.delete_all" do
1289
- setup do
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
- specify "should delete all records in the dataset" do
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
- context "Model.destroy_all" do
1305
- setup do
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
- specify "should delete all records in the dataset" do
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
- context "Model.join" do
1321
- setup do
1322
- MODEL_DB.reset
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
- specify "should format proper SQL" do
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
- context "Model.all" do
1335
- setup do
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
- specify "should return all records in the dataset" do
405
+ it "should return all records in the dataset" do
1345
406
  @c.all.should == 1234
1346
407
  end
1347
- end
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