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