mongoid-report 0.1.3 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YTcxMTUyOWNhZWVmNjVhOWY1MGRkMDhhYTQ2YTI5YTY4YzE5NzhlOA==
4
+ NDE4Y2Y5ODExNDhkNTdmMWNlNzIwMTg1MWU0OTcwYWJlZTg0YTIzYw==
5
5
  data.tar.gz: !binary |-
6
- YWQ1YTZjMTMwZjJkMzU0MjE5ZGZlMDNlNDdhNzdiZmM5MjE0YTM1Yw==
6
+ NzBhN2ZiY2RhMDhjOTcwZDA2ZjBkNjQ4NDEzN2I2MDZhNmVhZjZhOQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- YzAxMjg1Yjc4MDQyZjU3MTJiNTJhNzhiMmY4N2U5YTA3NGJkZGQwZDc4Yjll
10
- MzMzZjdjNzZjYjI4N2UyNGJmMWRkZTUzZTA2MzEwZDY0MDI0NDgwZDU0MzEw
11
- ZjEyZmFkM2ViMzNhMDMyYjI2YTVmY2FiMzRiZjJjYzI5YjRkMTk=
9
+ MzY3ZDQ5ZTI2YjJmNmE4OWU2Y2JmZWRkMTkzMzYwNmQ0NzJlNGFjNGI3OTY4
10
+ Mzc3MjBiNGE3ZjRiMTJjZDY3Nzk1NWFlNzBkMmM4YjA4YzM3MWM3NDQzMDhk
11
+ MWRiMTRhMzNkZDg1NDk3ZjUxOTdiMDAxZWZkNmNiMGU0ZGZiOWY=
12
12
  data.tar.gz: !binary |-
13
- NGMzZTJiYWQ4OGZmODRkYjhjNTBmYmQwNTZmNDNhM2M2ZTdkYTVkMGRlMGVl
14
- OTcwYjVmZGNjODkyZDE4YTc2YjJmMDhiMmJiODg3ZDI2ODdhZjUzOTZkM2Vj
15
- ZjY5ZTliYzdlMDY4MDExMmRlMmE3NTVlZDI0ODFiZTdiZGY0NzI=
13
+ M2NkYmJlYTU0YjcyZmRhODE3OTY5Yjg0Y2MzMmRhYzU0NTc5YmVkZTc0M2Zh
14
+ OWE4MWJjNWY5YjZkMjZkMGZmZGVkZTBkZjIwNzVjM2E4M2FhZTE1MjViYzhk
15
+ NTAzYTU5NGJhYjY4MGRkYjA3ZTc5YmQ3NjdiOGFlZWRiZjk5OTc=
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mongoid-report (0.1.3)
4
+ mongoid-report (0.1.5)
5
5
  mongoid (> 3.0.1)
6
6
 
7
7
  GEM
@@ -4,12 +4,18 @@ module Mongoid
4
4
  module Report
5
5
 
6
6
  class Collection < SimpleDelegator
7
- def initialize(rows, fields, columns)
7
+ def initialize(context, rows, fields, columns)
8
+ @context = context
8
9
  @rows = rows
9
10
  @fields = fields
10
11
  @columns = columns
12
+
13
+ # Apply dyncamic columns in context of row and apply indifferent access
14
+ # for the rows.
15
+ rows = compile_dynamic_fields(rows, columns)
16
+
17
+ # Collection should behave like Array using delegator method.
11
18
  super(rows)
12
- compile_dynamic_fields(columns)
13
19
  end
14
20
 
15
21
  def summary
@@ -24,11 +30,13 @@ module Mongoid
24
30
 
25
31
  private
26
32
 
27
- def compile_dynamic_fields(columns)
28
- self.each do |row|
33
+ def compile_dynamic_fields(rows, columns)
34
+ rows.map do |row|
29
35
  @columns.each do |name, function|
30
- row[name] = function.call(row)
36
+ row[name] = function.call(@context, row)
31
37
  end
38
+
39
+ row.with_indifferent_access
32
40
  end
33
41
  end
34
42
  end
@@ -0,0 +1,11 @@
1
+ module Mongoid
2
+ module Report
3
+
4
+ class Config
5
+ class_attribute :use_threads_on_aggregate
6
+
7
+ self.use_threads_on_aggregate = false
8
+ end
9
+
10
+ end
11
+ end
@@ -13,13 +13,7 @@ module Mongoid
13
13
  private
14
14
 
15
15
  def groups
16
- @group_by ||= begin
17
- if settings[:group_by].size == 0
18
- [:_id]
19
- else
20
- settings[:group_by]
21
- end
22
- end
16
+ @group_by ||= settings.fetch(:group_by, [])
23
17
  end
24
18
 
25
19
  def fields
@@ -19,23 +19,32 @@ module Mongoid
19
19
 
20
20
  def all
21
21
  self.yield unless yielded?
22
- queries = compile_queries
23
- Collection.new(klass.collection.aggregate(queries), fields, columns)
22
+
23
+ aggregation_queries = compile_queries
24
+ rows = klass.collection.aggregate(aggregation_queries)
25
+
26
+ Collection.new(context, rows, fields, columns)
24
27
  end
25
28
 
26
29
  private
27
30
 
28
31
  def compile_queries
29
- queries.dup.map do |query|
30
- query.each do |function_name, values|
31
- values.each do |name, value|
32
- value = value.call(context) if value.respond_to?(:call)
33
- query[function_name][name] = value
32
+ compiled = queries.map do |query|
33
+ next query unless query.has_key?("$match")
34
+
35
+ query.deep_dup.tap do |new_query|
36
+ new_query.each do |function_name, values|
37
+ values.each do |name, value|
38
+ if value.respond_to?(:call)
39
+ value = value.call(context)
40
+ end
41
+ new_query[function_name][name] = value
42
+ end
34
43
  end
35
44
  end
36
-
37
- query
38
45
  end
46
+
47
+ compiled
39
48
  end
40
49
 
41
50
  def yielded?
@@ -47,17 +56,17 @@ module Mongoid
47
56
  end
48
57
 
49
58
  def klass
50
- context.class.settings_property(report_name, :for)
59
+ context.report_module_settings[report_name][:for]
51
60
  end
52
61
 
53
62
  def fields
54
63
  # We need to use here only output field names it could be different
55
64
  # than defined colunms, Example: field1: 'report-field-name'
56
- context.class.settings_property(report_name, :fields).values
65
+ context.report_module_settings[report_name][:fields].values
57
66
  end
58
67
 
59
68
  def columns
60
- context.class.settings_property(report_name, :columns)
69
+ context.report_module_settings[report_name][:columns]
61
70
  end
62
71
  end
63
72
 
@@ -1,7 +1,14 @@
1
+ require 'thread'
2
+
1
3
  module Mongoid
2
4
  module Report
3
5
 
4
6
  ScopeCollection = Struct.new(:context) do
7
+ def initialize(context)
8
+ @mutex = Mutex.new
9
+ super
10
+ end
11
+
5
12
  def scopes
6
13
  @scopes ||= modules.map do |key|
7
14
  Scope.new(context, key)
@@ -23,9 +30,22 @@ module Mongoid
23
30
  end
24
31
 
25
32
  def all
26
- scopes.inject({}) do |hash, scope|
27
- hash[scope.report_name] = scope.all
28
- hash
33
+ {}.tap do |hash|
34
+ if Mongoid::Report::Config.use_threads_on_aggregate
35
+ scopes.map do |scope|
36
+ Thread.new do
37
+ rows = scope.all
38
+
39
+ @mutex.synchronize do
40
+ hash[scope.report_name] = rows
41
+ end
42
+ end
43
+ end.map(&:join)
44
+ else
45
+ scopes.each do |scope|
46
+ hash[scope.report_name] = scope.all
47
+ end
48
+ end
29
49
  end
30
50
  end
31
51
 
@@ -1,5 +1,5 @@
1
1
  module Mongoid
2
2
  module Report
3
- VERSION = "0.1.3"
3
+ VERSION = "0.1.5"
4
4
  end
5
5
  end
@@ -1,6 +1,7 @@
1
1
  require 'active_support/concern'
2
2
  require 'active_support/core_ext/class/attribute'
3
3
 
4
+ require_relative 'report/config'
4
5
  require_relative 'report/queries_builder'
5
6
  require_relative 'report/attach_proxy'
6
7
  require_relative 'report/collection'
@@ -19,8 +20,20 @@ module Mongoid
19
20
 
20
21
  self.settings = {}
21
22
 
23
+ def self.inherited(subclass)
24
+ subclass.settings = self.settings.dup
25
+ end
26
+
27
+ # Variable for copying internal class settings to the instance because of
28
+ # possible modifications in case of using filters with lambda
29
+ # expressions.
30
+ attr_reader :report_module_settings
31
+
22
32
  def initialize_report_module
23
- self.class.settings.each do |klass, configuration|
33
+ # Lets store settings under created instance.
34
+ @report_module_settings = self.class.settings.dup
35
+
36
+ @report_module_settings.each do |klass, configuration|
24
37
  builder = QueriesBuilder.new(configuration)
25
38
 
26
39
  # Prepare group queries depends on the configuration in the included
@@ -35,7 +48,7 @@ module Mongoid
35
48
  alias :initialize :initialize_report_module
36
49
 
37
50
  def queries(klass)
38
- self.class.settings[klass][:queries]
51
+ report_module_settings[klass][:queries]
39
52
  end
40
53
 
41
54
  # We should pass here mongoid document
@@ -140,6 +153,7 @@ module Mongoid
140
153
  fields: ActiveSupport::OrderedHash.new,
141
154
  group_by: [],
142
155
  queries: [],
156
+ compiled: false,
143
157
  columns: ActiveSupport::OrderedHash.new,
144
158
  }
145
159
  end
@@ -7,19 +7,25 @@ describe Mongoid::Report do
7
7
  let(:two_days_ago) { Date.parse("18-12-2004") }
8
8
 
9
9
  describe '.aggregate_for' do
10
- it 'aggregates fields by default group _id as well' do
11
- instance1 = klass.create!(day: today , field1: 1)
12
- instance2 = klass.create!(day: today , field1: 1)
13
- instance3 = klass.create!(day: yesterday , field1: 1)
10
+ it 'aggregates fields by app' do
11
+ Report = Class.new do
12
+ include Mongoid::Report
14
13
 
15
- example = Report2.new
14
+ attach_to Model do
15
+ aggregation_field :field1
16
+ end
17
+ end
18
+
19
+ klass.create!(field1: 1)
20
+ klass.create!(field1: 1)
21
+ klass.create!(field1: 1)
22
+
23
+ example = Report.new
16
24
  rows = example.aggregate_for(klass)
17
25
  rows = rows.all
18
26
 
19
- expect(rows.size).to eq(3)
20
- expect(rows[0]['field1']).to eq(1)
21
- expect(rows[1]['field1']).to eq(1)
22
- expect(rows[2]['field1']).to eq(1)
27
+ expect(rows.size).to eq(1)
28
+ expect(rows[0]['field1']).to eq(3)
23
29
  end
24
30
 
25
31
  it 'aggregates field by defined field of the mode' do
@@ -13,7 +13,7 @@ describe Mongoid::Report do
13
13
  attach_to Model do
14
14
  group_by :day
15
15
  aggregation_field :field1
16
- column 'dynamic-field1' => ->(row) { row['field1'] * 10 }
16
+ column 'dynamic-field1' => ->(context, row) { row['field1'] * 10 }
17
17
  end
18
18
  end
19
19
  end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mongoid::Report::Config do
4
+ it 'allows to set aggregation threads' do
5
+ expect(Mongoid::Report::Config.use_threads_on_aggregate).to eq(false)
6
+ Mongoid::Report::Config.use_threads_on_aggregate = true
7
+ expect(Mongoid::Report::Config.use_threads_on_aggregate).to eq(true)
8
+ end
9
+ end
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe Mongoid::Report::QueriesBuilder do
4
4
 
5
5
  describe '.queries' do
6
- it 'builds queries for aggregation using default group _id field' do
6
+ it 'builds queries for aggregation' do
7
7
  queries = Report1.new.queries(Model)
8
8
  expect(queries.size).to eq(3)
9
9
  expect(queries[0]).to eq(
@@ -13,12 +13,12 @@ describe Mongoid::Report::QueriesBuilder do
13
13
  })
14
14
  expect(queries[1]).to eq(
15
15
  '$group' => {
16
- :_id => { :_id => '$_id' },
16
+ :_id => { },
17
17
  :field1 => { '$sum' => '$field1' },
18
18
  })
19
19
  expect(queries[2]).to eq(
20
20
  '$project' => {
21
- :_id => '$_id',
21
+ :_id => 0,
22
22
  :field1 => '$field1',
23
23
  })
24
24
  end
@@ -20,6 +20,30 @@ describe Mongoid::Report do
20
20
  expect(rows.summary[:field1]).to eq(3)
21
21
  expect(rows.summary['field1']).to eq(3)
22
22
  end
23
+
24
+ it 'should support dynamic columns as well' do
25
+ Report = Class.new do
26
+ include Mongoid::Report
27
+
28
+ report 'example' do
29
+ attach_to Model do
30
+ aggregation_field :field1
31
+ column 'new-field1' => ->(context, row) { row['field1'] * 10 }
32
+ end
33
+ end
34
+ end
35
+
36
+ klass.create!(field1: 1)
37
+ klass.create!(field1: 1)
38
+ klass.create!(field1: 1)
39
+
40
+ example = Report.new
41
+ rows = example.aggregate_for('example-models')
42
+ rows = rows.all
43
+
44
+ expect(rows[0]['field1']).to eq(3)
45
+ expect(rows[0]['new-field1']).to eq(30)
46
+ end
23
47
  end
24
48
 
25
49
  end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+ require 'benchmark'
3
+
4
+ describe Mongoid::Report do
5
+ let(:klass) { Model }
6
+
7
+ it 'aggregates fields by app in threads' do
8
+ Report = Class.new do
9
+ include Mongoid::Report
10
+
11
+ attach_to Model, as: 'field1-aggregation' do
12
+ aggregation_field :field1
13
+ end
14
+
15
+ attach_to Model, as: 'field2-aggregation' do
16
+ aggregation_field :field2
17
+ end
18
+ end
19
+
20
+ 30000.times { klass.create!(field1: 1, field2: 1) }
21
+
22
+ report = Report.new
23
+ scoped = report.aggregate
24
+
25
+ Mongoid::Report::Config.use_threads_on_aggregate = true
26
+ time1 = Benchmark.measure do
27
+ rows = scoped.all
28
+ end
29
+
30
+ Mongoid::Report::Config.use_threads_on_aggregate = false
31
+ time2 = Benchmark.measure do
32
+ rows = scoped.all
33
+ end
34
+
35
+ puts time2
36
+ puts time1
37
+
38
+ time2.real.should > time1.real
39
+ end
40
+ end
@@ -88,4 +88,46 @@ describe Mongoid::Report do
88
88
  end
89
89
  end
90
90
 
91
+
92
+ describe 'two report classes' do
93
+ it 'should have different settings' do
94
+ ReportKlass1 = Class.new do
95
+ include Mongoid::Report
96
+
97
+ attach_to Model do
98
+ aggregation_field :field1
99
+ end
100
+ end
101
+
102
+ ReportKlass2 = Class.new do
103
+ include Mongoid::Report
104
+
105
+ attach_to Model do
106
+ aggregation_field :field2
107
+ end
108
+ end
109
+
110
+ expect(ReportKlass1.settings).not_to eq(ReportKlass2.settings)
111
+ end
112
+
113
+ class ReportKlass
114
+ include Mongoid::Report
115
+ end
116
+
117
+ class ReportKlass1 < ReportKlass
118
+ attach_to Model do
119
+ aggregation_field :field1
120
+ end
121
+ end
122
+
123
+ class ReportKlass2 < ReportKlass
124
+ attach_to Model do
125
+ aggregation_field :field2
126
+ end
127
+ end
128
+
129
+ it 'should have different settings for inherited classes' do
130
+ expect(ReportKlass1.fields(Model)).not_to eq(ReportKlass2.fields(Model))
131
+ end
132
+ end
91
133
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mongoid-report
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexandr Korsak
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-24 00:00:00.000000000 Z
11
+ date: 2014-06-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mongoid
@@ -74,6 +74,7 @@ files:
74
74
  - lib/mongoid/report.rb
75
75
  - lib/mongoid/report/attach_proxy.rb
76
76
  - lib/mongoid/report/collection.rb
77
+ - lib/mongoid/report/config.rb
77
78
  - lib/mongoid/report/queries_builder.rb
78
79
  - lib/mongoid/report/report_proxy.rb
79
80
  - lib/mongoid/report/scope.rb
@@ -82,8 +83,10 @@ files:
82
83
  - mongoid-report.gemspec
83
84
  - spec/mongoid/report/aggregation_spec.rb
84
85
  - spec/mongoid/report/column_spec.rb
86
+ - spec/mongoid/report/config_spec.rb
85
87
  - spec/mongoid/report/queries_builder_spec.rb
86
88
  - spec/mongoid/report/summary_spec.rb
89
+ - spec/mongoid/report/threads_spec.rb
87
90
  - spec/mongoid/report_spec.rb
88
91
  - spec/spec_helper.rb
89
92
  - spec/support/models.rb
@@ -114,8 +117,10 @@ summary: Easily build mongoid reports using aggregation framework
114
117
  test_files:
115
118
  - spec/mongoid/report/aggregation_spec.rb
116
119
  - spec/mongoid/report/column_spec.rb
120
+ - spec/mongoid/report/config_spec.rb
117
121
  - spec/mongoid/report/queries_builder_spec.rb
118
122
  - spec/mongoid/report/summary_spec.rb
123
+ - spec/mongoid/report/threads_spec.rb
119
124
  - spec/mongoid/report_spec.rb
120
125
  - spec/spec_helper.rb
121
126
  - spec/support/models.rb