sequel 1.3 → 1.4.0

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