mongoid-report 0.1.9 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,122 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mongoid::Report do
4
+ let(:klass) { Model }
5
+
6
+ it 'should merge properly results on splitted requests' do
7
+ ########## 1. Making the first report and out to the defined collection name.
8
+ report_klass = Class.new do
9
+ include Mongoid::Report
10
+
11
+ report 'example' do
12
+ attach_to Model do
13
+ group_by :field1
14
+ batches pool_size: 2
15
+ column :field1, :field2
16
+ end
17
+ end
18
+ end
19
+
20
+ klass.create!(day: 0.days.ago, field1: 1, field2: 1)
21
+ klass.create!(day: 1.days.ago, field1: 1, field2: 1)
22
+ klass.create!(day: 1.days.ago, field1: 2, field2: 2)
23
+ klass.create!(day: 2.days.ago, field1: 3, field2: 3)
24
+ klass.create!(day: 3.days.ago, field1: 1, field2: 1)
25
+ klass.create!(day: 4.days.ago, field1: 1, field2: 1)
26
+
27
+ report = report_klass.new
28
+
29
+ scoped = report.aggregate_for('example', 'models')
30
+ scoped = scoped
31
+ .in_batches(day: (5.days.ago.to_date..0.days.from_now.to_date))
32
+ .out('stored-report')
33
+ .all
34
+
35
+ values = scoped.rows.map {|row| row['field2']}
36
+ expect(values.size).to eq(3)
37
+ expect(values).to include(4)
38
+ expect(values).to include(2)
39
+ expect(values).to include(3)
40
+
41
+ stored_report_klass = Class.new do
42
+ include Mongoid::Document
43
+ store_in collection: 'stored-report'
44
+ end
45
+
46
+ out = stored_report_klass.all
47
+ expect(out.count).to eq(3)
48
+ values = out.map { |o| o['field2'] }
49
+ expect(values).to include(4)
50
+ expect(values).to include(2)
51
+ expect(values).to include(3)
52
+
53
+ ########## 2. Making the second report and out to the defined collection name with new data.
54
+ klass.create!(day: 3.days.ago, field1: 1, field2: 1)
55
+
56
+ scoped = report.aggregate_for('example', 'models')
57
+ scoped = scoped
58
+ .in_batches(day: (5.days.ago.to_date..0.days.from_now.to_date))
59
+ .out('stored-report')
60
+ .all
61
+
62
+ values = scoped.rows.map {|row| row['field2']}
63
+ expect(values.size).to eq(3)
64
+ expect(values).to include(5)
65
+ expect(values).to include(2)
66
+ expect(values).to include(3)
67
+
68
+ out = stored_report_klass.all
69
+ expect(out.count).to eq(3)
70
+ values = out.map { |o| o['field2'] }
71
+ expect(values).to include(5)
72
+ expect(values).to include(2)
73
+ expect(values).to include(3)
74
+ end
75
+
76
+ it 'should leave data out of date range' do
77
+ report_klass = Class.new do
78
+ include Mongoid::Report
79
+
80
+ report 'example' do
81
+ attach_to Model do
82
+ group_by :day
83
+ batches pool_size: 2
84
+ column :field1, :day
85
+ end
86
+ end
87
+ end
88
+
89
+ stored_report_klass = Class.new do
90
+ include Mongoid::Document
91
+ store_in collection: 'stored-report'
92
+ end
93
+
94
+ klass.create!(day: 0.days.ago, field1: 1)
95
+ klass.create!(day: 1.days.ago, field1: 1)
96
+ klass.create!(day: 1.days.ago, field1: 1)
97
+ klass.create!(day: 2.days.ago, field1: 1)
98
+ klass.create!(day: 3.days.ago, field1: 1)
99
+ klass.create!(day: 4.days.ago, field1: 1)
100
+
101
+ report = report_klass.new
102
+ scoped = report.aggregate_for('example', 'models')
103
+ scoped = scoped
104
+ .in_batches(day: (1.days.ago.to_date..0.days.from_now.to_date))
105
+ .out('stored-report', drop: { 'day' => { '$gte' => 1.days.ago.to_date.mongoize, '$lte' => 0.days.from_now.to_date.mongoize } })
106
+ .all
107
+
108
+ scoped = report.aggregate_for('example', 'models')
109
+ scoped = scoped
110
+ .in_batches(day: (5.days.ago.to_date..1.days.ago.to_date))
111
+ .out('stored-report', drop: { 'day' => { '$gte' => 5.days.ago.to_date.mongoize, '$lte' => 1.days.ago.to_date.mongoize } })
112
+ .all
113
+
114
+ out = stored_report_klass.all
115
+ expect(out.count).to eq(5)
116
+
117
+ days = stored_report_klass.distinct('day')
118
+ 4.times do |i|
119
+ expect(days).to include(i.days.ago.to_date)
120
+ end
121
+ end
122
+ end
@@ -4,13 +4,15 @@ describe Mongoid::Report::QueriesBuilder do
4
4
 
5
5
  describe '.queries' do
6
6
  it 'builds queries for aggregation' do
7
- Report = Class.new do
7
+ report_klass = Class.new do
8
8
  include Mongoid::Report
9
- column :field1, for: Model
9
+ def self.name ; 'report-klass' ; end
10
+
11
+ column :field1, collection: Model
10
12
  end
11
- report = Report.new
13
+ report = report_klass.new
12
14
 
13
- queries = report.queries(Model)
15
+ queries = report.queries('report-klass', 'models')
14
16
  expect(queries.size).to eq(3)
15
17
  expect(queries[0]).to eq(
16
18
  '$project' => {
@@ -30,17 +32,18 @@ describe Mongoid::Report::QueriesBuilder do
30
32
  end
31
33
 
32
34
  it 'builds queries using custom one group' do
33
- Report = Class.new do
35
+ report_klass = Class.new do
34
36
  include Mongoid::Report
37
+ def self.name ; 'report-klass' ; end
35
38
 
36
39
  attach_to Model do
37
40
  group_by :day
38
41
  column :field1
39
42
  end
40
43
  end
41
- report = Report.new
44
+ report = report_klass.new
42
45
 
43
- queries = report.queries(Model)
46
+ queries = report.queries('report-klass', 'models')
44
47
  expect(queries.size).to eq(3)
45
48
  expect(queries[0]).to eq(
46
49
  '$project' => {
@@ -61,18 +64,18 @@ describe Mongoid::Report::QueriesBuilder do
61
64
  })
62
65
  end
63
66
 
64
- class Report5
65
- include Mongoid::Report
66
-
67
- attach_to Model do
68
- group_by :day, :field2
67
+ it 'builds queries using custom one group' do
68
+ report_klass = Class.new do
69
+ include Mongoid::Report
70
+ def self.name ; 'report-klass' ; end
69
71
 
70
- column :field1, :field3
72
+ attach_to Model do
73
+ group_by :day, :field2
74
+ column :field2, :field1, :field3
75
+ end
71
76
  end
72
- end
73
77
 
74
- it 'builds queries using custom one group' do
75
- queries = Report5.new.queries(Model)
78
+ queries = report_klass.new.queries('report-klass', 'models')
76
79
  expect(queries.size).to eq(3)
77
80
  expect(queries[0]).to eq(
78
81
  '$project' => {
@@ -99,4 +102,39 @@ describe Mongoid::Report::QueriesBuilder do
99
102
  end
100
103
  end
101
104
 
105
+ it 'allows to pass raw query' do
106
+ report_klass = Class.new do
107
+ include Mongoid::Report
108
+
109
+ report 'example' do
110
+ attach_to 'models' do
111
+ query '$match' => { 'field1' => 1 }
112
+ match 'field2' => 2
113
+ match 'field3' => ->(report) { 3 }
114
+ match '$or' => ->(this) {
115
+ [
116
+ { 'field1' => 1 },
117
+ { 'field2' => 2 },
118
+ ]
119
+ }
120
+ column :field1, :field2
121
+ end
122
+ end
123
+ end
124
+
125
+ Model.create(field1: 1, field2: 2, field3: 1)
126
+ Model.create(field1: 2, field2: 2, field3: 1)
127
+ Model.create(field1: 1, field2: 2, field3: 3)
128
+
129
+ report = report_klass.new
130
+ queries = report.report_module_settings['example'][:reports]['models'][:queries]
131
+ expect(queries).to include('$match' => { 'field1' => 1 })
132
+ expect(queries).to include('$match' => { 'field2' => 2 })
133
+
134
+ scope = report.aggregate_for('example', 'models').all
135
+ expect(scope.rows.size).to eq(1)
136
+ expect(scope.rows[0]['field1']).to eq(1)
137
+ expect(scope.rows[0]['field2']).to eq(2)
138
+ end # it
139
+
102
140
  end
@@ -6,7 +6,7 @@ describe Mongoid::Report do
6
6
  it 'allows to save options per report and attached model' do
7
7
  2.times { klass.create!(field1: 1) }
8
8
 
9
- Report = Class.new do
9
+ report_klass = Class.new do
10
10
  include Mongoid::Report
11
11
 
12
12
  report 'example' do
@@ -19,8 +19,8 @@ describe Mongoid::Report do
19
19
  end
20
20
  end
21
21
 
22
- report = Report.new
23
- report = report.aggregate_for('example-models')
22
+ report = report_klass.new
23
+ report = report.aggregate_for('example', 'models')
24
24
  report = report.all
25
25
 
26
26
  rows = report.rows
@@ -12,14 +12,16 @@ describe Mongoid::Report do
12
12
  klass.create!(day: today , field1: 1)
13
13
  klass.create!(day: yesterday , field1: 1)
14
14
 
15
- Report = Class.new do
15
+ report_klass = Class.new do
16
16
  include Mongoid::Report
17
- group_by :day, for: Model
18
- column :field1, for: Model
17
+ def self.name ; 'report-klass' ; end
18
+
19
+ group_by :day, collection: Model
20
+ column :field1, collection: Model
19
21
  end
20
- example = Report.new
22
+ example = report_klass.new
21
23
 
22
- report = example.aggregate_for(klass)
24
+ report = example.aggregate_for('report-klass', 'models')
23
25
  report = report.all
24
26
  rows = report.rows
25
27
 
@@ -28,39 +30,108 @@ describe Mongoid::Report do
28
30
  end
29
31
 
30
32
  it 'should support dynamic columns as well' do
31
- Report = Class.new do
33
+ report_klass = Class.new do
32
34
  include Mongoid::Report
33
35
 
34
36
  COLUMNS = {
35
37
  :'new-field1' => ->(context, row, options) { row['field1'] * 10 },
36
- :'new-field2' => ->(context, row, options) { row['field1'] * 20 },
38
+ :'new-field2' => ->(context, row, options) { row['field2'] * 20 },
37
39
  }
38
40
 
39
41
  report 'example' do
40
42
  attach_to Model do
41
43
  columns COLUMNS
42
- column :field1, 'new-field1'
44
+ column :field1, :field2, 'new-field1', 'new-field2'
45
+ end
46
+ end
47
+ end
48
+
49
+ klass.create!(field1: 1, field2: 3)
50
+ klass.create!(field1: 2, field2: 2)
51
+ klass.create!(field1: 3, field2: 1)
52
+
53
+ report = report_klass.new
54
+ report = report.aggregate_for('example', 'models')
55
+ report = report.all
56
+ rows = report.rows
57
+
58
+ expect(rows[0].keys.size).to eq(4)
59
+ expect(rows[0]['field1']).to eq(6)
60
+ expect(rows[0]['field2']).to eq(6)
61
+ expect(rows[0]['new-field1']).to eq(60)
62
+ expect(rows[0]['new-field2']).to eq(120)
63
+
64
+ expect(report.summary.keys.size).to eq(4)
65
+ expect(report.summary['field1']).to eq(6)
66
+ expect(report.summary['field2']).to eq(6)
67
+ expect(report.summary['new-field1']).to eq(60)
68
+ expect(report.summary['new-field2']).to eq(120)
69
+ end
70
+
71
+ it 'should not summaries day field' do
72
+ report_klass = Class.new do
73
+ include Mongoid::Report
74
+
75
+ report 'example' do
76
+ attach_to Model do
77
+ group_by :day
78
+ column :day, :field1
43
79
  end
44
80
  end
45
81
  end
46
82
 
47
- klass.create!(field1: 1)
48
- klass.create!(field1: 1)
49
- klass.create!(field1: 1)
83
+ klass.create!(day: DateTime.now, field1: 1)
84
+ klass.create!(day: DateTime.now, field1: 1)
85
+ klass.create!(day: DateTime.now, field1: 1)
50
86
 
51
- report = Report.new
52
- report = report.aggregate_for('example-models')
87
+ report = report_klass.new
88
+ report = report.aggregate_for('example', 'models')
53
89
  report = report.all
54
90
  rows = report.rows
55
91
 
56
92
  expect(rows[0].keys.size).to eq(2)
57
93
  expect(rows[0]['field1']).to eq(3)
58
- expect(rows[0]['new-field1']).to eq(30)
94
+ expect(rows[0]['day']).to be
59
95
 
60
- expect(report.summary.keys.size).to eq(2)
96
+ expect(report.summary.keys.size).to eq(1)
61
97
  expect(report.summary['field1']).to eq(3)
62
- expect(report.summary['new-field1']).to eq(30)
63
98
  end
99
+
100
+ it 'should calculate dynamic columns for summary' do
101
+ report_klass = Class.new do
102
+ include Mongoid::Report
103
+
104
+ COLUMNS = {
105
+ :'new-field1' => ->(context, row, options) { row['field2'] * 10 / row['field1'] * 1.2 },
106
+ :'new-field2' => ->(context, row, options) { row['field2'] * 20.0 * row['field1'] / 100 },
107
+ }
108
+
109
+ report 'example' do
110
+ attach_to Model do
111
+ columns COLUMNS
112
+ group_by :day
113
+ column :day, :field1, :field2, 'new-field1', 'new-field2'
114
+ end
115
+ end
116
+ end
117
+
118
+ klass.create!(day: 1.day.ago, field1: 1, field2: 3)
119
+ klass.create!(day: 2.day.ago, field1: 2, field2: 0)
120
+ klass.create!(day: 3.day.ago, field1: 3, field2: 1)
121
+ klass.create!(day: 4.day.ago, field1: 4, field2: 0)
122
+
123
+ report = report_klass.new
124
+ report = report.aggregate_for('example', 'models')
125
+ report = report.all
126
+ rows = report.rows
127
+
128
+ expect(report.summary.keys.size).to eq(4)
129
+ expect(report.summary['field1']).to eq(10)
130
+ expect(report.summary['field2']).to eq(4)
131
+ expect(report.summary['new-field1']).to eq(4.8)
132
+ expect(report.summary['new-field2']).to eq(8.0)
133
+ end
134
+
64
135
  end
65
136
 
66
137
  end
@@ -1,11 +1,11 @@
1
1
  require 'spec_helper'
2
2
  require 'benchmark'
3
3
 
4
- describe Mongoid::Report do
4
+ describe Mongoid::Report, long: true do
5
5
  let(:klass) { Model }
6
6
 
7
7
  it 'aggregates fields by app in threads' do
8
- Report = Class.new do
8
+ report_klass = Class.new do
9
9
  include Mongoid::Report
10
10
 
11
11
  attach_to Model, as: 'field1-aggregation' do
@@ -17,9 +17,11 @@ describe Mongoid::Report do
17
17
  end
18
18
  end
19
19
 
20
- 30000.times { klass.create!(field1: 1, field2: 1) }
20
+ times = 30000
21
21
 
22
- report = Report.new
22
+ times.times { klass.create!(field1: 1, field2: 1) }
23
+
24
+ report = report_klass.new
23
25
  scoped = report.aggregate
24
26
 
25
27
  Mongoid::Report::Config.use_threads_on_aggregate = true
@@ -37,4 +39,130 @@ describe Mongoid::Report do
37
39
 
38
40
  time2.real.should > time1.real
39
41
  end
42
+
43
+ it 'should work faster using batches in threads on aggregate' do
44
+ report_klass1 = Class.new do
45
+ include Mongoid::Report
46
+
47
+ report 'example' do
48
+ attach_to Model do
49
+ group_by :day
50
+ column :field1
51
+ end
52
+ end
53
+ end
54
+
55
+ report_klass2 = Class.new do
56
+ include Mongoid::Report
57
+
58
+ report 'example' do
59
+ attach_to Model do
60
+ group_by :day
61
+ batches pool_size: 4
62
+ column :field1
63
+ end
64
+ end
65
+ end
66
+
67
+ times = 10
68
+
69
+ times.times.map do |i|
70
+ Thread.new do
71
+ 10000.times { klass.create!(day: i.days.ago, field1: 1) }
72
+ end
73
+ end.map(&:join)
74
+
75
+ report1 = report_klass1.new
76
+ scoped = report1.aggregate
77
+
78
+ time1 = Benchmark.measure do
79
+ rows = scoped.all
80
+ expect(rows['example']['models'].rows[0]['field1']).to eq(10000)
81
+ end
82
+
83
+ report2 = report_klass2.new
84
+ scoped = report2.aggregate_for('example', 'models')
85
+
86
+ time2 = Benchmark.measure do
87
+ scoped = scoped
88
+ .in_batches(day: (5.days.ago.to_date..0.days.from_now.to_date))
89
+ .all
90
+ expect(scoped.rows[0]['field1']).to eq(10000)
91
+ end
92
+
93
+ puts time2
94
+ puts time1
95
+
96
+ time1.real.should > time2.real
97
+ end
98
+
99
+ it 'should merge properly results on splitted requests' do
100
+ report_klass = Class.new do
101
+ include Mongoid::Report
102
+
103
+ report 'example' do
104
+ attach_to Model do
105
+ group_by :field1
106
+ batches pool_size: 2
107
+ column :field1, :field2
108
+ end
109
+ end
110
+ end
111
+
112
+ klass.create!(day: 0.days.ago, field1: 1, field2: 1)
113
+ klass.create!(day: 1.days.ago, field1: 1, field2: 1)
114
+ klass.create!(day: 1.days.ago, field1: 2, field2: 2)
115
+ klass.create!(day: 2.days.ago, field1: 3, field2: 3)
116
+ klass.create!(day: 3.days.ago, field1: 1, field2: 1)
117
+ klass.create!(day: 4.days.ago, field1: 1, field2: 1)
118
+
119
+ report = report_klass.new
120
+
121
+ scoped = report.aggregate_for('example', 'models')
122
+ scoped = scoped
123
+ .in_batches(day: (5.days.ago.to_date..0.days.from_now.to_date))
124
+ .all
125
+
126
+ values = scoped.rows.map {|row| row['field2']}
127
+ expect(values).to include(4)
128
+ expect(values).to include(2)
129
+ expect(values).to include(3)
130
+ end
131
+
132
+ it 'should merge properly results with multiple groups' do
133
+ report_klass = Class.new do
134
+ include Mongoid::Report
135
+
136
+ report 'example' do
137
+ attach_to Model do
138
+ group_by :field1, :field2
139
+ batches pool_size: 2
140
+ column :field1, :field2, :field3
141
+ end
142
+ end
143
+ end
144
+
145
+ klass.create!(day: 0.days.ago, field1: 1, field2: 4, field3: 1)
146
+ klass.create!(day: 1.days.ago, field1: 1, field2: 5, field3: 1)
147
+ klass.create!(day: 1.days.ago, field1: 2, field2: 6, field3: 2)
148
+ klass.create!(day: 2.days.ago, field1: 3, field2: 7, field3: 3)
149
+ klass.create!(day: 3.days.ago, field1: 1, field2: 8, field3: 1)
150
+ klass.create!(day: 4.days.ago, field1: 1, field2: 9, field3: 1)
151
+
152
+ report = report_klass.new
153
+
154
+ scoped = report.aggregate_for('example', 'models')
155
+ scoped = scoped
156
+ .in_batches(day: (5.days.ago.to_date..0.days.from_now.to_date))
157
+ .all
158
+
159
+ expect(scoped.summary['field3']).to eq(9)
160
+ expect(scoped.rows.size).to eq(6)
161
+ expect(scoped.rows).to include({"field3"=>2, "field1"=>2, "field2"=>6})
162
+ expect(scoped.rows).to include({"field3"=>3, "field1"=>3, "field2"=>7})
163
+ expect(scoped.rows).to include({"field3"=>1, "field1"=>1, "field2"=>5})
164
+ expect(scoped.rows).to include({"field3"=>1, "field1"=>1, "field2"=>4})
165
+ expect(scoped.rows).to include({"field3"=>1, "field1"=>1, "field2"=>9})
166
+ expect(scoped.rows).to include({"field3"=>1, "field1"=>1, "field2"=>8})
167
+ end
40
168
  end