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 CHANGED
@@ -1,3 +1,3 @@
1
1
  module Mochigome
2
- VERSION = "0.0.9"
2
+ VERSION = "0.0.10"
3
3
  end
@@ -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 != 2
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.first),
314
- :value_proc => value_proc(vals.last)
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
- agg_data_rel.project(a[:value_proc].call(data_tbl).as("d#{i}"))
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) + [Arel::Table.new(data_model.table_name)[data_model.primary_key]]
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 forms of aggregation.
95
- # When we can avoid it, we should, because query performance is greatly increased.
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
- cls = obj.class
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
- rel ||= Arel::Table.new(real_path.first.table_name).project
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
@@ -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
@@ -1,6 +1,10 @@
1
1
  class Sale < ActiveRecord::Base
2
2
  has_mochigome_aggregations do |a|
3
- a.fields [:count]
3
+ a.fields [:count, {
4
+ "Gross" => [:sum, lambda {|t|
5
+ Product.arel_table[:price]
6
+ }, {:joins => [Product]}]
7
+ }]
4
8
  end
5
9
 
6
10
  belongs_to :store_product
@@ -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(Product.all) # FIXME: Need a better way of doing "all objs" queries
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(Product.all) # FIXME: Need a better way of doing "all objs" queries
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: 13
4
+ hash: 11
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 9
10
- version: 0.0.9
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-03-16 00:00:00 Z
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