wonkavision 0.5.11 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +3 -0
- data/lib/wonkavision.rb +28 -1
- data/lib/wonkavision/aggregation.rb +21 -0
- data/lib/wonkavision/event_coordinator.rb +19 -7
- data/lib/wonkavision/extensions/symbol.rb +55 -0
- data/lib/wonkavision/facts.rb +27 -0
- data/lib/wonkavision/local_job_queue.rb +28 -0
- data/lib/wonkavision/message_mapper.rb +2 -2
- data/lib/wonkavision/message_mapper/map.rb +60 -8
- data/lib/wonkavision/persistence/mongo.rb +95 -0
- data/lib/wonkavision/plugins.rb +2 -1
- data/lib/wonkavision/plugins/analytics/aggregation.rb +139 -0
- data/lib/wonkavision/plugins/analytics/aggregation/aggregation_spec.rb +53 -0
- data/lib/wonkavision/plugins/analytics/aggregation/attribute.rb +22 -0
- data/lib/wonkavision/plugins/analytics/aggregation/dimension.rb +64 -0
- data/lib/wonkavision/plugins/analytics/aggregation/measure.rb +240 -0
- data/lib/wonkavision/plugins/analytics/cellset.rb +171 -0
- data/lib/wonkavision/plugins/analytics/facts.rb +106 -0
- data/lib/wonkavision/plugins/analytics/handlers/apply_aggregation.rb +35 -0
- data/lib/wonkavision/plugins/analytics/handlers/split_by_aggregation.rb +60 -0
- data/lib/wonkavision/plugins/analytics/member_filter.rb +106 -0
- data/lib/wonkavision/plugins/analytics/mongo.rb +6 -0
- data/lib/wonkavision/plugins/analytics/persistence/hash_store.rb +59 -0
- data/lib/wonkavision/plugins/analytics/persistence/mongo_store.rb +85 -0
- data/lib/wonkavision/plugins/analytics/persistence/store.rb +105 -0
- data/lib/wonkavision/plugins/analytics/query.rb +76 -0
- data/lib/wonkavision/plugins/event_handling.rb +15 -3
- data/lib/wonkavision/version.rb +1 -1
- data/test/aggregation_spec_test.rb +99 -0
- data/test/aggregation_test.rb +170 -0
- data/test/analytics/test_aggregation.rb +78 -0
- data/test/apply_aggregation_test.rb +92 -0
- data/test/attribute_test.rb +26 -0
- data/test/cellset_test.rb +200 -0
- data/test/dimension_test.rb +186 -0
- data/test/facts_test.rb +146 -0
- data/test/hash_store_test.rb +112 -0
- data/test/log/test.log +96844 -0
- data/test/map_test.rb +48 -1
- data/test/measure_test.rb +146 -0
- data/test/member_filter_test.rb +143 -0
- data/test/mongo_store_test.rb +115 -0
- data/test/query_test.rb +106 -0
- data/test/split_by_aggregation_test.rb +114 -0
- data/test/store_test.rb +71 -0
- data/test/symbol_test.rb +62 -0
- data/test/test_activity_models.rb +1 -1
- data/test/test_aggregation.rb +42 -0
- data/test/test_data.tuples +100 -0
- data/test/test_helper.rb +7 -0
- metadata +57 -5
data/test/query_test.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class QueryTest < ActiveSupport::TestCase
|
4
|
+
Query = Wonkavision::Analytics::Query
|
5
|
+
|
6
|
+
context "Query" do
|
7
|
+
setup do
|
8
|
+
@query = Query.new
|
9
|
+
end
|
10
|
+
|
11
|
+
context "Class methods" do
|
12
|
+
context "#axis_ordinal" do
|
13
|
+
should "convert nil or empty string to axis zero" do
|
14
|
+
assert_equal 0, Query.axis_ordinal(nil)
|
15
|
+
assert_equal 0, Query.axis_ordinal("")
|
16
|
+
end
|
17
|
+
should "convert string integers into real integers" do
|
18
|
+
assert_equal 3, Query.axis_ordinal("3")
|
19
|
+
end
|
20
|
+
should "correctly interpret named axes" do
|
21
|
+
["Columns", :rows, :PAGES, "chapters", "SECTIONS"].each_with_index do |item,idx|
|
22
|
+
assert_equal idx, Query.axis_ordinal(item)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "#select" do
|
30
|
+
should "associate dimensions with the default axis (columns)" do
|
31
|
+
@query.select :hi, :there
|
32
|
+
assert_equal [:hi,:there], @query.axes[0]
|
33
|
+
end
|
34
|
+
should "associate dimensions with the specified axis" do
|
35
|
+
@query.select :hi, :there, :on => :rows
|
36
|
+
assert_equal [:hi, :there], @query.axes[1]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "#selected_dimensions" do
|
41
|
+
should "collect dimensions from each axis" do
|
42
|
+
@query.select :c, :d; @query.select :b, :a, :on => :rows
|
43
|
+
assert_equal [:c,:d,:b,:a], @query.selected_dimensions
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "#referenced_dimensions" do
|
48
|
+
should "match selected dimensions with no dimension filters" do
|
49
|
+
@query.select :c, :d
|
50
|
+
assert_equal @query.selected_dimensions, @query.referenced_dimensions
|
51
|
+
end
|
52
|
+
should "include filter dimensions in the presence of a dimension filter"do
|
53
|
+
@query.select(:c,:d).where(:e=>:f)
|
54
|
+
assert_equal 3, @query.referenced_dimensions.length
|
55
|
+
assert_equal [], @query.referenced_dimensions - [:c, :d, :e]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "#matches_filter?" do
|
60
|
+
should "return true if all filters are applied" do
|
61
|
+
@query.expects(:all_filters_applied?).returns(true)
|
62
|
+
assert @query.matches_filter?(nil,nil)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context "#where" do
|
67
|
+
should "convert a symbol to a MemberFilter" do
|
68
|
+
@query.where :a=>:b
|
69
|
+
assert @query.filters[0].kind_of?(Wonkavision::Analytics::MemberFilter)
|
70
|
+
end
|
71
|
+
|
72
|
+
should "append filters to the filters array" do
|
73
|
+
@query.where :a=>:b, :c=>:d
|
74
|
+
assert_equal 2, @query.filters.length
|
75
|
+
end
|
76
|
+
|
77
|
+
should "set the member filters value from the hash" do
|
78
|
+
@query.where :a=>:b
|
79
|
+
assert_equal :b, @query.filters[0].value
|
80
|
+
end
|
81
|
+
|
82
|
+
should "add dimension names to the slicer" do
|
83
|
+
@query.where :dimensions.a => :b
|
84
|
+
assert @query.slicer.include?(:a)
|
85
|
+
end
|
86
|
+
|
87
|
+
should "not add measure names to the slicer" do
|
88
|
+
@query.where :measures.a => :b
|
89
|
+
assert @query.slicer.include?(:a) == false
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
context "#slicer" do
|
95
|
+
setup do
|
96
|
+
@query.select :a, :b
|
97
|
+
@query.where :dimensions.b=>:b, :dimensions.c=>:c
|
98
|
+
end
|
99
|
+
should "include only dimensions not on another axis" do
|
100
|
+
assert_equal [:c], @query.slicer
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class SplitByAggregationTest < ActiveSupport::TestCase
|
4
|
+
context "SplitByAggregation" do
|
5
|
+
setup do
|
6
|
+
@agg = Class.new
|
7
|
+
@agg.class_eval do
|
8
|
+
def self.name; "MyAggregation" end
|
9
|
+
include Wonkavision::Aggregation
|
10
|
+
dimension :a, :b, :c
|
11
|
+
measure :d, :e
|
12
|
+
aggregate_by :a, :b
|
13
|
+
aggregate_by :a, :b, :c
|
14
|
+
store :hash_store
|
15
|
+
end
|
16
|
+
@handler = Wonkavision::Analytics::SplitByAggregation.new
|
17
|
+
end
|
18
|
+
|
19
|
+
should "initialize with the appropriate namespace" do
|
20
|
+
assert_equal Wonkavision.join("wv", "analytics"), @handler.class.event_namespace
|
21
|
+
end
|
22
|
+
|
23
|
+
context "#aggregation_for" do
|
24
|
+
should "look up an aggregation for the provided name" do
|
25
|
+
assert_equal @agg, @handler.aggregation_for(@agg.name)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "#split_dimensions_by_aggregation" do
|
30
|
+
setup do
|
31
|
+
@entity = {"a" => :a, "b" => :b, "c" => :c, "d" => :d, "e" => :e}
|
32
|
+
@split = @handler.split_dimensions_by_aggregation(@agg,@entity)
|
33
|
+
end
|
34
|
+
should "create one entry per aggregate_by" do
|
35
|
+
assert_equal 2, @split.length
|
36
|
+
end
|
37
|
+
should "create a hash of key values for each aggregation" do
|
38
|
+
assert_equal( { "a" => { "a" => :a}, "b" => { "b"=>:b} }, @split[0] )
|
39
|
+
assert_equal( { "a" => { "a" => :a}, "b" => { "b"=>:b} , "c"=>{ "c"=>:c} }, @split[1] )
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "#process_aggregations" do
|
44
|
+
should "call process on each message in the batch" do
|
45
|
+
path = Wonkavision.join("wv", "analytics", "aggregation", "updated")
|
46
|
+
@handler.expects(:submit).with(path, { :hi => "there"})
|
47
|
+
@handler.process_aggregations [{ :hi => "there"}]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "#process_event" do
|
52
|
+
should "return false unless all appropriate metadata is present and valid" do
|
53
|
+
assert_equal false, @handler.process_event({"aggregation"=>"ack",
|
54
|
+
"action"=>"add","data"=>{}})
|
55
|
+
|
56
|
+
assert_equal false, @handler.process_event( { "aggregation"=>@agg.name,
|
57
|
+
"action"=>"add"})
|
58
|
+
|
59
|
+
assert_equal false, @handler.process_event( { "aggregation"=>@agg.name,
|
60
|
+
"data"=>{}})
|
61
|
+
end
|
62
|
+
context "with a valid message" do
|
63
|
+
setup do
|
64
|
+
@message = {
|
65
|
+
"aggregation" => @agg.name,
|
66
|
+
"action" => "add",
|
67
|
+
"data" => {
|
68
|
+
"a" => :a, "b" => :b, "c" => :c,
|
69
|
+
"d" => 1.0, "e" => 2.0
|
70
|
+
}
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
should "prepare a message for each aggregation" do
|
75
|
+
assert_equal 2, @handler.process_event(@message).length
|
76
|
+
end
|
77
|
+
|
78
|
+
should "submit each message for processing" do
|
79
|
+
@handler.expects(:submit).times(2)
|
80
|
+
@handler.process_event(@message)
|
81
|
+
end
|
82
|
+
|
83
|
+
should "not submit messages if the filter doesn't match" do
|
84
|
+
@agg.filter { |m|m["a"] != :a}
|
85
|
+
assert_equal 0, @handler.process_event(@message).length
|
86
|
+
end
|
87
|
+
|
88
|
+
should "copy the measures once for each aggregation" do
|
89
|
+
results = @handler.process_event(@message)
|
90
|
+
results.each do |result|
|
91
|
+
assert_equal "add", result["action"]
|
92
|
+
assert_equal @agg.name, result["aggregation"]
|
93
|
+
assert_equal( { "d" => 1.0, "e" => 2.0} , result["measures"] )
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
should "key each message with a unique aggregation" do
|
98
|
+
results = @handler.process_event(@message)
|
99
|
+
results[0][:dimensions] = { "a" => :a, "b" => :b}
|
100
|
+
results[1][:dimensions] = { "a" => :a, "b" => :b, "c" => :c}
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
context "will listen for entity updated messages" do
|
105
|
+
should "respond to entity updated messages" do
|
106
|
+
Wonkavision::Analytics::SplitByAggregation.any_instance.expects(:process_event)
|
107
|
+
Wonkavision.event_coordinator.receive_event("wv/analytics/facts/updated",{ :a=>:b})
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
data/test/store_test.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class StoreTest < ActiveSupport::TestCase
|
4
|
+
Store = Wonkavision::Analytics::Persistence::Store
|
5
|
+
|
6
|
+
context "Store" do
|
7
|
+
setup do
|
8
|
+
@facts = Class.new
|
9
|
+
@facts.class_eval do
|
10
|
+
include Wonkavision::Facts
|
11
|
+
record_id :tada
|
12
|
+
end
|
13
|
+
@store = Store.new(@facts)
|
14
|
+
end
|
15
|
+
|
16
|
+
should "provide access to the underlying owner" do
|
17
|
+
assert_equal @facts, @store.owner
|
18
|
+
end
|
19
|
+
|
20
|
+
should "be able to extract a record_id from a message" do
|
21
|
+
assert_equal 123, @store.send(:assert_record_id,{ "tada" => 123 })
|
22
|
+
end
|
23
|
+
|
24
|
+
should "raise an exception if a record_id is requested but not found" do
|
25
|
+
assert_raise(RuntimeError) { @store.send(:assert_record_id,{ "haha" => 123})}
|
26
|
+
end
|
27
|
+
|
28
|
+
context "Public api" do
|
29
|
+
context "#update_facts" do
|
30
|
+
should "extract a record_id and delegate to update_facts_record" do
|
31
|
+
@store.expects(:update_facts_record).with(123,{ "tada"=>123})
|
32
|
+
@store.update_facts("tada"=>123)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
context "#add_facts" do
|
36
|
+
should "extract a record_id and delegate to insert_facts_record" do
|
37
|
+
@store.expects(:insert_facts_record).with(123,{ "tada" => 123})
|
38
|
+
@store.add_facts("tada"=>123)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
context "#remove_facts" do
|
42
|
+
should "extract a record_id and delegate to delete_facts_record" do
|
43
|
+
@store.expects(:delete_facts_record).with(123,{ "tada" => 123} )
|
44
|
+
@store.remove_facts("tada"=>123)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
context "#execute_query" do
|
48
|
+
setup do
|
49
|
+
@query = Wonkavision::Analytics::Query.new
|
50
|
+
@query.select :a, :b, :on => :columns
|
51
|
+
@query.select :c, :on => :rows
|
52
|
+
@query.where :d=>:e
|
53
|
+
end
|
54
|
+
should "delegate to fetch tuples, passing the selected dimensions" do
|
55
|
+
@store.expects(:fetch_tuples).with([:a,:b,:c, :d],@query.filters)
|
56
|
+
@store.execute_query(@query)
|
57
|
+
end
|
58
|
+
should "pass an empty array of dimensions when nothing is selected" do
|
59
|
+
@store.expects(:fetch_tuples).with([],[])
|
60
|
+
@store.execute_query(Wonkavision::Analytics::Query.new)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
context "Deriving from Store" do
|
64
|
+
should "register the derived class with the superclass" do
|
65
|
+
class NewStore < Store; end
|
66
|
+
assert_equal NewStore, Store[:new_store]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/test/symbol_test.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class SymbolTest < ActiveSupport::TestCase
|
4
|
+
context "Symbol extensions" do
|
5
|
+
setup do
|
6
|
+
@symbol = :member
|
7
|
+
end
|
8
|
+
context "#key, #caption and #sort" do
|
9
|
+
should "produce MemberFilters of type dimension" do
|
10
|
+
[:key,:caption,:sort].each do |method|
|
11
|
+
filter = @symbol.send(method)
|
12
|
+
assert_equal @symbol, filter.name
|
13
|
+
assert_equal :dimension, filter.member_type
|
14
|
+
assert_equal method, filter.attribute_name
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
context "#sum, #sum2, #count" do
|
19
|
+
should "produce MemberFilters of type measure" do
|
20
|
+
[:sum,:sum2,:count].each do |method|
|
21
|
+
filter = @symbol.send(method)
|
22
|
+
assert_equal @symbol, filter.name
|
23
|
+
assert_equal :measure, filter.member_type
|
24
|
+
assert_equal method, filter.attribute_name
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "#[]" do
|
30
|
+
should "produce a MemberFilter with the attribute name specified by the indexer" do
|
31
|
+
filter = @symbol[:an_attribute]
|
32
|
+
assert_equal @symbol, filter.name
|
33
|
+
assert_equal :an_attribute, filter.attribute_name
|
34
|
+
end
|
35
|
+
end
|
36
|
+
context "method_missing" do
|
37
|
+
should "produce a MemberFilter with an attribute name of the method called" do
|
38
|
+
filter = @symbol.an_attribute
|
39
|
+
assert_equal @symbol, filter.name
|
40
|
+
assert_equal :an_attribute, filter.attribute_name
|
41
|
+
end
|
42
|
+
end
|
43
|
+
context "when the symbol is ':dimensions'" do
|
44
|
+
should "produce a MemberFilter with a dimension name as specified and a default attribute name" do
|
45
|
+
filter = :dimensions.a_dimension
|
46
|
+
assert_equal :a_dimension, filter.name
|
47
|
+
assert_equal :key, filter.attribute_name
|
48
|
+
assert_equal :dimension, filter.member_type
|
49
|
+
end
|
50
|
+
end
|
51
|
+
context "when the symbol is ':measures'" do
|
52
|
+
should "produce a MemberFilter with a measure name as specified and a default attribute name" do
|
53
|
+
filter = :measures.a_measure
|
54
|
+
assert_equal :a_measure, filter.name
|
55
|
+
assert_equal :count, filter.attribute_name
|
56
|
+
assert_equal :measure, filter.member_type
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class TestAggregation
|
2
|
+
include Wonkavision::Aggregation
|
3
|
+
|
4
|
+
store :hash_store
|
5
|
+
|
6
|
+
dimension :color, :size, :shape
|
7
|
+
measure :weight, :cost
|
8
|
+
|
9
|
+
aggregate_by :color
|
10
|
+
aggregate_by :size
|
11
|
+
aggregate_by :shape
|
12
|
+
aggregate_by :color, :size
|
13
|
+
aggregate_by :color, :shape
|
14
|
+
aggregate_by :size, :shape
|
15
|
+
aggregate_by :size, :color, :shape
|
16
|
+
|
17
|
+
@i = 0
|
18
|
+
def self.send_messages
|
19
|
+
colors = %w(red green red green yellow black black white red yellow)
|
20
|
+
sizes = %w(large large small medium medium small large small medium small)
|
21
|
+
shapes = %w(square circle rectangle rectangle circle square circle rectangle square square)
|
22
|
+
weights = [1.0, 2.0, 1.1, 2.1, 1.2, 2.2, 1.3, 2.3,4.5,6.5]
|
23
|
+
costs = [5, 10, 15, 20, 15, 20, 5, 8, 9, 20]
|
24
|
+
|
25
|
+
(0..9).each do |idx|
|
26
|
+
Wonkavision.event_coordinator.submit_job "wv/analytics/entity/updated", {
|
27
|
+
"aggregation" => "TestAggregation",
|
28
|
+
"action" => "add",
|
29
|
+
"entity" => {
|
30
|
+
"color" => colors[idx],
|
31
|
+
"size" => sizes[idx],
|
32
|
+
"shape" => shapes[idx],
|
33
|
+
"weight" => weights[idx],
|
34
|
+
"cost" => costs[idx]
|
35
|
+
}
|
36
|
+
}
|
37
|
+
@i+=1
|
38
|
+
print @i % 100 == 0 ? @i : "."
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
[{"_id"=>BSON::ObjectId('4d470fb0ca248815e73bd89a'),
|
2
|
+
"dimension_keys"=>["red", "square", "large"],
|
3
|
+
"dimension_names"=>["color", "shape", "size"],
|
4
|
+
"dimensions"=>
|
5
|
+
{"size"=>{"size"=>"large"},
|
6
|
+
"shape"=>{"shape"=>"square"},
|
7
|
+
"color"=>{"color"=>"red"}},
|
8
|
+
"measures"=>
|
9
|
+
{"cost"=>{"count"=>10, "sum"=>50, "sum2"=>250},
|
10
|
+
"weight"=>{"count"=>10, "sum"=>10.0, "sum2"=>10.0}}},
|
11
|
+
{"_id"=>BSON::ObjectId('4d470fb0ca248815e73bd8a0'),
|
12
|
+
"dimension_keys"=>["green", "circle", "large"],
|
13
|
+
"dimension_names"=>["color", "shape", "size"],
|
14
|
+
"dimensions"=>
|
15
|
+
{"size"=>{"size"=>"large"},
|
16
|
+
"shape"=>{"shape"=>"circle"},
|
17
|
+
"color"=>{"color"=>"green"}},
|
18
|
+
"measures"=>
|
19
|
+
{"cost"=>{"count"=>10, "sum"=>100, "sum2"=>1000},
|
20
|
+
"weight"=>{"count"=>10, "sum"=>20.0, "sum2"=>40.0}}},
|
21
|
+
{"_id"=>BSON::ObjectId('4d470fb0ca248815e73bd8a6'),
|
22
|
+
"dimension_keys"=>["red", "rectangle", "small"],
|
23
|
+
"dimension_names"=>["color", "shape", "size"],
|
24
|
+
"dimensions"=>
|
25
|
+
{"size"=>{"size"=>"small"},
|
26
|
+
"shape"=>{"shape"=>"rectangle"},
|
27
|
+
"color"=>{"color"=>"red"}},
|
28
|
+
"measures"=>
|
29
|
+
{"cost"=>{"count"=>10, "sum"=>150, "sum2"=>2250},
|
30
|
+
"weight"=>{"count"=>10, "sum"=>11.0, "sum2"=>12.1}}},
|
31
|
+
{"_id"=>BSON::ObjectId('4d470fb0ca248815e73bd8ab'),
|
32
|
+
"dimension_keys"=>["green", "rectangle", "medium"],
|
33
|
+
"dimension_names"=>["color", "shape", "size"],
|
34
|
+
"dimensions"=>
|
35
|
+
{"size"=>{"size"=>"medium"},
|
36
|
+
"shape"=>{"shape"=>"rectangle"},
|
37
|
+
"color"=>{"color"=>"green"}},
|
38
|
+
"measures"=>
|
39
|
+
{"cost"=>{"count"=>10, "sum"=>200, "sum2"=>4000},
|
40
|
+
"weight"=>{"count"=>10, "sum"=>21.0, "sum2"=>44.1}}},
|
41
|
+
{"_id"=>BSON::ObjectId('4d470fb0ca248815e73bd8b0'),
|
42
|
+
"dimension_keys"=>["yellow", "circle", "medium"],
|
43
|
+
"dimension_names"=>["color", "shape", "size"],
|
44
|
+
"dimensions"=>
|
45
|
+
{"size"=>{"size"=>"medium"},
|
46
|
+
"shape"=>{"shape"=>"circle"},
|
47
|
+
"color"=>{"color"=>"yellow"}},
|
48
|
+
"measures"=>
|
49
|
+
{"cost"=>{"count"=>10, "sum"=>150, "sum2"=>2250},
|
50
|
+
"weight"=>{"count"=>10, "sum"=>12.0, "sum2"=>14.4}}},
|
51
|
+
{"_id"=>BSON::ObjectId('4d470fb0ca248815e73bd8b5'),
|
52
|
+
"dimension_keys"=>["black", "square", "small"],
|
53
|
+
"dimension_names"=>["color", "shape", "size"],
|
54
|
+
"dimensions"=>
|
55
|
+
{"size"=>{"size"=>"small"},
|
56
|
+
"shape"=>{"shape"=>"square"},
|
57
|
+
"color"=>{"color"=>"black"}},
|
58
|
+
"measures"=>
|
59
|
+
{"cost"=>{"count"=>10, "sum"=>200, "sum2"=>4000},
|
60
|
+
"weight"=>{"count"=>10, "sum"=>22.0, "sum2"=>48.4}}},
|
61
|
+
{"_id"=>BSON::ObjectId('4d470fb0ca248815e73bd8b8'),
|
62
|
+
"dimension_keys"=>["black", "circle", "large"],
|
63
|
+
"dimension_names"=>["color", "shape", "size"],
|
64
|
+
"dimensions"=>
|
65
|
+
{"size"=>{"size"=>"large"},
|
66
|
+
"shape"=>{"shape"=>"circle"},
|
67
|
+
"color"=>{"color"=>"black"}},
|
68
|
+
"measures"=>
|
69
|
+
{"cost"=>{"count"=>10, "sum"=>50, "sum2"=>250},
|
70
|
+
"weight"=>{"count"=>10, "sum"=>13.0, "sum2"=>16.9}}},
|
71
|
+
{"_id"=>BSON::ObjectId('4d470fb0ca248815e73bd8bc'),
|
72
|
+
"dimension_keys"=>["white", "rectangle", "small"],
|
73
|
+
"dimension_names"=>["color", "shape", "size"],
|
74
|
+
"dimensions"=>
|
75
|
+
{"size"=>{"size"=>"small"},
|
76
|
+
"shape"=>{"shape"=>"rectangle"},
|
77
|
+
"color"=>{"color"=>"white"}},
|
78
|
+
"measures"=>
|
79
|
+
{"cost"=>{"count"=>10, "sum"=>80, "sum2"=>640},
|
80
|
+
"weight"=>{"count"=>10, "sum"=>23.0, "sum2"=>52.9}}},
|
81
|
+
{"_id"=>BSON::ObjectId('4d470fb0ca248815e73bd8bf'),
|
82
|
+
"dimension_keys"=>["red", "square", "medium"],
|
83
|
+
"dimension_names"=>["color", "shape", "size"],
|
84
|
+
"dimensions"=>
|
85
|
+
{"size"=>{"size"=>"medium"},
|
86
|
+
"shape"=>{"shape"=>"square"},
|
87
|
+
"color"=>{"color"=>"red"}},
|
88
|
+
"measures"=>
|
89
|
+
{"cost"=>{"count"=>10, "sum"=>90, "sum2"=>810},
|
90
|
+
"weight"=>{"count"=>10, "sum"=>45.0, "sum2"=>202.5}}},
|
91
|
+
{"_id"=>BSON::ObjectId('4d470fb0ca248815e73bd8c2'),
|
92
|
+
"dimension_keys"=>["yellow", "square", "small"],
|
93
|
+
"dimension_names"=>["color", "shape", "size"],
|
94
|
+
"dimensions"=>
|
95
|
+
{"size"=>{"size"=>"small"},
|
96
|
+
"shape"=>{"shape"=>"square"},
|
97
|
+
"color"=>{"color"=>"yellow"}},
|
98
|
+
"measures"=>
|
99
|
+
{"cost"=>{"count"=>10, "sum"=>200, "sum2"=>4000},
|
100
|
+
"weight"=>{"count"=>10, "sum"=>65.0, "sum2"=>422.5}}}]
|