cubicle 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/CHANGELOG.rdoc +14 -0
  2. data/README.rdoc +188 -174
  3. data/cubicle.gemspec +26 -10
  4. data/lib/cubicle.rb +47 -422
  5. data/lib/cubicle/aggregation.rb +58 -7
  6. data/lib/cubicle/aggregation/ad_hoc.rb +12 -0
  7. data/lib/cubicle/aggregation/aggregation_manager.rb +212 -0
  8. data/lib/cubicle/aggregation/dsl.rb +108 -0
  9. data/lib/cubicle/aggregation/map_reduce_helper.rb +55 -0
  10. data/lib/cubicle/data.rb +29 -84
  11. data/lib/cubicle/data/hierarchy.rb +55 -0
  12. data/lib/cubicle/data/level.rb +62 -0
  13. data/lib/cubicle/data/member.rb +28 -0
  14. data/lib/cubicle/data/table.rb +56 -0
  15. data/lib/cubicle/measure.rb +30 -20
  16. data/lib/cubicle/mongo_mapper/aggregate_plugin.rb +1 -1
  17. data/lib/cubicle/ordered_hash_with_indifferent_access.rb +27 -0
  18. data/lib/cubicle/query.rb +21 -194
  19. data/lib/cubicle/query/dsl.rb +118 -0
  20. data/lib/cubicle/query/dsl/time_intelligence.rb +89 -0
  21. data/lib/cubicle/ratio.rb +28 -12
  22. data/lib/cubicle/version.rb +2 -2
  23. data/test/cubicle/aggregation/ad_hoc_test.rb +21 -0
  24. data/test/cubicle/cubicle_aggregation_test.rb +84 -20
  25. data/test/cubicle/cubicle_query_test.rb +36 -0
  26. data/test/cubicle/data/data_test.rb +30 -0
  27. data/test/cubicle/data/level_test.rb +42 -0
  28. data/test/cubicle/data/member_test.rb +40 -0
  29. data/test/cubicle/{cubicle_data_test.rb → data/table_test.rb} +50 -50
  30. data/test/cubicle/duration_test.rb +46 -48
  31. data/test/cubicle/ordered_hash_with_indifferent_access_test.rb +19 -0
  32. data/test/cubicles/defect_cubicle.rb +31 -31
  33. data/test/log/test.log +102066 -0
  34. metadata +26 -10
  35. data/lib/cubicle/data_level.rb +0 -60
  36. data/test/cubicle/cubicle_data_level_test.rb +0 -58
  37. data/test/cubicle/cubicle_test.rb +0 -85
data/lib/cubicle/data.rb CHANGED
@@ -1,84 +1,29 @@
1
- module Cubicle
2
- class Data < Array
3
-
4
- attr_reader :dimension_names, :measure_names, :total_count
5
-
6
- def initialize(query,query_results,total_count = nil)
7
- @dimension_names = query.dimensions.map{|d|d.name}
8
- @measure_names = query.measures.map{|m|m.name}
9
- @time_dimension_name = query.time_dimension.name if query.respond_to?(:time_dimension) && query.time_dimension
10
- @time_period = query.time_period if query.respond_to?(:time_period)
11
- @time_range = query.time_range if query.respond_to?(:time_range)
12
- extract_data(query_results)
13
- @total_count = total_count if total_count
14
- end
15
-
16
- def hierarchize(*args)
17
- args = [@time_dimension_name || @dimension_names].flatten if args.blank?
18
- extract_dimensions args, self
19
- end
20
- alias hierarchize_by hierarchize
21
- alias by hierarchize
22
-
23
- def records_per_page=(records_per_page)
24
- @records_per_page=records_per_page
25
- end
26
-
27
- def total_pages
28
- if (!defined?(@total_count))
29
- raise "Cannot find the total number of pages without setting the total count"
30
- end
31
-
32
- if (!defined?(@records_per_page))
33
- raise "Cannot find the total number of pages without setting the number of records per page"
34
- end
35
-
36
- (@total_count.to_f / @records_per_page.to_f).ceil
37
- end
38
-
39
- private
40
-
41
- def extract_dimensions(dimension_names, data)
42
- data, dimension_names = data.dup, dimension_names.dup
43
-
44
- return data.map{|measures|Cubicle::DataLevel.new(:measures,measures)} if dimension_names.blank?
45
-
46
- dim_name = dimension_names.shift
47
-
48
- result = Cubicle::DataLevel.new(dim_name)
49
- data.each do |tuple|
50
- member_name = tuple.delete(dim_name.to_s) || "Unknown"
51
- result[member_name] << tuple
52
- end
53
-
54
- result.each do |key,value|
55
- result[key] = extract_dimensions(dimension_names,value)
56
- end
57
-
58
- expand_time_dimension_if_required(result)
59
-
60
- result
61
- end
62
-
63
- def extract_data(data)
64
- data.each do |result|
65
- new = result.dup
66
- self << new.delete("_id").merge(new.delete("value"))
67
- end
68
- end
69
-
70
- def expand_time_dimension_if_required(data_level)
71
- return unless data_level.leaf_level? && @time_dimension_name && @time_dimension_name.to_s == data_level.name.to_s &&
72
- @time_range && @time_period
73
-
74
- @time_range.by!(@time_period)
75
-
76
- @time_range.each do |date|
77
- formatted_date = date.to_cubicle(@time_period)
78
- data_level[formatted_date] = [Cubicle::DataLevel.new(:measures,{})] unless data_level.include?(formatted_date)
79
- end
80
- data_level.keys.sort!
81
- end
82
- end
83
- end
84
-
1
+ module Cubicle
2
+ module Data
3
+
4
+ def self.aggregate(data,measures)
5
+ aggregated = OrderedHashWithIndifferentAccess.new {|hash,key|hash[key]=[]}
6
+ #in step one, we will gather our values into columns to give to the measure
7
+ #definitions to aggregation.
8
+ data.each do |row|
9
+ measures.each do |measure|
10
+ if (row.include?(measure.name))
11
+ val = row[measure.name]
12
+ aggregated[measure.name] << val if val.kind_of?(Numeric)
13
+ end
14
+ end
15
+ end
16
+ #in step two, we will let the measures reduce the columns of values to a single number, preferably using
17
+ #black magic or human sacrifice
18
+ measures.each do |measure|
19
+ aggregated[measure.name] = measure.aggregate(aggregated[measure.name])
20
+ end
21
+
22
+ #give each measure a final shot to operate on the results. This is useful for measures that
23
+ #act on the results of other aggregations, like Ratio does.
24
+ measures.each {|measure|measure.finalize_aggregation(aggregated)}
25
+ aggregated
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,55 @@
1
+ module Cubicle
2
+ module Data
3
+ class Hierarchy < Cubicle::Data::Level
4
+ include Member
5
+
6
+ attr_reader :measures
7
+ def initialize(root_dimension,measures)
8
+ super(root_dimension)
9
+ @measures = measures
10
+ @member_name = name
11
+ end
12
+
13
+ def self.hierarchize_table(table, dimension_names=nil)
14
+ dimension_names = [table.time_dimension_name || table.dimension_names].flatten if dimension_names.blank?
15
+ Cubicle::Data::Hierarchy.extract_dimensions(dimension_names,table,table.dup)
16
+ end
17
+ private
18
+
19
+ def self.extract_dimensions(dimension_names, data, table,parent_level=nil)
20
+ data, dimension_names = data.dup, dimension_names.dup
21
+
22
+ return data if dimension_names.blank?
23
+
24
+ dim_name = dimension_names.shift
25
+ dim = table.dimensions.find{|d|d.name==dim_name}
26
+ level = parent_level ? Cubicle::Data::Level.new(dim,parent_level) : Cubicle::Data::Hierarchy.new(dim,data.measures)
27
+ data.each do |tuple|
28
+ member_name = tuple.delete(dim_name.to_s) || "Unknown"
29
+ level[member_name] << tuple
30
+ end
31
+
32
+ level.each do |key,value|
33
+ level[key] = Cubicle::Data::Hierarchy.extract_dimensions(dimension_names,value,table,level)
34
+ end
35
+
36
+ Cubicle::Data::Hierarchy.expand_time_dimension_if_required(level,table)
37
+
38
+ level
39
+ end
40
+
41
+ def self.expand_time_dimension_if_required(data_level,table)
42
+ return unless data_level.leaf_level? && table.time_dimension_name && table.time_dimension_name.to_s == data_level.name.to_s &&
43
+ table.time_range && table.time_period
44
+
45
+ table.time_range.by!(table.time_period)
46
+
47
+ table.time_range.each do |date|
48
+ formatted_date = date.to_cubicle(table.time_period)
49
+ data_level[formatted_date] = [OrderedHashWithIndifferentAccess.new] unless data_level.include?(formatted_date)
50
+ end
51
+ data_level.keys.sort!
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,62 @@
1
+ module Cubicle
2
+ module Data
3
+ class Level < OrderedHashWithIndifferentAccess
4
+
5
+ def initialize(dimension,parent_level=nil)
6
+ @dimension = dimension
7
+ @parent_level = parent_level
8
+ super() {|hash,key|hash[key]=[]}#Always have an array freshly baked when strangers call
9
+ end
10
+
11
+ attr_reader :dimension, :parent_level
12
+ attr_accessor :missing_member_default
13
+
14
+ alias member_names keys
15
+ alias members values
16
+
17
+ def name
18
+ @dimension.name
19
+ end
20
+
21
+ def flatten(member_name = nil, opts={}, &block)
22
+
23
+ default_val = opts[:default] || @missing_member_default || 0
24
+
25
+ self.values.inject([]) do |output, data|
26
+ data.inject(output) do |flattened, value|
27
+ value.missing_member_default = default_val if value.respond_to?(:missing_member_default)
28
+
29
+ if block_given?
30
+ flat_val = block.arity == 1 ? (yield value) : (value.instance_eval(&block))
31
+ end
32
+ flat_val ||= value[member_name] if member_name && value.include?(member_name)
33
+ flat_val ||= default_val
34
+ flattened << flat_val
35
+ end
36
+ end
37
+ end
38
+
39
+ def leaf_level?
40
+ return self.length < 1 ||
41
+ !self[self.keys[0]].is_a?(Cubicle::Data::Level)
42
+ end
43
+
44
+ def []=(key,val)
45
+ prepare_level_member(val,key,self)
46
+ super(key.to_s,val)
47
+ end
48
+
49
+ def hierarchy
50
+ parent_level || self
51
+ end
52
+
53
+ private
54
+ def prepare_level_member(member,member_name,parent_level)
55
+ member.class_eval("include Cubicle::Data::Member")
56
+ member.member_name = member_name
57
+ member.parent_level = parent_level
58
+ end
59
+
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,28 @@
1
+ module Cubicle
2
+ module Data
3
+ module Member
4
+ attr_accessor :member_name, :parent_level
5
+
6
+ def measure_values
7
+ @measure_values ||= aggregate_children()
8
+ end
9
+
10
+ def leaf_member?
11
+ !self.kind_of?(Cubicle::Data::Level)
12
+ end
13
+
14
+ def measures
15
+ parent_level.hierarchy.measures
16
+ end
17
+
18
+ def measure_data
19
+ leaf_member? ? self : members.map{|member|member.aggregate_children}
20
+ end
21
+
22
+ def aggregate_children()
23
+ Cubicle::Data.aggregate(measure_data,measures)
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,56 @@
1
+ module Cubicle
2
+ module Data
3
+ class Table < Array
4
+ attr_reader :total_count, :dimensions, :measures, :time_dimension_name, :time_range, :time_period
5
+
6
+ def initialize(query,query_results,total_count = nil)
7
+ @dimensions = Marshal.load(Marshal.dump(query.dimensions))
8
+ @measures = Marshal.load(Marshal.dump(query.measures))
9
+ @time_dimension_name = query.time_dimension.name if query.respond_to?(:time_dimension) && query.time_dimension
10
+ @time_period = query.time_period if query.respond_to?(:time_period)
11
+ @time_range = query.time_range if query.respond_to?(:time_range)
12
+ extract_data(query_results)
13
+ @total_count = total_count if total_count
14
+ end
15
+
16
+ def dimension_names
17
+ @dimensions.map{|d|d.name}
18
+ end
19
+
20
+ def measure_names
21
+ @measures.map{|m|m.name}
22
+ end
23
+
24
+ def hierarchize(*args)
25
+ Cubicle::Data::Hierarchy.hierarchize_table(self,args)
26
+ end
27
+ alias hierarchize_by hierarchize
28
+ alias by hierarchize
29
+
30
+ def records_per_page=(records_per_page)
31
+ @records_per_page=records_per_page
32
+ end
33
+
34
+ def total_pages
35
+ if (!defined?(@total_count))
36
+ raise "Cannot find the total number of pages without setting the total count"
37
+ end
38
+
39
+ if (!defined?(@records_per_page))
40
+ raise "Cannot find the total number of pages without setting the number of records per page"
41
+ end
42
+
43
+ (@total_count.to_f / @records_per_page.to_f).ceil
44
+ end
45
+
46
+ private
47
+
48
+ def extract_data(data)
49
+ data.each do |result|
50
+ new = result.dup
51
+ self << OrderedHashWithIndifferentAccess.new(new.delete("_id").merge(new.delete("value")))
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -1,20 +1,30 @@
1
- module Cubicle
2
- class Measure < Member
3
-
4
- def initialize(*args)
5
- super
6
- @aggregation_method = self.options.delete(:aggregation_method) || default_aggregation_method
7
- end
8
-
9
- attr_accessor :aggregation_method #can be :sum, :average, :count
10
-
11
- def to_js_value
12
- return super unless aggregation_method == :count
13
- "((#{super}) ? 1 : 0)"
14
- end
15
-
16
- def default_aggregation_method
17
- :count
18
- end
19
- end
20
- end
1
+ module Cubicle
2
+ class Measure < Member
3
+
4
+ def initialize(*args)
5
+ super
6
+ @aggregation_method = self.options.delete(:aggregation_method) || default_aggregation_method
7
+ end
8
+
9
+ attr_accessor :aggregation_method #can be :sum, :average, :count
10
+
11
+ def to_js_value
12
+ return super unless aggregation_method == :count
13
+ "((#{super}) ? 1 : 0)"
14
+ end
15
+
16
+ def default_aggregation_method
17
+ :count
18
+ end
19
+
20
+ def aggregate(values)
21
+ return nil if values.blank?
22
+ sum = values.inject(0){|total,val|total+val}
23
+ aggregation_method == :average ? sum/values.length : sum
24
+ end
25
+
26
+ def finalize_aggregation(aggregation)
27
+ aggregation
28
+ end
29
+ end
30
+ end
@@ -3,7 +3,7 @@ module Cubicle
3
3
  module AggregatePlugin
4
4
  module ClassMethods
5
5
  def aggregate(&block)
6
- return Cubicle::Aggregation.new(self.collection_name,&block)
6
+ return Cubicle::Aggregation::AdHoc.new(self.collection_name,&block)
7
7
  end
8
8
  end
9
9
 
@@ -0,0 +1,27 @@
1
+ class OrderedHashWithIndifferentAccess < OrderedHash
2
+ def initialize(initial_data={},&block)
3
+ merge!(initial_data.stringify_keys)
4
+ super(&block) if block
5
+ end
6
+
7
+
8
+ def [](key)
9
+ key = key.to_s
10
+ #self[key] = [] unless self.keys.include?(key)
11
+ super(key)
12
+ end
13
+
14
+ def []=(key,val)
15
+ super(key.to_s,val)
16
+ end
17
+
18
+ def include?(key)
19
+ super(key.to_s)
20
+ end
21
+
22
+ def method_missing(sym,*args,&block)
23
+ return self[sym.to_s[0..-2]] = args[0] if sym.to_s =~ /.*=$/
24
+ return self[sym] if self.keys.include?(sym.to_s)
25
+ missing_member_default
26
+ end
27
+ end
data/lib/cubicle/query.rb CHANGED
@@ -1,14 +1,16 @@
1
1
  module Cubicle
2
2
  class Query
3
+ include Dsl
3
4
 
4
- attr_reader :time_period, :transient
5
+ attr_reader :time_period, :transient, :aggregation
5
6
  attr_accessor :source_collection_name
6
- def initialize(cubicle)
7
- @cubicle = cubicle
7
+
8
+ def initialize(aggregation)
9
+ @aggregation = aggregation
8
10
 
9
11
  @dimensions = Cubicle::MemberList.new
10
12
  @measures = Cubicle::MemberList.new
11
- @source_collection_name = @cubicle.target_collection_name
13
+ @source_collection_name = @aggregation.target_collection_name
12
14
  @where, @from_date, @to_date, @date_dimension, @time_period, @limit, @offset = nil
13
15
  @all_dimensions, @all_measures = false, false
14
16
  @transient = false
@@ -16,31 +18,24 @@ module Cubicle
16
18
  @order_by=[]
17
19
  @from_date_filter = "$gte"
18
20
  @to_date_filter = "$lte"
21
+ @query_aliases=HashWithIndifferentAccess.new
19
22
  end
20
23
 
21
24
  def clone
22
25
  Marshal.load(Marshal.dump(self))
23
26
  end
24
27
 
25
- def select_all
26
- select :all_dimensions, :all_measures
27
- end
28
-
29
28
  def selected?(member = nil)
30
29
  return (@dimensions.length > 0 || @measures.length > 0) unless member
31
- member_name = member.kind_of?(Cubicle::Member) ? member.name : member.to_s
30
+ member_name = member.kind_of?(Cubicle::Member) ? member.name : unalias(member.to_s)
32
31
  return @dimensions[member_name] ||
33
32
  @measures[member_name]
34
33
  end
35
34
 
36
35
  def transient?
37
- @transient || @cubicle.transient?
36
+ @transient || @aggregation.transient?
38
37
  end
39
38
 
40
- def transient!
41
- @transient = true
42
- @source_collection_name = nil
43
- end
44
39
 
45
40
  def all_measures?
46
41
  @all_measures
@@ -48,183 +43,7 @@ module Cubicle
48
43
 
49
44
  def all_dimensions?
50
45
  @all_dimensions
51
- end
52
-
53
- def select(*args)
54
- args = args[0] if args[0].is_a?(Array)
55
-
56
- if (args.include?(:all))
57
- select_all
58
- return
59
- end
60
-
61
- if (args.include?(:all_measures))
62
- @all_measures = true
63
- @measures = Cubicle::MemberList.new
64
- end
65
- if (args.include?(:all_dimensions))
66
- @all_dimensions = true
67
- @dimensions = Cubicle::MemberList.new
68
- end
69
-
70
- return if args.length == 1 && selected?(args[0])
71
-
72
- found=[:all_measures,:all_dimensions]
73
-
74
- if args.length == 1 && !all_dimensions? && args[0].is_a?(Cubicle::Dimension)
75
- @dimensions << convert_dimension(args.pop)
76
- elsif args.length == 1 && !all_measures? && args[0].is_a?(Cubicle::Measure)
77
- @measures << convert_measure(args.pop)
78
- else
79
- #remove from the list any dimensions or measures that are already
80
- #selected. This allows select to be idempotent,
81
- #which is useful for ensuring certain members are selected
82
- #even though the user may already have selected them previously
83
- args.each do |member_name|
84
- if (member = @cubicle.dimensions[member_name])
85
- @dimensions << convert_dimension(member)
86
- elsif (member = @cubicle.measures[member_name])
87
- @measures << convert_measure(member)
88
- end
89
- found << member_name if member || selected?(member_name)
90
- end
91
- end
92
- args = args - found
93
- raise "You selected one or more members that do not exist in the underlying data source:#{args.inspect}" unless args.blank?
94
- self
95
- end
96
-
97
- def limit(in_limit = nil)
98
- return @limit unless in_limit
99
- @limit = in_limit
100
- return self
101
- end
102
-
103
- def offset(in_offset = nil)
104
- return @offset unless in_offset
105
- @offset = in_offset
106
- return self
107
- end
108
- alias skip offset
109
-
110
- def by(*args)
111
- return @by unless args.length > 0
112
-
113
- #We'll need these in the result set
114
- select *args
115
-
116
- #replace any alias names with actual member names
117
- @by = args.map{|member_name|@cubicle.find_member(member_name).name}
118
- return if @time_dimension #If a time dimension has been explicitly specified, the following isn't helpful.
119
-
120
- #Now let's see if we can find ourselves a time dimension
121
- if (@cubicle.time_dimension && time_dimension.included_in?(args))
122
- time_dimension(@cubicle.time_dimension)
123
- else
124
- args.each do |by_member|
125
- if (detected = detect_time_period by_member)
126
- time_dimension by_member
127
- @time_period = detected
128
- break
129
- end
130
- end
131
- end
132
- end
133
-
134
- def order_by(*args)
135
- return @order_by unless args.length > 0
136
- args.each do |order|
137
- @order_by << (order.is_a?(Array) ? order : [order,:asc])
138
- end
139
- end
140
-
141
- def time_range(date_range = nil)
142
- return nil unless date_range || @from_date || @to_date
143
- unless date_range
144
- start,stop = @from_date || Time.now, @to_date || Time.now
145
- return @to_date_filter=="$lte" ? start..stop : start...stop
146
- end
147
-
148
- @to_date_filter = date_range.exclude_end? ? "$lt" : "$lte"
149
- @from_date, @to_date = date_range.first, date_range.last if date_range
150
- end
151
-
152
- def time_dimension(dimension = nil)
153
- return (@time_dimension ||= @cubicle.time_dimension) unless dimension
154
- @time_dimension = dimension.is_a?(Cubicle::Dimension) ? dimension : @cubicle.dimensions[dimension]
155
- raise "No dimension matching the name #{dimension} could be found in the underlying data source" unless @time_dimension
156
- #select @time_dimension unless selected?(dimension)
157
- end
158
- alias date_dimension time_dimension
159
-
160
- def last(duration,as_of = Time.now)
161
- duration = 1.send(duration) if [:year,:month,:week,:day].include?(duration)
162
- period = duration.parts[0][0]
163
- @from_date = duration.ago(as_of).advance(period=>1)
164
- @to_date = as_of
165
- end
166
- alias for_the_last last
167
-
168
- def last_complete(duration,as_of = Time.now)
169
- duration = 1.send(duration) if [:year,:month,:week,:day].include?(duration)
170
- period = duration.parts[0][0]
171
- @to_date = as_of.beginning_of(period)
172
- @from_date = duration.ago(@to_date)
173
- @to_date_filter = "$lt"
174
- end
175
- alias for_the_last_complete last_complete
176
-
177
- def next(duration,as_of = Time.now)
178
- duration = 1.send(duration) if [:year,:month,:week,:day].include?(duration)
179
- period = duration.parts[0][0]
180
- @to_date = duration.from_now(as_of).advance(period=>-1)
181
- @from_date = as_of
182
- end
183
- alias for_the_next next
184
-
185
- def this(period,as_of = Time.now)
186
- @from_date = as_of.beginning_of(period)
187
- @to_date = as_of
188
- self
189
- end
190
-
191
- def from(time = nil)
192
- return @from_date unless time
193
- @from_date = if time.is_a?(Symbol)
194
- Time.send(time) if Time.respond_to?(time)
195
- Date.send(time).to_time if Date.respond_to?(time)
196
- else
197
- time.to_time
198
- end
199
- self
200
- end
201
-
202
- def until(time = nil)
203
- return @to_date unless time
204
- @to_date = if time.is_a?(Symbol)
205
- Time.send(time) if Time.respond_to?(time)
206
- Date.send(time).to_time if Date.respond_to?(time)
207
- else
208
- time.to_time
209
- end
210
- self
211
- end
212
-
213
- def ytd(as_of = Time.now)
214
- this :year, as_of
215
- end
216
- alias year_to_date ytd
217
-
218
- def mtd(as_of = Time.now)
219
- this :month, as_of
220
- end
221
- alias month_to_date mtd
222
-
223
- def where(filter = nil)
224
- return prepare_filter unless filter
225
- (@where ||= {}).merge!(filter)
226
- self
227
- end
46
+ end
228
47
 
229
48
  def dimension_names
230
49
  return dimensions.map{|dim|dim.name.to_s}
@@ -236,16 +55,16 @@ module Cubicle
236
55
 
237
56
  def dimensions
238
57
  return @dimensions unless all_dimensions?
239
- @cubicle.dimensions.collect{|dim|convert_dimension(dim)}
58
+ @aggregation.dimensions.collect{|dim|convert_dimension(dim)}
240
59
  end
241
60
 
242
61
  def measures
243
62
  return @measures unless all_measures?
244
- @cubicle.measures.collect{|measure|convert_measure(measure)}
63
+ @aggregation.measures.collect{|measure|convert_measure(measure)}
245
64
  end
246
65
 
247
66
  def execute(options={})
248
- @cubicle.execute_query(self,options)
67
+ @aggregation.execute_query(self,options)
249
68
  end
250
69
 
251
70
  private
@@ -315,5 +134,13 @@ module Cubicle
315
134
  Cubicle::Measure.new(measure.name, :expression=>expression,:aggregation_method=>aggregation)
316
135
  end
317
136
 
137
+ def unalias(*name_or_names)
138
+ return (@query_aliases[name_or_names[0]] || name_or_names[0]) unless
139
+ name_or_names.length > 1 || name_or_names[0].is_a?(Array)
140
+
141
+ name_or_names = name_or_names[0] if name_or_names[0].is_a?(Array)
142
+ name_or_names.map {|name|@query_aliases[name] || name}
143
+ end
144
+
318
145
  end
319
146
  end