mochigome 0.0.9 → 0.0.10
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/mochigome_ver.rb +1 -1
- data/lib/model_extensions.rb +4 -4
- data/lib/query.rb +28 -11
- data/lib/subgroup_model.rb +1 -1
- data/test/app_root/app/models/sale.rb +5 -1
- data/test/unit/query_test.rb +17 -2
- metadata +4 -4
data/lib/mochigome_ver.rb
CHANGED
data/lib/model_extensions.rb
CHANGED
@@ -305,13 +305,13 @@ module Mochigome
|
|
305
305
|
|
306
306
|
if vals.size == 1
|
307
307
|
vals << :id # TODO : Use real primary key, only do this for appropriate agg funcs
|
308
|
-
elsif vals.size
|
308
|
+
elsif vals.empty? || vals.size > 3
|
309
309
|
raise ModelSetupError.new "Wrong # of components for agg: #{obj.inspect}"
|
310
310
|
end
|
311
311
|
|
312
312
|
{
|
313
|
-
:agg_proc => aggregation_proc(vals
|
314
|
-
:value_proc => value_proc(vals
|
315
|
-
}
|
313
|
+
:agg_proc => aggregation_proc(vals[0]),
|
314
|
+
:value_proc => value_proc(vals[1])
|
315
|
+
}.merge(vals[2] || {})
|
316
316
|
end
|
317
317
|
end
|
data/lib/query.rb
CHANGED
@@ -76,23 +76,41 @@ module Mochigome
|
|
76
76
|
|
77
77
|
agg_data_rel = self.class.relation_over_path(agg_path, focus_rel.dup)
|
78
78
|
agg_data_rel = access_filtered_relation(agg_data_rel, @layers_path + agg_path)
|
79
|
-
data_tbl = Arel::Table.new(data_model.table_name)
|
80
79
|
agg_fields = data_model.mochigome_aggregation_settings.options[:fields].reject{|a| a[:in_ruby]}
|
80
|
+
agg_joined_models = @layers_path + agg_path
|
81
81
|
agg_fields.each_with_index do |a, i|
|
82
|
-
|
82
|
+
(a[:joins] || []).each do |m|
|
83
|
+
unless agg_joined_models.include?(m)
|
84
|
+
cand = nil
|
85
|
+
agg_joined_models.each do |agg_join_src_m|
|
86
|
+
p = self.class.path_thru([agg_join_src_m, m])
|
87
|
+
if p && (cand.nil? || p.size < cand.size)
|
88
|
+
cand = p
|
89
|
+
end
|
90
|
+
end
|
91
|
+
if cand
|
92
|
+
agg_data_rel = self.class.relation_over_path(cand, agg_data_rel)
|
93
|
+
agg_joined_models += cand
|
94
|
+
else
|
95
|
+
raise QueryError.new("Can't join from query to agg join model #{m.name}") # TODO: Test this
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
d_expr = a[:value_proc].call(data_model.arel_table)
|
100
|
+
agg_data_rel.project(d_expr.as("d#{i}"))
|
83
101
|
end
|
84
102
|
|
85
103
|
@aggregate_rels[focus_model][data_model] = (0..key_cols.length).map{|n|
|
86
104
|
lambda {|cond|
|
87
105
|
d_rel = agg_data_rel.dup
|
88
|
-
d_cols = key_cols.take(n) + [
|
106
|
+
d_cols = key_cols.take(n) + [data_model.arel_primary_key]
|
89
107
|
d_cols.each_with_index do |col, i|
|
90
108
|
d_rel.project(col.as("g#{i}")).group(col)
|
91
109
|
end
|
92
110
|
d_rel.where(cond) if cond
|
93
111
|
|
94
|
-
# FIXME: This subtable won't be necessary for all
|
95
|
-
# When we can avoid it, we should,
|
112
|
+
# FIXME: This subtable won't be necessary for all aggregation funcs.
|
113
|
+
# When we can avoid it, we should, for performance.
|
96
114
|
a_rel = Arel::SelectManager.new(
|
97
115
|
Arel::Table.engine,
|
98
116
|
Arel.sql("(#{d_rel.to_sql}) as mochigome_data")
|
@@ -122,16 +140,14 @@ module Mochigome
|
|
122
140
|
if cond.is_a?(Array)
|
123
141
|
return root if cond.empty?
|
124
142
|
cond = cond.inject(nil) do |expr, obj|
|
125
|
-
|
126
|
-
tbl = Arel::Table.new(cls.table_name)
|
127
|
-
subexpr = tbl[cls.primary_key].eq(obj.id)
|
143
|
+
subexpr = obj.class.arel_primary_key.eq(obj.id)
|
128
144
|
expr ? expr.or(subexpr) : subexpr
|
129
145
|
end
|
130
146
|
end
|
131
147
|
if cond
|
132
148
|
self.class.expr_tables(cond).each do |t|
|
133
149
|
raise QueryError.new("Condition table #{t} not in layer list") unless
|
134
|
-
@layers_path.any?{|m| m.table_name == t}
|
150
|
+
@layers_path.any?{|m| m.real_model? && m.table_name == t}
|
135
151
|
end
|
136
152
|
end
|
137
153
|
|
@@ -270,6 +286,7 @@ module Mochigome
|
|
270
286
|
|
271
287
|
# TODO: Move the stuff below into its own module
|
272
288
|
|
289
|
+
# Take an expression and return a Set of all tables it references
|
273
290
|
def self.expr_tables(e)
|
274
291
|
# TODO: This is kind of hacky, Arel probably has a better way
|
275
292
|
# to do this with its API.
|
@@ -287,9 +304,9 @@ module Mochigome
|
|
287
304
|
@@shortest_paths = {}
|
288
305
|
|
289
306
|
def self.relation_over_path(path, rel = nil)
|
290
|
-
# Project ensures that we don't return a Table even if path is empty
|
291
307
|
real_path = path.map{|e| (e.real_model? ? e : e.model)}.uniq
|
292
|
-
|
308
|
+
# Project ensures that we return a Rel, not a Table, even if path is empty
|
309
|
+
rel ||= real_path.first.arel_table.project
|
293
310
|
(0..(real_path.size-2)).each do |i|
|
294
311
|
rel = relation_func(real_path[i], real_path[i+1]).call(rel)
|
295
312
|
end
|
data/lib/subgroup_model.rb
CHANGED
@@ -54,7 +54,7 @@ module Mochigome
|
|
54
54
|
unless c.is_a?(Hash) && c.size == 1 && c[@attr].is_a?(Array)
|
55
55
|
raise QueryError.new("Invalid conditions given to SubgroupModel#all")
|
56
56
|
end
|
57
|
-
recs = c[@attr].map do |val|
|
57
|
+
recs = c[@attr].compact.map do |val|
|
58
58
|
SubgroupPseudoRecord.new(self, val)
|
59
59
|
end
|
60
60
|
# TODO: Support some kind of custom ordering
|
data/test/unit/query_test.rb
CHANGED
@@ -200,6 +200,7 @@ describe Mochigome::Query do
|
|
200
200
|
# Store X, Product C
|
201
201
|
assert_equal "Product C", (data_node/0/0/1).name
|
202
202
|
assert_equal 3, (data_node/0/0/1)['Sales count']
|
203
|
+
assert_equal (3*@product_c.price).to_s, (data_node/0/0/1)['Gross'].to_s
|
203
204
|
# Store Z, Product C
|
204
205
|
assert_equal "Product C", (data_node/1/1/0).name
|
205
206
|
assert_equal 2, (data_node/1/1/0)['Sales count']
|
@@ -211,6 +212,7 @@ describe Mochigome::Query do
|
|
211
212
|
# Store Z, Product C
|
212
213
|
assert_equal "Product C", (data_node/1/0/0).name
|
213
214
|
assert_equal 2, (data_node/1/0/0)['Sales count']
|
215
|
+
assert_equal (2*@product_c.price).to_s, (data_node/1/0/0)['Gross'].to_s
|
214
216
|
end
|
215
217
|
|
216
218
|
it "collects aggregate data in subgroups" do
|
@@ -325,6 +327,19 @@ describe Mochigome::Query do
|
|
325
327
|
assert_nil (data_node/0/0/0/0)['Sales count']
|
326
328
|
end
|
327
329
|
|
330
|
+
it "can collect aggregate data involving joins to tables not on path" do
|
331
|
+
q = Mochigome::Query.new(
|
332
|
+
[Owner, Store],
|
333
|
+
:aggregate_sources => [Sale]
|
334
|
+
)
|
335
|
+
|
336
|
+
data_node = q.run()
|
337
|
+
assert_equal "John's Store", (data_node/0/0).name
|
338
|
+
assert_equal 8, (data_node/0/0)['Sales count']
|
339
|
+
assert_equal (5*@product_a.price + 3*@product_c.price).to_s,
|
340
|
+
(data_node/0/0)['Gross'].to_s
|
341
|
+
end
|
342
|
+
|
328
343
|
it "can do conditional counts" do
|
329
344
|
q = Mochigome::Query.new(
|
330
345
|
[Category],
|
@@ -447,7 +462,7 @@ describe Mochigome::Query do
|
|
447
462
|
}
|
448
463
|
end
|
449
464
|
q = Mochigome::Query.new([Product], :access_filter => af)
|
450
|
-
dn = q.run
|
465
|
+
dn = q.run
|
451
466
|
assert_equal 4, dn.children.size
|
452
467
|
refute dn.children.any?{|c| c.name == "Product E"}
|
453
468
|
end
|
@@ -461,7 +476,7 @@ describe Mochigome::Query do
|
|
461
476
|
}
|
462
477
|
end
|
463
478
|
q = Mochigome::Query.new([Product], :access_filter => af)
|
464
|
-
dn = q.run
|
479
|
+
dn = q.run
|
465
480
|
assert_equal 2, dn.children.size
|
466
481
|
refute dn.children.any?{|c| c.name == "Product E"}
|
467
482
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mochigome
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 11
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 10
|
10
|
+
version: 0.0.10
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- David Mike Simon
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2012-
|
18
|
+
date: 2012-04-01 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
version_requirements: &id001 !ruby/object:Gem::Requirement
|