mongoid-report 0.1.9 → 0.2.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.
@@ -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