mdquery 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +53 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +78 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/lib/mdquery.rb +13 -0
- data/lib/mdquery/dataset.rb +283 -0
- data/lib/mdquery/dsl.rb +176 -0
- data/lib/mdquery/model.rb +270 -0
- data/lib/mdquery/util.rb +21 -0
- data/mdquery.gemspec +78 -0
- data/spec/mdquery/dataset_spec.rb +232 -0
- data/spec/mdquery/dsl_spec.rb +144 -0
- data/spec/mdquery/model_spec.rb +473 -0
- data/spec/mdquery/util_spec.rb +25 -0
- data/spec/mdquery_spec.rb +35 -0
- data/spec/spec_helper.rb +11 -0
- metadata +149 -0
data/mdquery.gemspec
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "mdquery"
|
8
|
+
s.version = "0.3.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["mccraigmccraig"]
|
12
|
+
s.date = "2012-03-27"
|
13
|
+
s.description = "provides a DSL for simply specifying and executing segmented multi-dimensional queries on your active-record-3 models"
|
14
|
+
s.email = "mccraigmccraig@gmail.com"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".rspec",
|
22
|
+
".travis.yml",
|
23
|
+
"Gemfile",
|
24
|
+
"Gemfile.lock",
|
25
|
+
"LICENSE.txt",
|
26
|
+
"README.rdoc",
|
27
|
+
"Rakefile",
|
28
|
+
"VERSION",
|
29
|
+
"lib/mdquery.rb",
|
30
|
+
"lib/mdquery/dataset.rb",
|
31
|
+
"lib/mdquery/dsl.rb",
|
32
|
+
"lib/mdquery/model.rb",
|
33
|
+
"lib/mdquery/util.rb",
|
34
|
+
"mdquery.gemspec",
|
35
|
+
"spec/mdquery/dataset_spec.rb",
|
36
|
+
"spec/mdquery/dsl_spec.rb",
|
37
|
+
"spec/mdquery/model_spec.rb",
|
38
|
+
"spec/mdquery/util_spec.rb",
|
39
|
+
"spec/mdquery_spec.rb",
|
40
|
+
"spec/spec_helper.rb"
|
41
|
+
]
|
42
|
+
s.homepage = "http://github.com/mccraigmccraig/mdquery"
|
43
|
+
s.licenses = ["MIT"]
|
44
|
+
s.require_paths = ["lib"]
|
45
|
+
s.rubygems_version = "1.8.10"
|
46
|
+
s.summary = "simple multi-dimensional queries on top of active-record-3"
|
47
|
+
|
48
|
+
if s.respond_to? :specification_version then
|
49
|
+
s.specification_version = 3
|
50
|
+
|
51
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
52
|
+
s.add_runtime_dependency(%q<activerecord>, [">= 3.1.0"])
|
53
|
+
s.add_development_dependency(%q<rake>, ["~> 0.9.2"])
|
54
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.8.0"])
|
55
|
+
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
56
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.1.0"])
|
57
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
|
58
|
+
s.add_development_dependency(%q<rr>, [">= 1.0.4"])
|
59
|
+
else
|
60
|
+
s.add_dependency(%q<activerecord>, [">= 3.1.0"])
|
61
|
+
s.add_dependency(%q<rake>, ["~> 0.9.2"])
|
62
|
+
s.add_dependency(%q<rspec>, ["~> 2.8.0"])
|
63
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
64
|
+
s.add_dependency(%q<bundler>, ["~> 1.1.0"])
|
65
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
66
|
+
s.add_dependency(%q<rr>, [">= 1.0.4"])
|
67
|
+
end
|
68
|
+
else
|
69
|
+
s.add_dependency(%q<activerecord>, [">= 3.1.0"])
|
70
|
+
s.add_dependency(%q<rake>, ["~> 0.9.2"])
|
71
|
+
s.add_dependency(%q<rspec>, ["~> 2.8.0"])
|
72
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
73
|
+
s.add_dependency(%q<bundler>, ["~> 1.1.0"])
|
74
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
75
|
+
s.add_dependency(%q<rr>, [">= 1.0.4"])
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
@@ -0,0 +1,232 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
2
|
+
require 'mdquery/dataset'
|
3
|
+
|
4
|
+
module MDQuery::Dataset
|
5
|
+
|
6
|
+
describe DimensionSegment do
|
7
|
+
|
8
|
+
def build
|
9
|
+
@model = Object.new
|
10
|
+
mock(@model).key{:foo_segment}
|
11
|
+
|
12
|
+
@dimension = Object.new
|
13
|
+
@source = Object.new
|
14
|
+
mock(@dimension).dataset.mock!.model.mock!.source{@source}
|
15
|
+
|
16
|
+
@values = [:foo, :bar, :baz]
|
17
|
+
mock(@model).get_values(@source){@values}
|
18
|
+
@labels = {:foo=>"foo", :bar=>"BAR", :baz=>"blah"}
|
19
|
+
mock(@model).labels(@values){@labels}
|
20
|
+
|
21
|
+
DimensionSegment.new(@model, @dimension)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should constract a DimensionSegment from a DimensionSegmentModel" do
|
25
|
+
ds = build
|
26
|
+
ds.dimension.should == @dimension
|
27
|
+
ds.key.should == :foo_segment
|
28
|
+
ds.dimension_values.map(&:dimension_segment).should == [ds, ds, ds]
|
29
|
+
ds.dimension_values.map(&:value).should == [:foo, :bar, :baz]
|
30
|
+
ds.dimension_values.map(&:label).should == ["foo", "BAR", "blah"]
|
31
|
+
ds.values.should == [:foo, :bar, :baz]
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "dimension_value_for" do
|
35
|
+
it "should retrieve a DimensionValue from the segment by value" do
|
36
|
+
ds = build
|
37
|
+
dv0 = ds.dimension_value_for(:foo)
|
38
|
+
dv0.dimension_segment.should == ds
|
39
|
+
dv0.value.should == :foo
|
40
|
+
dv0.label.should == "foo"
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should retrieve a DimensionValue from the segment with the deref operator" do
|
44
|
+
ds = build
|
45
|
+
dv0 = ds[:foo]
|
46
|
+
dv0.dimension_segment.should == ds
|
47
|
+
dv0.value.should == :foo
|
48
|
+
dv0.label.should == "foo"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "label_for" do
|
53
|
+
it "should get a label for a value from the segment" do
|
54
|
+
ds = build
|
55
|
+
ds.label_for(:foo).should == "foo"
|
56
|
+
ds.label_for(:bar).should == "BAR"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe Dimension do
|
62
|
+
|
63
|
+
def build
|
64
|
+
@model = Object.new
|
65
|
+
mock(@model).key{:foodim}
|
66
|
+
mock(@model).label{"Dimension Foo"}
|
67
|
+
|
68
|
+
@sm0 = Object.new
|
69
|
+
@segment0 = Object.new
|
70
|
+
stub(@segment0).key{:segment0_key}
|
71
|
+
stub(@segment0).values{[:foo, :bar]}
|
72
|
+
@s0v0 = Object.new
|
73
|
+
stub(@s0v0).label{"Foo"}
|
74
|
+
stub(@s0v0).value{:foo}
|
75
|
+
@s0v1 = Object.new
|
76
|
+
mock(@s0v1).value{:bar}
|
77
|
+
stub(@segment0).dimension_values{[@s0v0, @s0v1]}
|
78
|
+
|
79
|
+
mock(DimensionSegment).new(@sm0, anything){@segment0}
|
80
|
+
|
81
|
+
@sm1 = Object.new
|
82
|
+
@segment1 = Object.new
|
83
|
+
stub(@segment1).key{:segment1_key}
|
84
|
+
stub(@segment1).values{[:baz, :waz]}
|
85
|
+
@s1v0 = Object.new
|
86
|
+
stub(@s1v0).value{:baz}
|
87
|
+
@s1v1 = Object.new
|
88
|
+
stub(@s1v1).value{:waz}
|
89
|
+
stub(@s1v1).label{"WAZ"}
|
90
|
+
stub(@segment1).dimension_values{[@s1v0, @s1v1]}
|
91
|
+
|
92
|
+
mock(DimensionSegment).new(@sm1, anything){@segment1}
|
93
|
+
|
94
|
+
mock(@model).segment_models{[@sm0,@sm1]}
|
95
|
+
|
96
|
+
@dataset = Object.new
|
97
|
+
|
98
|
+
Dimension.new(@model, @dataset)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should construct a Dimension from a DimensionModel" do
|
102
|
+
d = build
|
103
|
+
d.dataset.should == @dataset
|
104
|
+
d.key.should == :foodim
|
105
|
+
d.label.should == "Dimension Foo"
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "segment" do
|
109
|
+
it "should retrieve a segment by key" do
|
110
|
+
d = build
|
111
|
+
d.segment(:segment0_key).should == @segment0
|
112
|
+
d.segment(:segment1_key).should == @segment1
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should retrieve a segment by key with deref operator" do
|
116
|
+
d = build
|
117
|
+
d[:segment0_key].should == @segment0
|
118
|
+
d[:segment1_key].should == @segment1
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe "values_for_segments" do
|
123
|
+
it "values_for_segment should extract values belonging to a segment" do
|
124
|
+
d = build
|
125
|
+
d.values_for_segments([:segment1_key, :segment0_key]).should == [:baz, :waz, :foo, :bar]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe "dimension_value_for" do
|
130
|
+
it "should retrieve the DimensionValue for a segment value" do
|
131
|
+
d = build
|
132
|
+
d.dimension_value_for(:foo).should == @s0v0
|
133
|
+
d.dimension_value_for(:waz).should == @s1v1
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
describe "dimension_values_for_segments" do
|
138
|
+
it "should extract DimensionValues belonging to a list of segments" do
|
139
|
+
d = build
|
140
|
+
d.dimension_values_for_segments(nil).should == [@s0v0, @s0v1, @s1v0, @s1v1]
|
141
|
+
d.dimension_values_for_segments([:segment1_key]).should == [@s1v0, @s1v1]
|
142
|
+
d.dimension_values_for_segments([:segment1_key, :segment0_key]).should == [@s1v0, @s1v1, @s0v0, @s0v1]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
describe "dimension_values" do
|
147
|
+
it "should return a list of DimensionValues from all segments" do
|
148
|
+
d = build
|
149
|
+
d.dimension_values.should == [@s0v0, @s0v1, @s1v0, @s1v1]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe "label_for" do
|
154
|
+
it "should retrieve the label for a segment value" do
|
155
|
+
d = build
|
156
|
+
d.label_for(:foo).should == "Foo"
|
157
|
+
d.label_for(:waz).should == "WAZ"
|
158
|
+
d.label_for(:blah).should == nil
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
describe Measure do
|
164
|
+
def build
|
165
|
+
@dataset = Object.new
|
166
|
+
@model = Object.new
|
167
|
+
mock(@model).key{:count}
|
168
|
+
mock(@model).definition{"count(*)"}
|
169
|
+
|
170
|
+
Measure.new(@model, @dataset)
|
171
|
+
end
|
172
|
+
|
173
|
+
it "should construct a Measure from a MeasureModel" do
|
174
|
+
m = build
|
175
|
+
m.dataset.should == @dataset
|
176
|
+
m.key.should == :count
|
177
|
+
m.definition.should == "count(*)"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
describe Dataset do
|
182
|
+
def build
|
183
|
+
@data = [{:foo=>10, :bar=>10, :count=>100, :sum=>200}, {:foo=>5, :bar=>4, :count=>10, :sum=>20}]
|
184
|
+
|
185
|
+
@model = Object.new
|
186
|
+
|
187
|
+
@mm0 = Object.new
|
188
|
+
@mm1 = Object.new
|
189
|
+
stub(@model).measure_models{[@mm0, @mm1]}
|
190
|
+
|
191
|
+
@m0 = Object.new
|
192
|
+
stub(@m0).key{:count}
|
193
|
+
@m1 = Object.new
|
194
|
+
stub(@m1).key{:sum}
|
195
|
+
mock(Measure).new(@mm0, anything){@m0}
|
196
|
+
mock(Measure).new(@mm1, anything){@m1}
|
197
|
+
|
198
|
+
@dm0 = Object.new
|
199
|
+
@dm1 = Object.new
|
200
|
+
stub(@model).dimension_models{[@dm0, @dm1]}
|
201
|
+
|
202
|
+
@d0 = Object.new
|
203
|
+
stub(@d0).key{:foo}
|
204
|
+
@d1 = Object.new
|
205
|
+
stub(@d1).key{:bar}
|
206
|
+
|
207
|
+
mock(Dimension).new(@dm0, anything){@d0}
|
208
|
+
mock(Dimension).new(@dm1, anything){@d1}
|
209
|
+
|
210
|
+
Dataset.new(@model, @data)
|
211
|
+
end
|
212
|
+
|
213
|
+
it "should construct a Dataset from a DatasetModel" do
|
214
|
+
ds = build
|
215
|
+
ds.data.should == @data
|
216
|
+
ds.dimensions.should == {:foo=>@d0, :bar=>@d1}
|
217
|
+
ds.measures.should == {:count=>@m0, :sum=>@m1}
|
218
|
+
ds.indexed_data.should == {{:foo=>10, :bar=>10}=>{:count=>100, :sum=>200}, {:foo=>5, :bar=>4}=>{:count=>10, :sum=>20}}
|
219
|
+
end
|
220
|
+
|
221
|
+
it "should index the dataset" do
|
222
|
+
ds = build
|
223
|
+
ds.indexed_data.should == {{:foo=>10, :bar=>10}=>{:count=>100, :sum=>200}, {:foo=>5, :bar=>4}=>{:count=>10, :sum=>20}}
|
224
|
+
end
|
225
|
+
|
226
|
+
it "should retrieve datapoints from the index" do
|
227
|
+
ds = build
|
228
|
+
ds.datapoint({:foo=>10, :bar=>10}, :count).should == 100
|
229
|
+
ds.datapoint({:foo=>5, :bar=>4}, :sum).should == 20
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
2
|
+
require 'mdquery/dsl'
|
3
|
+
|
4
|
+
module MDQuery::DSL
|
5
|
+
describe MDQuery::DSL do
|
6
|
+
describe DimensionSegmentDSL do
|
7
|
+
|
8
|
+
it "should build a DimensionSegmentModel for a fixed dimension-value" do
|
9
|
+
dimension_model = Object.new
|
10
|
+
narrow_proc = lambda{}
|
11
|
+
values_proc = lambda{}
|
12
|
+
label_proc = lambda{}
|
13
|
+
modify_count_proc = lambda{}
|
14
|
+
|
15
|
+
dsl = DimensionSegmentDSL.new(:foo) do
|
16
|
+
fix_dimension :blah
|
17
|
+
narrow(&narrow_proc)
|
18
|
+
values(&values_proc)
|
19
|
+
label(&label_proc)
|
20
|
+
cast :sym
|
21
|
+
modify :count, &modify_count_proc
|
22
|
+
end
|
23
|
+
|
24
|
+
dsm = dsl.send(:build, dimension_model)
|
25
|
+
|
26
|
+
dsm.dimension_model.should == dimension_model
|
27
|
+
dsm.key.should == :foo
|
28
|
+
dsm.fixed_dimension_value.should == :blah
|
29
|
+
dsm.narrow_proc.should == narrow_proc
|
30
|
+
dsm.values_proc.should == values_proc
|
31
|
+
dsm.label_proc.should == label_proc
|
32
|
+
dsm.measure_modifiers.should == {:count=>modify_count_proc}
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should build a DimensionSegmentModel for an extracted dimension-value" do
|
36
|
+
dimension_model = Object.new
|
37
|
+
|
38
|
+
dsl = DimensionSegmentDSL.new(:foo) do
|
39
|
+
extract_dimension "bar"
|
40
|
+
end
|
41
|
+
|
42
|
+
dsm = dsl.send(:build, dimension_model)
|
43
|
+
dsm.dimension_model.should == dimension_model
|
44
|
+
dsm.key.should == :foo
|
45
|
+
dsm.extract_dimension_query.should == "bar"
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
describe DimensionDSL do
|
52
|
+
|
53
|
+
it "should build a DimensionModel with a single segment" do
|
54
|
+
dsl = DimensionDSL.new(:foo) do
|
55
|
+
label :blah
|
56
|
+
|
57
|
+
segment(:bar) do
|
58
|
+
fix_dimension :woot
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
dm = dsl.send(:build)
|
63
|
+
dm.key.should == :foo
|
64
|
+
dm.label.should == :blah
|
65
|
+
dm.segment_models.count.should == 1
|
66
|
+
dm.segment_models.first.key.should == :bar
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should build a DimensionModel with a list of segments" do
|
70
|
+
dsl = DimensionDSL.new(:foo) do
|
71
|
+
|
72
|
+
segment(:bar) do
|
73
|
+
fix_dimension :woot
|
74
|
+
end
|
75
|
+
|
76
|
+
segment(:baz) do
|
77
|
+
fix_dimension :bloop
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
dm = dsl.send(:build)
|
82
|
+
dm.key.should == :foo
|
83
|
+
dm.segment_models.count.should == 2
|
84
|
+
dm.segment_models[0].key.should == :bar
|
85
|
+
dm.segment_models[1].key.should == :baz
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe MeasureDSL do
|
90
|
+
|
91
|
+
it "should build a MeasureModel without cast" do
|
92
|
+
dsl = MeasureDSL.new(:foo, "count(*)")
|
93
|
+
mm = dsl.send(:build)
|
94
|
+
mm.key.should == :foo
|
95
|
+
mm.definition.should == "count(*)"
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should build a MeasureModel with cast" do
|
99
|
+
dsl = MeasureDSL.new(:foo, "count(*)", :sym)
|
100
|
+
mm = dsl.send(:build)
|
101
|
+
mm.key.should == :foo
|
102
|
+
mm.definition.should == "count(*)"
|
103
|
+
mm.cast.should == :sym
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
describe DatasetDSL do
|
109
|
+
|
110
|
+
it "should build a DatasetModel with multiple dimensions and measures" do
|
111
|
+
source = Object.new
|
112
|
+
|
113
|
+
dsl = DatasetDSL.new do
|
114
|
+
source source
|
115
|
+
|
116
|
+
dimension :foo do
|
117
|
+
segment :foo_a do
|
118
|
+
fix_dimension :foo_a_value
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
dimension :bar do
|
123
|
+
segment :bar_a do
|
124
|
+
fix_dimension :bar_a_value
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
measure :count, "count(*)"
|
129
|
+
measure :avg, "avg(foo)"
|
130
|
+
end
|
131
|
+
|
132
|
+
ds = dsl.send(:build)
|
133
|
+
ds.source.should == source
|
134
|
+
ds.dimension_models.count.should == 2
|
135
|
+
ds.dimension_models[0].key.should == :foo
|
136
|
+
ds.dimension_models[1].key.should == :bar
|
137
|
+
ds.measure_models.count.should == 2
|
138
|
+
ds.measure_models[0].key.should == :count
|
139
|
+
ds.measure_models[1].key.should == :avg
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|