mdquery 0.3.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,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