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