mdquery 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,473 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+ require 'mdquery/model'
3
+ require 'set'
4
+
5
+ module MDQuery
6
+ module Model
7
+ describe CASTS do
8
+ it "sym cast should cast a string to a symbol" do
9
+ CASTS[:sym].call("blah").should == :blah
10
+ end
11
+ it "int cast should cast a string to a symbol" do
12
+ cv = CASTS[:int].call("100")
13
+ cv.should == 100
14
+ cv.class.should == 100.class
15
+ end
16
+ it "float cast should cast a string to a float" do
17
+ cv = CASTS[:float].call("3.5")
18
+ cv.should == 3.5
19
+ cv.class.should == 3.5.class
20
+ end
21
+ it "date cast should cast a string to a date" do
22
+ cv = CASTS[:date].call("2011-03-25")
23
+ cv.should == Date.parse("2011-03-25")
24
+ cv.class.should == Date
25
+ end
26
+ it "datetime cast should cast a string to a datetime" do
27
+ cv = CASTS[:datetime].call("2011-03-25 08:47:23")
28
+ cv.should == DateTime.parse("2011-03-25 08:47:23")
29
+ cv.class.should == DateTime
30
+ end
31
+ it "time cast should cast a string to a time" do
32
+ cv = CASTS[:time].call("08:47:23")
33
+ cv.should == Time.parse("08:47:23")
34
+ cv.class.should == Time
35
+ end
36
+
37
+ end
38
+
39
+ describe DimensionSegmentModel do
40
+ def create(attrs={})
41
+ dimension_model = Object.new
42
+ stub(dimension_model).key{:foodim}
43
+ DimensionSegmentModel.new({ :key=>:foo,
44
+ :dimension_model=>dimension_model,
45
+ :fixed_dimension_value=>:foofoo}.merge(attrs))
46
+ end
47
+
48
+ it "should assign attributes on initialization" do
49
+ dimension_model = Object.new
50
+ key = Object.new
51
+ fixed_dimension_value = Object.new
52
+ extract_dimension_query = Object.new
53
+ narrow_proc = Object.new
54
+ values_proc = Object.new
55
+ label_proc = Object.new
56
+ value_cast = Object.new
57
+ measure_modifiers = Object.new
58
+
59
+ dsm = DimensionSegmentModel.new(:dimension_model=>dimension_model,
60
+ :key=>key,
61
+ :fixed_dimension_value=>fixed_dimension_value,
62
+ :narrow_proc=>narrow_proc,
63
+ :values_proc=>values_proc,
64
+ :label_proc=>label_proc,
65
+ :value_cast=>value_cast,
66
+ :measure_modifiers=>measure_modifiers)
67
+ dsm.dimension_model.should == dimension_model
68
+ dsm.key.should == key
69
+ dsm.fixed_dimension_value.should == fixed_dimension_value
70
+ dsm.narrow_proc.should == narrow_proc
71
+ dsm.values_proc.should == values_proc
72
+ dsm.label_proc.should == label_proc
73
+ dsm.value_cast.should == value_cast
74
+ dsm.measure_modifiers.should == measure_modifiers
75
+
76
+ dsm = DimensionSegmentModel.new(:dimension_model=>dimension_model,
77
+ :key=>key,
78
+ :extract_dimension_query=>extract_dimension_query,
79
+ :narrow_proc=>narrow_proc,
80
+ :values_proc=>values_proc,
81
+ :label_proc=>label_proc,
82
+ :value_cast=>value_cast,
83
+ :measure_modifiers=>measure_modifiers)
84
+ dsm.dimension_model.should == dimension_model
85
+ dsm.key.should == key
86
+ dsm.extract_dimension_query.should == extract_dimension_query
87
+ dsm.narrow_proc.should == narrow_proc
88
+ dsm.values_proc.should == values_proc
89
+ dsm.label_proc.should == label_proc
90
+ dsm.value_cast.should == value_cast
91
+ dsm.measure_modifiers.should == measure_modifiers
92
+ end
93
+
94
+ describe "do_narrow" do
95
+ it "should narrow the scope with the narrow_proc" do
96
+ narrow_proc = Object.new
97
+ dsm = create(:narrow_proc=>narrow_proc)
98
+
99
+ scope = Object.new
100
+ narrowed_scope = Object.new
101
+ mock(narrow_proc).call(scope){narrowed_scope}
102
+
103
+ dsm.do_narrow(scope).should == narrowed_scope
104
+ end
105
+
106
+ it "should return the scope unchaged if no narrow_proc" do
107
+ dsm = create
108
+ scope = Object.new
109
+ dsm.do_narrow(scope).should == scope
110
+ end
111
+ end
112
+
113
+ describe "do_cast" do
114
+ it "should cast the value with the referenced Proc if value_cast is given" do
115
+ dsm = create(:value_cast=>:int)
116
+ dsm.do_cast("100").should == 100
117
+ end
118
+
119
+ it "should return the value unchanged if no value_cast is given" do
120
+ dsm = create
121
+ dsm.do_cast("100").should == "100"
122
+ end
123
+ end
124
+
125
+ describe "do_modify" do
126
+ it "should modify the measure_def with the modifier_proc" do
127
+ dsm = create(:measure_modifiers => {:foo=>Proc.new{|mdef| "#{mdef}/12"}})
128
+ dsm.do_modify(:foo, "count(*)").should == "count(*)/12"
129
+ end
130
+
131
+ it "should return the measure_def unchanged if no modifier_proc is given" do
132
+ dsm = create
133
+ dsm.do_modify(:foo, "count(*)").should == "count(*)"
134
+ end
135
+ end
136
+
137
+ describe "select_string" do
138
+ it "should return a quoted aliased fixed_dimension_value if given" do
139
+ mock(ActiveRecord::Base).quote_value(:foofoo){"foofoo"}
140
+ dsm = create
141
+ dsm.select_string.should == "foofoo as foodim"
142
+ end
143
+
144
+ it "should return an aliased version of the extract_dimension_query if given" do
145
+ dsm = create(:fixed_dimension_value=>nil, :extract_dimension_query=>"foocol")
146
+ dsm.select_string.should == "foocol as foodim"
147
+ end
148
+ end
149
+
150
+ describe "group_by_column" do
151
+ it "should return the stringified dimension key" do
152
+ dsm = create
153
+ dsm.group_by_column.should == "foodim"
154
+ end
155
+ end
156
+
157
+ describe "get_values" do
158
+ it "should return a stringified fixed_dimension_value if given" do
159
+ dsm = create
160
+ dsm.get_values(Object.new).should == ["foofoo"]
161
+ end
162
+
163
+ it "should narrow the scope and return distinct values if extract_dimension_query" do
164
+ dsm = create(:fixed_dimension_value=>nil, :extract_dimension_query=>"foocol")
165
+
166
+ r1 = Object.new
167
+ mock(r1).foodim{10}
168
+ r2 = Object.new
169
+ mock(r2).foodim{20}
170
+
171
+ scope = Object.new
172
+ mock(scope).select("distinct foocol as foodim").mock!.all{[r1,r2]}
173
+
174
+ dsm.get_values(scope).should == [10,20]
175
+ end
176
+ end
177
+
178
+ describe "labels" do
179
+ it "should return capitalized stringified keys if no label_proc is given" do
180
+ dsm = create
181
+ dsm.labels([:foo, :foo_bar, "foo_bar_baz"]).should == {:foo=>"Foo", :foo_bar=>"Foo Bar", "foo_bar_baz"=>"Foo Bar Baz"}
182
+ end
183
+
184
+ it "should call label_proc for each value if label_proc is given" do
185
+ dsm = create(:label_proc=>lambda{|v| v.to_s.upcase})
186
+ dsm.labels([:foo]).should == {:foo=>"FOO"}
187
+ end
188
+ end
189
+ end
190
+
191
+ describe DimensionModel do
192
+ describe "index_list" do
193
+ it "should return a list of lists each with a segment_model index when given a nil prefix" do
194
+ dm = DimensionModel.new(:key=>:foo, :segment_models=>[Object.new, Object.new])
195
+ dm.index_list(nil).should == [[0],[1]]
196
+ end
197
+
198
+ it "should suffix each segment_model index to each prefix when given a non-nil prefix" do
199
+ dm = DimensionModel.new(:key=>:foo, :segment_models=>[Object.new, Object.new])
200
+ dm.index_list([[0],[1]]).should == [[0,0],[1,0],[0,1],[1,1]]
201
+ end
202
+ end
203
+ end
204
+
205
+ describe MeasureModel do
206
+ describe "select_string" do
207
+ it "should return an aliased definition if no region_segment_models modify the definition" do
208
+ mm = MeasureModel.new(:key=>:count, :definition=>"count(*)")
209
+
210
+ sm1 = Object.new
211
+ mock(sm1).do_modify(:count, "count(*)"){"count(*)"}
212
+ sm2 = Object.new
213
+ mock(sm2).do_modify(:count, "count(*)"){"count(*)"}
214
+
215
+ mm.select_string([sm1, sm2]).should == "count(*) as count"
216
+ end
217
+
218
+ it "should allow the region_segment_models to modify the definition" do
219
+ mm = MeasureModel.new(:key=>:count, :definition=>"count(*)")
220
+
221
+ sm1 = Object.new
222
+ mock(sm1).do_modify(:count, anything){|key,mdef| "#{mdef}/12"}
223
+ sm2 = Object.new
224
+ mock(sm2).do_modify(:count, anything){|key,mdef| mdef}
225
+
226
+ mm.select_string([sm1, sm2]).should == "count(*)/12 as count"
227
+ end
228
+ end
229
+
230
+ describe "do_cast" do
231
+ it "should return the value unchanged if no cast is specified" do
232
+ mm = MeasureModel.new(:key=>:count, :definition=>"count(*)")
233
+ mm.do_cast("100").should == "100"
234
+ mm.do_cast("100").class.should == "100".class
235
+ end
236
+
237
+ it "should cast the value according to the cast specified" do
238
+ mm = MeasureModel.new(:key=>:count, :definition=>"count(*)", :cast=>:int)
239
+ mm.do_cast("100").should == 100
240
+ mm.do_cast("100").class.should == 100.class
241
+ end
242
+ end
243
+ end
244
+
245
+ describe DatasetModel do
246
+ describe "region_segment_model_indexes" do
247
+
248
+ it "should produce the cross-producton of dimension segment indexes for one dimension" do
249
+ dim1 = DimensionModel.new(:key=>:foo, :segment_models=>[Object.new, Object.new])
250
+ mm1 = MeasureModel.new(:key=>:count, :definition=>"count(*)")
251
+
252
+ dm = DatasetModel.new(:source=>Object.new,
253
+ :dimension_models=>[dim1],
254
+ :measure_models=>[mm1])
255
+
256
+ dm.region_segment_model_indexes.to_set.should == [[0],[1]].to_set
257
+ end
258
+
259
+ it "should produce the cross-product of dimension segment indexes for two dimensions" do
260
+ dim1 = DimensionModel.new(:key=>:foo, :segment_models=>[Object.new, Object.new])
261
+ dim2 = DimensionModel.new(:key=>:foo, :segment_models=>[Object.new, Object.new, Object.new])
262
+ mm1 = MeasureModel.new(:key=>:count, :definition=>"count(*)")
263
+
264
+ dm = DatasetModel.new(:source=>Object.new,
265
+ :dimension_models=>[dim1, dim2],
266
+ :measure_models=>[mm1])
267
+
268
+ dm.region_segment_model_indexes.to_set.should == [[0,0],[0,1],[0,2],[1,0],[1,1],[1,2]].to_set
269
+ end
270
+
271
+ it "should produce the cross-product of dimension segment indexes for 3 dimensions" do
272
+ dim1 = DimensionModel.new(:key=>:foo, :segment_models=>[Object.new, Object.new])
273
+ dim2 = DimensionModel.new(:key=>:foo, :segment_models=>[Object.new, Object.new, Object.new])
274
+ dim3 = DimensionModel.new(:key=>:foo, :segment_models=>[Object.new, Object.new])
275
+ mm1 = MeasureModel.new(:key=>:count, :definition=>"count(*)")
276
+
277
+ dm = DatasetModel.new(:source=>Object.new,
278
+ :dimension_models=>[dim1, dim2, dim3],
279
+ :measure_models=>[mm1])
280
+
281
+ dm.region_segment_model_indexes.to_set.should == [[0,0,0],[0,0,1],[0,1,0],[0,1,1],[0,2,0],[0,2,1],[1,0,0],[1,0,1],[1,1,0],[1,1,1],[1,2,0],[1,2,1]].to_set
282
+ end
283
+ end
284
+
285
+ describe "all_dimension_segment_models" do
286
+ it "should return a list lists of dimension segments" do
287
+ dim1sm1 = Object.new
288
+ dim1sm2 = Object.new
289
+ dim1 = DimensionModel.new(:key=>:foo, :segment_models=>[dim1sm1, dim1sm2])
290
+ dim2sm1 = Object.new
291
+ dim2sm2 = Object.new
292
+ dim2sm3 = Object.new
293
+ dim2 = DimensionModel.new(:key=>:foo, :segment_models=>[dim2sm1, dim2sm2, dim2sm3])
294
+ mm1 = MeasureModel.new(:key=>:count, :definition=>"count(*)")
295
+
296
+ dm = DatasetModel.new(:source=>Object.new,
297
+ :dimension_models=>[dim1, dim2],
298
+ :measure_models=>[mm1])
299
+ dm.all_dimension_segment_models.should == [[dim1sm1, dim1sm2], [dim2sm1, dim2sm2, dim2sm3]]
300
+ end
301
+ end
302
+
303
+ describe "region_segment_models" do
304
+ it "should produce a list of dimension segment models corresponding to the supplied indexes" do
305
+ dim0sm0 = Object.new
306
+ dim0sm1 = Object.new
307
+ dim0 = DimensionModel.new(:key=>:foo, :segment_models=>[dim0sm0, dim0sm1])
308
+ dim1sm0 = Object.new
309
+ dim1sm1 = Object.new
310
+ dim1sm2 = Object.new
311
+ dim1 = DimensionModel.new(:key=>:foo, :segment_models=>[dim1sm0, dim1sm1, dim1sm2])
312
+ mm1 = MeasureModel.new(:key=>:count, :definition=>"count(*)")
313
+
314
+ dm = DatasetModel.new(:source=>Object.new,
315
+ :dimension_models=>[dim0, dim1],
316
+ :measure_models=>[mm1])
317
+ dm.region_segment_models([0,1]).should == [dim0sm0, dim1sm1]
318
+ dm.region_segment_models([1,2]).should == [dim0sm1, dim1sm2]
319
+ end
320
+ end
321
+
322
+ describe "with_regions" do
323
+ it "should iterate of all regions calling the proc with the region segment models" do
324
+ dim0sm0 = Object.new
325
+ dim0sm1 = Object.new
326
+ dim0 = DimensionModel.new(:key=>:foo, :segment_models=>[dim0sm0, dim0sm1])
327
+ dim1sm0 = Object.new
328
+ dim1sm1 = Object.new
329
+ dim1sm2 = Object.new
330
+ dim1 = DimensionModel.new(:key=>:foo, :segment_models=>[dim1sm0, dim1sm1, dim1sm2])
331
+ mm1 = MeasureModel.new(:key=>:count, :definition=>"count(*)")
332
+
333
+ dm = DatasetModel.new(:source=>Object.new,
334
+ :dimension_models=>[dim0, dim1],
335
+ :measure_models=>[mm1])
336
+
337
+ all_region_segment_models = [[dim0sm0,dim1sm0],[dim0sm0,dim1sm1],[dim0sm0,dim1sm2],[dim0sm1,dim1sm0],[dim0sm1,dim1sm1],[dim0sm1,dim1sm2]].to_set
338
+ dm.with_regions do |region_segment_models|
339
+ all_region_segment_models.delete(region_segment_models)
340
+ end
341
+ all_region_segment_models.empty?.should == true
342
+ end
343
+ end
344
+
345
+ describe "construct_query" do
346
+ it "should narrow the scope according to each segment in the region, select dimension and measure values, and group by dimensions" do
347
+ dim0sm0 = Object.new
348
+ dim0 = DimensionModel.new(:key=>:foo, :segment_models=>[dim0sm0])
349
+ dim1sm0 = Object.new
350
+ dim1 = DimensionModel.new(:key=>:bar, :segment_models=>[dim1sm0])
351
+ mm1 = MeasureModel.new(:key=>:count, :definition=>"count(*)")
352
+
353
+ dm = DatasetModel.new(:source=>Object.new,
354
+ :dimension_models=>[dim0, dim1],
355
+ :measure_models=>[mm1])
356
+
357
+ scope = Object.new
358
+ region_segment_models = [dim0sm0, dim1sm0]
359
+ measure_models = [mm1]
360
+
361
+ # the narrowing
362
+ scope_n1 = Object.new
363
+ mock(dim0sm0).do_narrow(scope){scope_n1}
364
+ scope_n2 = Object.new
365
+ mock(dim1sm0).do_narrow(scope_n1){scope_n2}
366
+
367
+ # dimension_select_strings
368
+ mock(dim0sm0).select_string{"foofoo as foo"}
369
+ mock(dim1sm0).select_string{"barbar as bar"}
370
+
371
+
372
+ # measure_select_strings
373
+ mock(mm1).select_string([dim0sm0, dim1sm0]){"count(*) as count"}
374
+
375
+ select_string = "foofoo as foo,barbar as bar,count(*) as count"
376
+
377
+ # group_string
378
+ mock(dim0sm0).group_by_column{"foo"}
379
+ mock(dim1sm0).group_by_column{"bar"}
380
+ group_string = "foo,bar"
381
+
382
+ scope_n3 = Object.new
383
+ query = Object.new
384
+ mock(scope_n2).select(select_string){scope_n3}.mock!.group(group_string){query}
385
+
386
+ dm.construct_query(scope, region_segment_models, measure_models).should == query
387
+ end
388
+ end
389
+
390
+ describe "extract" do
391
+ it "should extract row records into hashes keyed by dimension-values and measure-keys" do
392
+ dim0sm0 = Object.new
393
+ dim0 = DimensionModel.new(:key=>:foo, :segment_models=>[dim0sm0])
394
+ dim1sm0 = Object.new
395
+ dim1 = DimensionModel.new(:key=>:bar, :segment_models=>[dim1sm0])
396
+ mm1 = MeasureModel.new(:key=>:count, :definition=>"count(*)", :cast=>:int)
397
+
398
+ dm = DatasetModel.new(:source=>Object.new,
399
+ :dimension_models=>[dim0, dim1],
400
+ :measure_models=>[mm1])
401
+
402
+ stub(dim0sm0).dimension_model{dim0}
403
+ stub(dim0sm0).do_cast(anything){|value| value}
404
+ stub(dim1sm0).dimension_model{dim1}
405
+ stub(dim1sm0).do_cast(anything){|value| value}
406
+
407
+ # ActiveRecord methods are called on row instances
408
+ row1 = Object.new
409
+ mock(row1).foo{"foo1"}
410
+ mock(row1).bar{"bar1"}
411
+ mock(row1).count{"10"}
412
+ row2 = Object.new
413
+ mock(row2).foo{"foo2"}
414
+ mock(row2).bar{"bar2"}
415
+ mock(row2).count{"20"}
416
+
417
+ dm.extract([row1,row2], [dim0sm0, dim1sm0], [mm1]).should == [{:foo=>"foo1", :bar=>"bar1", :count=>10},
418
+ {:foo=>"foo2", :bar=>"bar2", :count=>20}]
419
+ end
420
+ end
421
+
422
+
423
+ describe "do_queries" do
424
+ it "should do a query for each region and put the results in a dataset" do
425
+ dim0sm0 = Object.new
426
+ dim0 = DimensionModel.new(:key=>:foo, :segment_models=>[dim0sm0])
427
+ dim1sm0 = Object.new
428
+ dim1 = DimensionModel.new(:key=>:bar, :segment_models=>[dim1sm0])
429
+ mm1 = MeasureModel.new(:key=>:count, :definition=>"count(*)", :cast=>:int)
430
+
431
+ dm = DatasetModel.new(:source=>Object.new,
432
+ :dimension_models=>[dim0, dim1],
433
+ :measure_models=>[mm1])
434
+
435
+ mock(dm).with_regions do |proc|
436
+ proc.call([dim0sm0,dim1sm0])
437
+ end
438
+
439
+ query = Object.new
440
+ mock(dm).construct_query(dm.source, [dim0sm0, dim1sm0], [mm1]){query}
441
+ records = Object.new
442
+ mock(query).all{records}
443
+ mock(dm).extract(records, [dim0sm0, dim1sm0], [mm1]){[{:foo=>"foo1", :bar=>"bar1", :count=>10},
444
+ {:foo=>"foo2", :bar=>"bar2", :count=>20}]}
445
+
446
+ data = dm.do_queries
447
+ data.should == [{:foo=>"foo1", :bar=>"bar1", :count=>10},
448
+ {:foo=>"foo2", :bar=>"bar2", :count=>20}]
449
+ end
450
+ end
451
+
452
+ describe "collect" do
453
+ it "should do_queries and use the result to constract a Dataset" do
454
+ dim0sm0 = Object.new
455
+ dim0 = DimensionModel.new(:key=>:foo, :segment_models=>[dim0sm0])
456
+ mm1 = MeasureModel.new(:key=>:count, :definition=>"count(*)", :cast=>:int)
457
+
458
+ dm = DatasetModel.new(:source=>Object.new,
459
+ :dimension_models=>[dim0],
460
+ :measure_models => [mm1])
461
+
462
+ dataset = Object.new
463
+ mock(dm).do_queries{dataset}
464
+
465
+ mock(MDQuery::Dataset::Dataset).new(dm, dataset)
466
+
467
+ dm.collect
468
+
469
+ end
470
+ end
471
+ end
472
+ end
473
+ end