mochigome 0.0.9 → 0.0.10

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