sequel 1.3 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,260 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ describe Sequel::Model, "eager" do
4
+ before(:each) do
5
+ MODEL_DB.reset
6
+
7
+ class EagerAlbum < Sequel::Model(:albums)
8
+ def columns; [:id, :band_id]; end
9
+ many_to_one :band, :class=>'EagerBand', :key=>:band_id
10
+ one_to_many :tracks, :class=>'EagerTrack', :key=>:album_id
11
+ many_to_many :genres, :class=>'EagerGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag
12
+ end
13
+
14
+ class EagerBand < Sequel::Model(:bands)
15
+ def columns; [:id]; end
16
+ one_to_many :albums, :class=>'EagerAlbum', :key=>:band_id, :eager=>:tracks
17
+ many_to_many :members, :class=>'EagerBandMember', :left_key=>:band_id, :right_key=>:member_id, :join_table=>:bm
18
+ end
19
+
20
+ class EagerTrack < Sequel::Model(:tracks)
21
+ def columns; [:id, :album_id]; end
22
+ many_to_one :album, :class=>'EagerAlbum', :key=>:album_id
23
+ end
24
+
25
+ class EagerGenre < Sequel::Model(:genres)
26
+ def columns; [:id]; end
27
+ many_to_many :albums, :class=>'EagerAlbum', :left_key=>:genre_id, :right_key=>:album_id, :join_table=>:ag
28
+ end
29
+
30
+ class EagerBandMember < Sequel::Model(:members)
31
+ def columns; [:id]; end
32
+ many_to_many :bands, :class=>'EagerBand', :left_key=>:member_id, :right_key=>:band_id, :join_table=>:bm, :order =>:id
33
+ end
34
+
35
+ EagerAlbum.dataset.extend(Module.new {
36
+ def fetch_rows(sql)
37
+ h = {:id => 1, :band_id=> 2}
38
+ h.merge!(:x_foreign_key_x=>4) if sql =~ /ag\.genre_id/
39
+ @db << sql
40
+ yield h
41
+ end
42
+ })
43
+
44
+ EagerBand.dataset.extend(Module.new {
45
+ def fetch_rows(sql)
46
+ h = {:id => 2}
47
+ h.merge!(:x_foreign_key_x=>5) if sql =~ /bm\.member_id/
48
+ @db << sql
49
+ yield h
50
+ end
51
+ })
52
+
53
+ EagerTrack.dataset.extend(Module.new {
54
+ def fetch_rows(sql)
55
+ @db << sql
56
+ yield({:id => 3, :album_id => 1})
57
+ end
58
+ })
59
+
60
+ EagerGenre.dataset.extend(Module.new {
61
+ def fetch_rows(sql)
62
+ h = {:id => 4}
63
+ h.merge!(:x_foreign_key_x=>1) if sql =~ /ag\.album_id/
64
+ @db << sql
65
+ yield h
66
+ end
67
+ })
68
+
69
+ EagerBandMember.dataset.extend(Module.new {
70
+ def fetch_rows(sql)
71
+ h = {:id => 5}
72
+ h.merge!(:x_foreign_key_x=>2) if sql =~ /bm\.band_id/
73
+ @db << sql
74
+ yield h
75
+ end
76
+ })
77
+ end
78
+
79
+ it "should eagerly load a single many_to_one association" do
80
+ a = EagerAlbum.eager(:band).all
81
+ a.should be_a_kind_of(Array)
82
+ a.size.should == 1
83
+ a.first.should be_a_kind_of(EagerAlbum)
84
+ a.first.values.should == {:id => 1, :band_id => 2}
85
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM bands WHERE (id IN (2))']
86
+ a = a.first
87
+ a.band.should be_a_kind_of(EagerBand)
88
+ a.band.values.should == {:id => 2}
89
+ MODEL_DB.sqls.length.should == 2
90
+ end
91
+
92
+ it "should eagerly load a single one_to_many association" do
93
+ a = EagerAlbum.eager(:tracks).all
94
+ a.should be_a_kind_of(Array)
95
+ a.size.should == 1
96
+ a.first.should be_a_kind_of(EagerAlbum)
97
+ a.first.values.should == {:id => 1, :band_id => 2}
98
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM tracks WHERE (album_id IN (1))']
99
+ a = a.first
100
+ a.tracks.should be_a_kind_of(Array)
101
+ a.tracks.size.should == 1
102
+ a.tracks.first.should be_a_kind_of(EagerTrack)
103
+ a.tracks.first.values.should == {:id => 3, :album_id=>1}
104
+ MODEL_DB.sqls.length.should == 2
105
+ end
106
+
107
+ it "should eagerly load a single many_to_many association" do
108
+ a = EagerAlbum.eager(:genres).all
109
+ a.should be_a_kind_of(Array)
110
+ a.size.should == 1
111
+ a.first.should be_a_kind_of(EagerAlbum)
112
+ a.first.values.should == {:id => 1, :band_id => 2}
113
+ MODEL_DB.sqls.length.should == 2
114
+ MODEL_DB.sqls[0].should == 'SELECT * FROM albums'
115
+ ["SELECT genres.*, ag.album_id AS x_foreign_key_x FROM genres INNER JOIN ag ON (ag.genre_id = genres.id) AND (ag.album_id IN (1))",
116
+ "SELECT genres.*, ag.album_id AS x_foreign_key_x FROM genres INNER JOIN ag ON (ag.album_id IN (1)) AND (ag.genre_id = genres.id)"
117
+ ].should(include(MODEL_DB.sqls[1]))
118
+ a = a.first
119
+ a.genres.should be_a_kind_of(Array)
120
+ a.genres.size.should == 1
121
+ a.genres.first.should be_a_kind_of(EagerGenre)
122
+ a.genres.first.values.should == {:id => 4}
123
+ MODEL_DB.sqls.length.should == 2
124
+ end
125
+
126
+ it "should eagerly load multiple associations" do
127
+ a = EagerAlbum.eager(:genres, :tracks, :band).all
128
+ a.should be_a_kind_of(Array)
129
+ a.size.should == 1
130
+ a.first.should be_a_kind_of(EagerAlbum)
131
+ a.first.values.should == {:id => 1, :band_id => 2}
132
+ MODEL_DB.sqls.length.should == 4
133
+ MODEL_DB.sqls[0].should == 'SELECT * FROM albums'
134
+ MODEL_DB.sqls[1..-1].should(include('SELECT * FROM bands WHERE (id IN (2))'))
135
+ MODEL_DB.sqls[1..-1].should(include('SELECT * FROM tracks WHERE (album_id IN (1))'))
136
+ sqls = MODEL_DB.sqls[1..-1] - ['SELECT * FROM bands WHERE (id IN (2))', 'SELECT * FROM tracks WHERE (album_id IN (1))']
137
+ ["SELECT genres.*, ag.album_id AS x_foreign_key_x FROM genres INNER JOIN ag ON (ag.genre_id = genres.id) AND (ag.album_id IN (1))",
138
+ "SELECT genres.*, ag.album_id AS x_foreign_key_x FROM genres INNER JOIN ag ON (ag.album_id IN (1)) AND (ag.genre_id = genres.id)"
139
+ ].should(include(sqls[0]))
140
+ a = a.first
141
+ a.band.should be_a_kind_of(EagerBand)
142
+ a.band.values.should == {:id => 2}
143
+ a.tracks.should be_a_kind_of(Array)
144
+ a.tracks.size.should == 1
145
+ a.tracks.first.should be_a_kind_of(EagerTrack)
146
+ a.tracks.first.values.should == {:id => 3, :album_id=>1}
147
+ a.genres.should be_a_kind_of(Array)
148
+ a.genres.size.should == 1
149
+ a.genres.first.should be_a_kind_of(EagerGenre)
150
+ a.genres.first.values.should == {:id => 4}
151
+ MODEL_DB.sqls.length.should == 4
152
+ end
153
+
154
+ it "should allow cascading of eager loading for associations of associated models" do
155
+ a = EagerTrack.eager(:album=>{:band=>:members}).all
156
+ a.should be_a_kind_of(Array)
157
+ a.size.should == 1
158
+ a.first.should be_a_kind_of(EagerTrack)
159
+ a.first.values.should == {:id => 3, :album_id => 1}
160
+ MODEL_DB.sqls.length.should == 4
161
+ MODEL_DB.sqls[0...-1].should == ['SELECT * FROM tracks',
162
+ 'SELECT * FROM albums WHERE (id IN (1))',
163
+ 'SELECT * FROM bands WHERE (id IN (2))']
164
+ ["SELECT members.*, bm.band_id AS x_foreign_key_x FROM members INNER JOIN bm ON (bm.member_id = members.id) AND (bm.band_id IN (2))",
165
+ "SELECT members.*, bm.band_id AS x_foreign_key_x FROM members INNER JOIN bm ON (bm.band_id IN (2)) AND (bm.member_id = members.id)"
166
+ ].should(include(MODEL_DB.sqls[-1]))
167
+ a = a.first
168
+ a.album.should be_a_kind_of(EagerAlbum)
169
+ a.album.values.should == {:id => 1, :band_id => 2}
170
+ a.album.band.should be_a_kind_of(EagerBand)
171
+ a.album.band.values.should == {:id => 2}
172
+ a.album.band.members.should be_a_kind_of(Array)
173
+ a.album.band.members.size.should == 1
174
+ a.album.band.members.first.should be_a_kind_of(EagerBandMember)
175
+ a.album.band.members.first.values.should == {:id => 5}
176
+ MODEL_DB.sqls.length.should == 4
177
+ end
178
+
179
+ it "should cascade eagerly loading when the :eager association option is used" do
180
+ a = EagerBand.eager(:albums).all
181
+ a.should be_a_kind_of(Array)
182
+ a.size.should == 1
183
+ a.first.should be_a_kind_of(EagerBand)
184
+ a.first.values.should == {:id => 2}
185
+ MODEL_DB.sqls.should == ['SELECT * FROM bands',
186
+ 'SELECT * FROM albums WHERE (band_id IN (2))',
187
+ 'SELECT * FROM tracks WHERE (album_id IN (1))']
188
+ a = a.first
189
+ a.albums.should be_a_kind_of(Array)
190
+ a.albums.size.should == 1
191
+ a.albums.first.should be_a_kind_of(EagerAlbum)
192
+ a.albums.first.values.should == {:id => 1, :band_id => 2}
193
+ a = a.albums.first
194
+ a.tracks.should be_a_kind_of(Array)
195
+ a.tracks.size.should == 1
196
+ a.tracks.first.should be_a_kind_of(EagerTrack)
197
+ a.tracks.first.values.should == {:id => 3, :album_id => 1}
198
+ MODEL_DB.sqls.length.should == 3
199
+ end
200
+
201
+ it "should respect :eager when lazily loading an association" do
202
+ a = EagerBand.all
203
+ a.should be_a_kind_of(Array)
204
+ a.size.should == 1
205
+ a.first.should be_a_kind_of(EagerBand)
206
+ a.first.values.should == {:id => 2}
207
+ MODEL_DB.sqls.should == ['SELECT * FROM bands']
208
+ a = a.first
209
+ a.albums.all
210
+ MODEL_DB.sqls.should == ['SELECT * FROM bands',
211
+ 'SELECT * FROM albums WHERE (band_id = 2)',
212
+ 'SELECT * FROM tracks WHERE (album_id IN (1))']
213
+ a.albums.should be_a_kind_of(Array)
214
+ a.albums.size.should == 1
215
+ a.albums.first.should be_a_kind_of(EagerAlbum)
216
+ a.albums.first.values.should == {:id => 1, :band_id => 2}
217
+ a = a.albums.first
218
+ a.tracks.should be_a_kind_of(Array)
219
+ a.tracks.size.should == 1
220
+ a.tracks.first.should be_a_kind_of(EagerTrack)
221
+ a.tracks.first.values.should == {:id => 3, :album_id => 1}
222
+ MODEL_DB.sqls.length.should == 3
223
+ end
224
+
225
+ it "should respect :order when eagerly loading" do
226
+ a = EagerBandMember.eager(:bands).all
227
+ a.should be_a_kind_of(Array)
228
+ a.size.should == 1
229
+ a.first.should be_a_kind_of(EagerBandMember)
230
+ a.first.values.should == {:id => 5}
231
+ MODEL_DB.sqls.length.should == 2
232
+ MODEL_DB.sqls[0].should == 'SELECT * FROM members'
233
+ ['SELECT bands.*, bm.member_id AS x_foreign_key_x FROM bands INNER JOIN bm ON (bm.band_id = bands.id) AND (bm.member_id IN (5)) ORDER BY id',
234
+ 'SELECT bands.*, bm.member_id AS x_foreign_key_x FROM bands INNER JOIN bm ON (bm.member_id IN (5)) AND (bm.band_id = bands.id) ORDER BY id'
235
+ ].should(include(MODEL_DB.sqls[1]))
236
+ a = a.first
237
+ a.bands.should be_a_kind_of(Array)
238
+ a.bands.size.should == 1
239
+ a.bands.first.should be_a_kind_of(EagerBand)
240
+ a.bands.first.values.should == {:id => 2}
241
+ MODEL_DB.sqls.length.should == 2
242
+ end
243
+
244
+ it "should populate the reciprocal many_to_one association when eagerly loading the one_to_many association" do
245
+ a = EagerAlbum.eager(:tracks).all
246
+ a.should be_a_kind_of(Array)
247
+ a.size.should == 1
248
+ a.first.should be_a_kind_of(EagerAlbum)
249
+ a.first.values.should == {:id => 1, :band_id => 2}
250
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM tracks WHERE (album_id IN (1))']
251
+ a = a.first
252
+ a.tracks.should be_a_kind_of(Array)
253
+ a.tracks.size.should == 1
254
+ a.tracks.first.should be_a_kind_of(EagerTrack)
255
+ a.tracks.first.values.should == {:id => 3, :album_id=>1}
256
+ a.tracks.first.album.should be_a_kind_of(EagerAlbum)
257
+ a.tracks.first.album.should == a
258
+ MODEL_DB.sqls.length.should == 2
259
+ end
260
+ end
@@ -0,0 +1,269 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ describe "Model hooks" do
4
+ before do
5
+ MODEL_DB.reset
6
+
7
+ @hooks = [
8
+ :after_initialize,
9
+ :before_create,
10
+ :after_create,
11
+ :before_update,
12
+ :after_update,
13
+ :before_save,
14
+ :after_save,
15
+ :before_destroy,
16
+ :after_destroy
17
+ ]
18
+
19
+ # @hooks.each {|h| Sequel::Model.class_def(h) {}}
20
+ end
21
+
22
+ specify "should be definable using def <hook name>" do
23
+ c = Class.new(Sequel::Model) do
24
+ def before_save
25
+ "hi there"
26
+ end
27
+ end
28
+
29
+ c.new.before_save.should == 'hi there'
30
+ end
31
+
32
+ specify "should be definable using a block" do
33
+ $adds = []
34
+ c = Class.new(Sequel::Model) do
35
+ before_save {$adds << 'hi'}
36
+ end
37
+
38
+ c.new.before_save
39
+ $adds.should == ['hi']
40
+ end
41
+
42
+ specify "should be definable using a method name" do
43
+ $adds = []
44
+ c = Class.new(Sequel::Model) do
45
+ def bye; $adds << 'bye'; end
46
+ before_save :bye
47
+ end
48
+
49
+ c.new.before_save
50
+ $adds.should == ['bye']
51
+ end
52
+
53
+ specify "should be additive" do
54
+ $adds = []
55
+ c = Class.new(Sequel::Model) do
56
+ before_save {$adds << 'hyiyie'}
57
+ before_save {$adds << 'byiyie'}
58
+ end
59
+
60
+ c.new.before_save
61
+ $adds.should == ['hyiyie', 'byiyie']
62
+ end
63
+
64
+ specify "should be inheritable" do
65
+ # pending
66
+
67
+ $adds = []
68
+ a = Class.new(Sequel::Model) do
69
+ before_save {$adds << '123'}
70
+ end
71
+
72
+ b = Class.new(a) do
73
+ before_save {$adds << '456'}
74
+ before_save {$adds << '789'}
75
+ end
76
+
77
+ b.new.before_save
78
+ $adds.should == ['123', '456', '789']
79
+ end
80
+
81
+ specify "should be overridable in descendant classes" do
82
+ $adds = []
83
+ a = Class.new(Sequel::Model) do
84
+ before_save {$adds << '123'}
85
+ end
86
+
87
+ b = Class.new(a) do
88
+ def before_save; $adds << '456'; end
89
+ end
90
+
91
+ a.new.before_save
92
+ $adds.should == ['123']
93
+ $adds = []
94
+ b.new.before_save
95
+ $adds.should == ['456']
96
+ end
97
+
98
+ specify "should stop processing if a hook returns false" do
99
+ $flag = true
100
+ $adds = []
101
+
102
+ a = Class.new(Sequel::Model) do
103
+ before_save {$adds << 'blah'; $flag}
104
+ before_save {$adds << 'cruel'}
105
+ end
106
+
107
+ a.new.before_save
108
+ $adds.should == ['blah', 'cruel']
109
+
110
+ # chain should not break on nil
111
+ $adds = []
112
+ $flag = nil
113
+ a.new.before_save
114
+ $adds.should == ['blah', 'cruel']
115
+
116
+ $adds = []
117
+ $flag = false
118
+ a.new.before_save
119
+ $adds.should == ['blah']
120
+
121
+ b = Class.new(a) do
122
+ before_save {$adds << 'mau'}
123
+ end
124
+
125
+ $adds = []
126
+ b.new.before_save
127
+ $adds.should == ['blah']
128
+ end
129
+ end
130
+
131
+ describe "Model#after_initialize" do
132
+ specify "should be called after initialization" do
133
+ $values1 = nil
134
+
135
+ a = Class.new(Sequel::Model) do
136
+ after_initialize do
137
+ $values1 = @values.clone
138
+ raise Sequel::Error if @values[:blow]
139
+ end
140
+ end
141
+
142
+ a.new(:x => 1, :y => 2)
143
+ $values1.should == {:x => 1, :y => 2}
144
+
145
+ proc {a.new(:blow => true)}.should raise_error(Sequel::Error)
146
+ end
147
+ end
148
+
149
+ describe "Model#before_create && Model#after_create" do
150
+ setup do
151
+ MODEL_DB.reset
152
+
153
+ @c = Class.new(Sequel::Model(:items)) do
154
+ no_primary_key
155
+
156
+ before_create {MODEL_DB << "BLAH before"}
157
+ after_create {MODEL_DB << "BLAH after"}
158
+ end
159
+ end
160
+
161
+ specify "should be called around new record creation" do
162
+ @c.create(:x => 2)
163
+ MODEL_DB.sqls.should == [
164
+ 'BLAH before',
165
+ 'INSERT INTO items (x) VALUES (2)',
166
+ 'BLAH after'
167
+ ]
168
+ end
169
+ end
170
+
171
+ describe "Model#before_update && Model#after_update" do
172
+ setup do
173
+ MODEL_DB.reset
174
+
175
+ @c = Class.new(Sequel::Model(:items)) do
176
+ before_update {MODEL_DB << "BLAH before"}
177
+ after_update {MODEL_DB << "BLAH after"}
178
+ end
179
+ end
180
+
181
+ specify "should be called around record update" do
182
+ m = @c.load(:id => 2233)
183
+ m.save
184
+ MODEL_DB.sqls.should == [
185
+ 'BLAH before',
186
+ 'UPDATE items SET id = 2233 WHERE (id = 2233)',
187
+ 'BLAH after'
188
+ ]
189
+ end
190
+ end
191
+
192
+ describe "Model#before_save && Model#after_save" do
193
+ setup do
194
+ MODEL_DB.reset
195
+
196
+ @c = Class.new(Sequel::Model(:items)) do
197
+ before_save {MODEL_DB << "BLAH before"}
198
+ after_save {MODEL_DB << "BLAH after"}
199
+ end
200
+ end
201
+
202
+ specify "should be called around record update" do
203
+ m = @c.load(:id => 2233)
204
+ m.save
205
+ MODEL_DB.sqls.should == [
206
+ 'BLAH before',
207
+ 'UPDATE items SET id = 2233 WHERE (id = 2233)',
208
+ 'BLAH after'
209
+ ]
210
+ end
211
+
212
+ specify "should be called around record creation" do
213
+ @c.no_primary_key
214
+ @c.create(:x => 2)
215
+ MODEL_DB.sqls.should == [
216
+ 'BLAH before',
217
+ 'INSERT INTO items (x) VALUES (2)',
218
+ 'BLAH after'
219
+ ]
220
+ end
221
+ end
222
+
223
+ describe "Model#before_destroy && Model#after_destroy" do
224
+ setup do
225
+ MODEL_DB.reset
226
+
227
+ @c = Class.new(Sequel::Model(:items)) do
228
+ before_destroy {MODEL_DB << "BLAH before"}
229
+ after_destroy {MODEL_DB << "BLAH after"}
230
+
231
+ def delete
232
+ MODEL_DB << "DELETE BLAH"
233
+ end
234
+ end
235
+ end
236
+
237
+ specify "should be called around record update" do
238
+ m = @c.new(:id => 2233)
239
+ m.destroy
240
+ MODEL_DB.sqls.should == [
241
+ 'BLAH before',
242
+ 'DELETE BLAH',
243
+ 'BLAH after'
244
+ ]
245
+ end
246
+ end
247
+
248
+ describe "Model#has_hooks?" do
249
+ setup do
250
+ @c = Class.new(Sequel::Model)
251
+ end
252
+
253
+ specify "should return false if no hooks are defined" do
254
+ @c.has_hooks?(:before_save).should be_false
255
+ end
256
+
257
+ specify "should return true if hooks are defined" do
258
+ @c.before_save {'blah'}
259
+ @c.has_hooks?(:before_save).should be_true
260
+ end
261
+
262
+ specify "should return true if hooks are inherited" do
263
+ @d = Class.new(@c)
264
+ @d.has_hooks?(:before_save).should be_false
265
+
266
+ @c.before_save :blah
267
+ @d.has_hooks?(:before_save).should be_true
268
+ end
269
+ end