wonkavision 0.5.11 → 0.6.0
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 +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}}}]
|