cubicle 0.1.2 → 0.1.3
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/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
|