cubicle 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +14 -0
- data/README.rdoc +188 -174
- data/cubicle.gemspec +26 -10
- data/lib/cubicle.rb +47 -422
- data/lib/cubicle/aggregation.rb +58 -7
- data/lib/cubicle/aggregation/ad_hoc.rb +12 -0
- data/lib/cubicle/aggregation/aggregation_manager.rb +212 -0
- data/lib/cubicle/aggregation/dsl.rb +108 -0
- data/lib/cubicle/aggregation/map_reduce_helper.rb +55 -0
- data/lib/cubicle/data.rb +29 -84
- data/lib/cubicle/data/hierarchy.rb +55 -0
- data/lib/cubicle/data/level.rb +62 -0
- data/lib/cubicle/data/member.rb +28 -0
- data/lib/cubicle/data/table.rb +56 -0
- data/lib/cubicle/measure.rb +30 -20
- data/lib/cubicle/mongo_mapper/aggregate_plugin.rb +1 -1
- data/lib/cubicle/ordered_hash_with_indifferent_access.rb +27 -0
- data/lib/cubicle/query.rb +21 -194
- data/lib/cubicle/query/dsl.rb +118 -0
- data/lib/cubicle/query/dsl/time_intelligence.rb +89 -0
- data/lib/cubicle/ratio.rb +28 -12
- data/lib/cubicle/version.rb +2 -2
- data/test/cubicle/aggregation/ad_hoc_test.rb +21 -0
- data/test/cubicle/cubicle_aggregation_test.rb +84 -20
- data/test/cubicle/cubicle_query_test.rb +36 -0
- data/test/cubicle/data/data_test.rb +30 -0
- data/test/cubicle/data/level_test.rb +42 -0
- data/test/cubicle/data/member_test.rb +40 -0
- data/test/cubicle/{cubicle_data_test.rb → data/table_test.rb} +50 -50
- data/test/cubicle/duration_test.rb +46 -48
- data/test/cubicle/ordered_hash_with_indifferent_access_test.rb +19 -0
- data/test/cubicles/defect_cubicle.rb +31 -31
- data/test/log/test.log +102066 -0
- metadata +26 -10
- data/lib/cubicle/data_level.rb +0 -60
- data/test/cubicle/cubicle_data_level_test.rb +0 -58
- data/test/cubicle/cubicle_test.rb +0 -85
data/lib/cubicle/data.rb
CHANGED
@@ -1,84 +1,29 @@
|
|
1
|
-
module Cubicle
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
data/lib/cubicle/measure.rb
CHANGED
@@ -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
|
-
|
20
|
-
|
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
|
@@ -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
|
-
|
7
|
-
|
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 = @
|
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 || @
|
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
|
-
@
|
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
|
-
@
|
63
|
+
@aggregation.measures.collect{|measure|convert_measure(measure)}
|
245
64
|
end
|
246
65
|
|
247
66
|
def execute(options={})
|
248
|
-
@
|
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
|