cubicle 0.1.4 → 0.1.5
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 +7 -0
- data/cubicle.gemspec +2 -2
- data/lib/cubicle/aggregation/dsl.rb +116 -107
- data/lib/cubicle/duration.rb +1 -1
- data/lib/cubicle/version.rb +1 -1
- data/test/cubicle/duration_test.rb +68 -46
- data/test/cubicles/defect_cubicle.rb +33 -31
- data/test/log/test.log +5821 -0
- metadata +3 -3
data/CHANGELOG.rdoc
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
==0.1.5
|
2
|
+
* Added duration_since (aliased as elapsed, age_since) to calculate the time elapsed since any
|
3
|
+
given timestamp and the current execution time of the query.
|
4
|
+
|
5
|
+
== 0.1.4
|
6
|
+
* Cached aggregations were being stored on disk, but not properly utilized. This is now fixed.
|
7
|
+
|
1
8
|
== 0.1.3
|
2
9
|
* Formalized flat (Cubicle::Data::Table) and hierarchical (Cubicle::Data::Hierarchy) data results from cubicle queries,
|
3
10
|
and added client side support for rolling up measure values in hierarchical data so that no matter how a given
|
data/cubicle.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{cubicle}
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.5"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Nathan Stults"]
|
12
|
-
s.date = %q{2010-03-
|
12
|
+
s.date = %q{2010-03-29}
|
13
13
|
s.description = %q{Cubicle provides a dsl and aggregation caching framework for automating the generation, execution and caching of map reduce queries when using MongoDB in Ruby. Cubicle also includes a MongoMapper plugin for quickly performing ad-hoc, multi-level group-by queries against a MongoMapper model.}
|
14
14
|
s.email = %q{hereiam@sonic.net}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -1,108 +1,117 @@
|
|
1
|
-
module Cubicle
|
2
|
-
module Aggregation
|
3
|
-
module Dsl
|
4
|
-
|
5
|
-
def source_collection_name(collection_name = nil)
|
6
|
-
return @source_collection = collection_name if collection_name
|
7
|
-
@source_collection ||= name.chomp("Cubicle").chomp("Cube").chomp("Aggregation").underscore.pluralize
|
8
|
-
end
|
9
|
-
alias source_collection_name= source_collection_name
|
10
|
-
|
11
|
-
def target_collection_name(collection_name = nil)
|
12
|
-
return nil if transient?
|
13
|
-
return @target_name = collection_name if collection_name
|
14
|
-
@target_name ||= "#{name.blank? ? source_collection_name : name.underscore.pluralize}_cubicle"
|
15
|
-
end
|
16
|
-
alias target_collection_name= target_collection_name
|
17
|
-
|
18
|
-
def dimension(*args)
|
19
|
-
dimensions << Cubicle::Dimension.new(*args)
|
20
|
-
dimensions[-1]
|
21
|
-
end
|
22
|
-
|
23
|
-
def dimensions(*args)
|
24
|
-
return (@dimensions ||= Cubicle::MemberList.new) if args.length < 1
|
25
|
-
args = args[0] if args.length == 1 && args[0].is_a?(Array)
|
26
|
-
args.each {|dim| dimension dim }
|
27
|
-
@dimensions
|
28
|
-
end
|
29
|
-
|
30
|
-
def measure(*args)
|
31
|
-
measures << Measure.new(*args)
|
32
|
-
measures[-1]
|
33
|
-
end
|
34
|
-
|
35
|
-
def measures(*args)
|
36
|
-
return (@measures ||= Cubicle::MemberList.new) if args.length < 1
|
37
|
-
args = args[0] if args.length == 1 && args[0].is_a?(Array)
|
38
|
-
args.each {|m| measure m}
|
39
|
-
@measures
|
40
|
-
end
|
41
|
-
|
42
|
-
def count(*args)
|
43
|
-
options = args.extract_options!
|
44
|
-
options[:aggregation_method] = :count
|
45
|
-
measure(*(args << options))
|
46
|
-
end
|
47
|
-
|
48
|
-
def average(*args)
|
49
|
-
options = args.extract_options!
|
50
|
-
options[:aggregation_method] = :average
|
51
|
-
measure(*(args << options))
|
52
|
-
#Averaged fields need a count of non-null values to properly calculate the average
|
53
|
-
args[0] = "#{args[0]}_count".to_sym
|
54
|
-
count *args
|
55
|
-
end
|
56
|
-
alias avg average
|
57
|
-
|
58
|
-
def sum(*args)
|
59
|
-
options = args.extract_options!
|
60
|
-
options[:aggregation_method] = :sum
|
61
|
-
measure(*(args << options))
|
62
|
-
end
|
63
|
-
|
64
|
-
def duration(*args)
|
65
|
-
options = args.extract_options!
|
66
|
-
options[:in] ||= durations_in
|
67
|
-
args << options
|
68
|
-
measures << (dur = Duration.new(*args))
|
69
|
-
count("#{dur.name}_count".to_sym, :expression=>dur.expression) if dur.aggregation_method == :average
|
70
|
-
end
|
71
|
-
|
72
|
-
def average_duration(*args)
|
73
|
-
duration(*args)
|
74
|
-
end
|
75
|
-
alias avg_duration average_duration
|
76
|
-
|
77
|
-
def total_duration(*args)
|
78
|
-
options = args.extract_options!
|
79
|
-
options[:aggregation_method] = :sum
|
80
|
-
duration(*(args<<options))
|
81
|
-
end
|
82
|
-
|
83
|
-
def
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
def
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
1
|
+
module Cubicle
|
2
|
+
module Aggregation
|
3
|
+
module Dsl
|
4
|
+
|
5
|
+
def source_collection_name(collection_name = nil)
|
6
|
+
return @source_collection = collection_name if collection_name
|
7
|
+
@source_collection ||= name.chomp("Cubicle").chomp("Cube").chomp("Aggregation").underscore.pluralize
|
8
|
+
end
|
9
|
+
alias source_collection_name= source_collection_name
|
10
|
+
|
11
|
+
def target_collection_name(collection_name = nil)
|
12
|
+
return nil if transient?
|
13
|
+
return @target_name = collection_name if collection_name
|
14
|
+
@target_name ||= "#{name.blank? ? source_collection_name : name.underscore.pluralize}_cubicle"
|
15
|
+
end
|
16
|
+
alias target_collection_name= target_collection_name
|
17
|
+
|
18
|
+
def dimension(*args)
|
19
|
+
dimensions << Cubicle::Dimension.new(*args)
|
20
|
+
dimensions[-1]
|
21
|
+
end
|
22
|
+
|
23
|
+
def dimensions(*args)
|
24
|
+
return (@dimensions ||= Cubicle::MemberList.new) if args.length < 1
|
25
|
+
args = args[0] if args.length == 1 && args[0].is_a?(Array)
|
26
|
+
args.each {|dim| dimension dim }
|
27
|
+
@dimensions
|
28
|
+
end
|
29
|
+
|
30
|
+
def measure(*args)
|
31
|
+
measures << Measure.new(*args)
|
32
|
+
measures[-1]
|
33
|
+
end
|
34
|
+
|
35
|
+
def measures(*args)
|
36
|
+
return (@measures ||= Cubicle::MemberList.new) if args.length < 1
|
37
|
+
args = args[0] if args.length == 1 && args[0].is_a?(Array)
|
38
|
+
args.each {|m| measure m}
|
39
|
+
@measures
|
40
|
+
end
|
41
|
+
|
42
|
+
def count(*args)
|
43
|
+
options = args.extract_options!
|
44
|
+
options[:aggregation_method] = :count
|
45
|
+
measure(*(args << options))
|
46
|
+
end
|
47
|
+
|
48
|
+
def average(*args)
|
49
|
+
options = args.extract_options!
|
50
|
+
options[:aggregation_method] = :average
|
51
|
+
measure(*(args << options))
|
52
|
+
#Averaged fields need a count of non-null values to properly calculate the average
|
53
|
+
args[0] = "#{args[0]}_count".to_sym
|
54
|
+
count *args
|
55
|
+
end
|
56
|
+
alias avg average
|
57
|
+
|
58
|
+
def sum(*args)
|
59
|
+
options = args.extract_options!
|
60
|
+
options[:aggregation_method] = :sum
|
61
|
+
measure(*(args << options))
|
62
|
+
end
|
63
|
+
|
64
|
+
def duration(*args)
|
65
|
+
options = args.extract_options!
|
66
|
+
options[:in] ||= durations_in
|
67
|
+
args << options
|
68
|
+
measures << (dur = Duration.new(*args))
|
69
|
+
count("#{dur.name}_count".to_sym, :expression=>dur.expression) if dur.aggregation_method == :average
|
70
|
+
end
|
71
|
+
|
72
|
+
def average_duration(*args)
|
73
|
+
duration(*args)
|
74
|
+
end
|
75
|
+
alias avg_duration average_duration
|
76
|
+
|
77
|
+
def total_duration(*args)
|
78
|
+
options = args.extract_options!
|
79
|
+
options[:aggregation_method] = :sum
|
80
|
+
duration(*(args<<options))
|
81
|
+
end
|
82
|
+
|
83
|
+
def duration_since(*args)
|
84
|
+
options = args.extract_options!
|
85
|
+
ms1 = args.length > 1 ? args.delete_at(1) : args.shift
|
86
|
+
options[ms1] = :now
|
87
|
+
duration(*(args<<options))
|
88
|
+
end
|
89
|
+
alias age_since duration_since
|
90
|
+
alias elapsed duration_since
|
91
|
+
|
92
|
+
def durations_in(unit_of_time = nil)
|
93
|
+
return (@duration_unit ||= :seconds) unless unit_of_time
|
94
|
+
@duration_unit = unit_of_time.to_s.pluralize.to_sym
|
95
|
+
end
|
96
|
+
alias :duration_unit :durations_in
|
97
|
+
|
98
|
+
|
99
|
+
def ratio(member_name, numerator, denominator)
|
100
|
+
measures << Ratio.new(member_name, numerator, denominator)
|
101
|
+
end
|
102
|
+
|
103
|
+
def aggregation(*member_list)
|
104
|
+
member_list = member_list[0] if member_list[0].is_a?(Array)
|
105
|
+
aggregations << member_list
|
106
|
+
end
|
107
|
+
|
108
|
+
def time_dimension(*args)
|
109
|
+
return (@time_dimension ||= nil) unless args.length > 0
|
110
|
+
@time_dimension = dimension(*args)
|
111
|
+
end
|
112
|
+
alias time_dimension= time_dimension
|
113
|
+
alias date time_dimension
|
114
|
+
alias time time_dimension
|
115
|
+
end
|
116
|
+
end
|
108
117
|
end
|
data/lib/cubicle/duration.rb
CHANGED
@@ -23,7 +23,7 @@ module Cubicle
|
|
23
23
|
cond = " && (#{self.condition})" unless self.condition.blank?
|
24
24
|
#prefix these names for the expression
|
25
25
|
prefix = "#{self.timestamp_prefix}#{self.timestamp_prefix.blank? ? '' : '.'}"
|
26
|
-
ms1,ms2 = [self.begin_milestone,self.end_milestone].map{|ms|"this.#{prefix}#{ms}"}
|
26
|
+
ms1,ms2 = [self.begin_milestone,self.end_milestone].map{|ms|ms.to_s=='now' ? "new Date(#{Time.now.to_i*1000})" : "this.#{prefix}#{ms}"}
|
27
27
|
@expression = "(#{ms1} && #{ms2}#{cond}) ? (#{ms2}-#{ms1})/#{denominator} : null"
|
28
28
|
end
|
29
29
|
|
data/lib/cubicle/version.rb
CHANGED
@@ -1,47 +1,69 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
class DurationTest < ActiveSupport::TestCase
|
4
|
-
context "Querying a cubicle with durations" do
|
5
|
-
setup do
|
6
|
-
#record 1, ms1 = 1/1/2000, ms2 = 1/2/2000, ms3=1/4/2000
|
7
|
-
#record 2, ms1 = 1/1/2000, ms2 = 1/3/2000, ms3=1/5/2000
|
8
|
-
Defect.create_duration_test_data
|
9
|
-
end
|
10
|
-
should "correctly calculate durations for each activity" do
|
11
|
-
results = DefectCubicle.query do
|
12
|
-
select :all
|
13
|
-
by :operator
|
14
|
-
end
|
15
|
-
a1 = results["a"][0]
|
16
|
-
a2 = results["b"][0]
|
17
|
-
|
18
|
-
assert_equal 1 * 60 * 60 * 24, a1["ms1_to_ms2_average"]
|
19
|
-
assert_equal 2 * 60 * 60 * 24, a1["ms2_to_ms3_sum"]
|
20
|
-
assert_equal 3, a1["total_duration"]
|
21
|
-
|
22
|
-
assert_equal 2 * 60 * 60 * 24, a2["ms1_to_ms2_average"]
|
23
|
-
assert_equal 2 * 60 * 60 * 24, a2["ms2_to_ms3_sum"]
|
24
|
-
assert_equal 4, a2["total_duration"]
|
25
|
-
end
|
26
|
-
|
27
|
-
results = DefectCubicle.query do
|
28
|
-
select :all_measures, :product
|
29
|
-
end
|
30
|
-
results = results[0]
|
31
|
-
|
32
|
-
assert_equal 1.5 * 60 * 60 * 24, results["ms1_to_ms2_average"]
|
33
|
-
assert_equal 4 * 60 * 60 * 24, results["ms2_to_ms3_sum"]
|
34
|
-
assert_equal 3.5, results["total_duration"]
|
35
|
-
|
36
|
-
|
37
|
-
should "respect the condition argument" do
|
38
|
-
results = DefectCubicle.query do
|
39
|
-
select :all_measures, :product
|
40
|
-
end
|
41
|
-
results = results[0]
|
42
|
-
|
43
|
-
assert_equal 3, results["conditional_duration"]
|
44
|
-
end
|
45
|
-
|
46
|
-
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class DurationTest < ActiveSupport::TestCase
|
4
|
+
context "Querying a cubicle with durations" do
|
5
|
+
setup do
|
6
|
+
#record 1, ms1 = 1/1/2000, ms2 = 1/2/2000, ms3=1/4/2000
|
7
|
+
#record 2, ms1 = 1/1/2000, ms2 = 1/3/2000, ms3=1/5/2000
|
8
|
+
Defect.create_duration_test_data
|
9
|
+
end
|
10
|
+
should "correctly calculate durations for each activity" do
|
11
|
+
results = DefectCubicle.query do
|
12
|
+
select :all
|
13
|
+
by :operator
|
14
|
+
end
|
15
|
+
a1 = results["a"][0]
|
16
|
+
a2 = results["b"][0]
|
17
|
+
|
18
|
+
assert_equal 1 * 60 * 60 * 24, a1["ms1_to_ms2_average"]
|
19
|
+
assert_equal 2 * 60 * 60 * 24, a1["ms2_to_ms3_sum"]
|
20
|
+
assert_equal 3, a1["total_duration"]
|
21
|
+
|
22
|
+
assert_equal 2 * 60 * 60 * 24, a2["ms1_to_ms2_average"]
|
23
|
+
assert_equal 2 * 60 * 60 * 24, a2["ms2_to_ms3_sum"]
|
24
|
+
assert_equal 4, a2["total_duration"]
|
25
|
+
end
|
26
|
+
should "correctly aggregate durations" do
|
27
|
+
results = DefectCubicle.query do
|
28
|
+
select :all_measures, :product
|
29
|
+
end
|
30
|
+
results = results[0]
|
31
|
+
|
32
|
+
assert_equal 1.5 * 60 * 60 * 24, results["ms1_to_ms2_average"]
|
33
|
+
assert_equal 4 * 60 * 60 * 24, results["ms2_to_ms3_sum"]
|
34
|
+
assert_equal 3.5, results["total_duration"]
|
35
|
+
|
36
|
+
end
|
37
|
+
should "respect the condition argument" do
|
38
|
+
results = DefectCubicle.query do
|
39
|
+
select :all_measures, :product
|
40
|
+
end
|
41
|
+
results = results[0]
|
42
|
+
|
43
|
+
assert_equal 3, results["conditional_duration"]
|
44
|
+
end
|
45
|
+
should "calculate duration_since via elapsed" do
|
46
|
+
Time.now = "1/10/2000"
|
47
|
+
results = DefectCubicle.query do
|
48
|
+
select :all_measures, :product
|
49
|
+
end
|
50
|
+
puts results.inspect
|
51
|
+
results = results[0]
|
52
|
+
|
53
|
+
assert_equal((6+5)/2.0, results["ms3_to_now_average"])
|
54
|
+
|
55
|
+
end
|
56
|
+
should "calculate named duration_since via age_since" do
|
57
|
+
Time.now = "1/10/2000"
|
58
|
+
results = DefectCubicle.query do
|
59
|
+
select :all_measures, :product
|
60
|
+
end
|
61
|
+
puts results.inspect
|
62
|
+
results = results[0]
|
63
|
+
|
64
|
+
assert_equal((6+5)/2.0, results["avg_time_since_ms3"])
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
47
69
|
end
|
@@ -1,32 +1,34 @@
|
|
1
|
-
class DefectCubicle
|
2
|
-
extend Cubicle::Aggregation
|
3
|
-
|
4
|
-
date :manufacture_date, :field_name=>'manufacture_date', :alias=>:date
|
5
|
-
dimension :month, :expression=>'this.manufacture_date.substring(0,7)'
|
6
|
-
dimension :year, :expression=>'this.manufacture_date.substring(0,4)'
|
7
|
-
|
8
|
-
dimension :manufacture_time
|
9
|
-
|
10
|
-
dimension :product, :field_name=>'product.name'
|
11
|
-
dimension :region, :field_name=>'plant.address.region'
|
12
|
-
|
13
|
-
dimensions :operator, :outcome
|
14
|
-
|
15
|
-
count :total_defects, :field_name=>'defect_id'
|
16
|
-
count :preventable_defects, :expression=>'this.root_cause != "act_of_god"'
|
17
|
-
sum :total_cost, :field_name=>'cost'
|
18
|
-
avg :avg_cost, :field_name=>'cost'
|
19
|
-
|
20
|
-
#calculated fields
|
21
|
-
ratio :preventable_pct, :preventable_defects, :total_defects
|
22
|
-
|
23
|
-
#durations
|
24
|
-
average_duration :ms1 => :ms2
|
25
|
-
total_duration :ms2 => :ms3
|
26
|
-
duration
|
27
|
-
duration
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
1
|
+
class DefectCubicle
|
2
|
+
extend Cubicle::Aggregation
|
3
|
+
|
4
|
+
date :manufacture_date, :field_name=>'manufacture_date', :alias=>:date
|
5
|
+
dimension :month, :expression=>'this.manufacture_date.substring(0,7)'
|
6
|
+
dimension :year, :expression=>'this.manufacture_date.substring(0,4)'
|
7
|
+
|
8
|
+
dimension :manufacture_time
|
9
|
+
|
10
|
+
dimension :product, :field_name=>'product.name'
|
11
|
+
dimension :region, :field_name=>'plant.address.region'
|
12
|
+
|
13
|
+
dimensions :operator, :outcome
|
14
|
+
|
15
|
+
count :total_defects, :field_name=>'defect_id'
|
16
|
+
count :preventable_defects, :expression=>'this.root_cause != "act_of_god"'
|
17
|
+
sum :total_cost, :field_name=>'cost'
|
18
|
+
avg :avg_cost, :field_name=>'cost'
|
19
|
+
|
20
|
+
#calculated fields
|
21
|
+
ratio :preventable_pct, :preventable_defects, :total_defects
|
22
|
+
|
23
|
+
#durations
|
24
|
+
average_duration :ms1 => :ms2
|
25
|
+
total_duration :ms2 => :ms3
|
26
|
+
duration :total_duration, :ms1 => :ms3, :in=>:days
|
27
|
+
duration :conditional_duration, :ms1 => :ms3, :in=>:days, :condition=>"this.defect_id != 2"
|
28
|
+
elapsed :ms3, :in=>:days
|
29
|
+
age_since :avg_time_since_ms3, :ms3, :in=>:days
|
30
|
+
|
31
|
+
#pre-cached aggregations
|
32
|
+
aggregation :month, :year, :product
|
33
|
+
aggregation :month, :region
|
32
34
|
end
|