active_reporter 0.5.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +14 -0
  3. data/README.md +436 -0
  4. data/Rakefile +23 -0
  5. data/lib/active_reporter.rb +26 -0
  6. data/lib/active_reporter/aggregator.rb +9 -0
  7. data/lib/active_reporter/aggregator/array.rb +14 -0
  8. data/lib/active_reporter/aggregator/average.rb +9 -0
  9. data/lib/active_reporter/aggregator/base.rb +73 -0
  10. data/lib/active_reporter/aggregator/count.rb +23 -0
  11. data/lib/active_reporter/aggregator/count_if.rb +23 -0
  12. data/lib/active_reporter/aggregator/max.rb +9 -0
  13. data/lib/active_reporter/aggregator/min.rb +9 -0
  14. data/lib/active_reporter/aggregator/ratio.rb +23 -0
  15. data/lib/active_reporter/aggregator/sum.rb +13 -0
  16. data/lib/active_reporter/calculator.rb +2 -0
  17. data/lib/active_reporter/calculator/base.rb +19 -0
  18. data/lib/active_reporter/calculator/ratio.rb +9 -0
  19. data/lib/active_reporter/dimension.rb +8 -0
  20. data/lib/active_reporter/dimension/base.rb +150 -0
  21. data/lib/active_reporter/dimension/bin.rb +123 -0
  22. data/lib/active_reporter/dimension/bin/set.rb +162 -0
  23. data/lib/active_reporter/dimension/bin/table.rb +43 -0
  24. data/lib/active_reporter/dimension/category.rb +29 -0
  25. data/lib/active_reporter/dimension/enum.rb +32 -0
  26. data/lib/active_reporter/dimension/number.rb +51 -0
  27. data/lib/active_reporter/dimension/time.rb +93 -0
  28. data/lib/active_reporter/evaluator.rb +2 -0
  29. data/lib/active_reporter/evaluator/base.rb +17 -0
  30. data/lib/active_reporter/evaluator/block.rb +15 -0
  31. data/lib/active_reporter/inflector.rb +8 -0
  32. data/lib/active_reporter/invalid_params_error.rb +4 -0
  33. data/lib/active_reporter/report.rb +102 -0
  34. data/lib/active_reporter/report/aggregation.rb +297 -0
  35. data/lib/active_reporter/report/definition.rb +195 -0
  36. data/lib/active_reporter/report/metrics.rb +75 -0
  37. data/lib/active_reporter/report/validation.rb +106 -0
  38. data/lib/active_reporter/serializer.rb +7 -0
  39. data/lib/active_reporter/serializer/base.rb +103 -0
  40. data/lib/active_reporter/serializer/csv.rb +22 -0
  41. data/lib/active_reporter/serializer/form_field.rb +134 -0
  42. data/lib/active_reporter/serializer/hash_table.rb +12 -0
  43. data/lib/active_reporter/serializer/highcharts.rb +200 -0
  44. data/lib/active_reporter/serializer/nested_hash.rb +11 -0
  45. data/lib/active_reporter/serializer/table.rb +21 -0
  46. data/lib/active_reporter/tracker.rb +2 -0
  47. data/lib/active_reporter/tracker/base.rb +15 -0
  48. data/lib/active_reporter/tracker/delta.rb +9 -0
  49. data/lib/active_reporter/version.rb +3 -0
  50. data/lib/tasks/active_reporter_tasks.rake +4 -0
  51. data/spec/acceptance/data_spec.rb +381 -0
  52. data/spec/active_reporter/aggregator_spec.rb +102 -0
  53. data/spec/active_reporter/dimension/base_spec.rb +102 -0
  54. data/spec/active_reporter/dimension/bin/set_spec.rb +83 -0
  55. data/spec/active_reporter/dimension/bin/table_spec.rb +47 -0
  56. data/spec/active_reporter/dimension/bin_spec.rb +77 -0
  57. data/spec/active_reporter/dimension/category_spec.rb +60 -0
  58. data/spec/active_reporter/dimension/enum_spec.rb +94 -0
  59. data/spec/active_reporter/dimension/number_spec.rb +71 -0
  60. data/spec/active_reporter/dimension/time_spec.rb +61 -0
  61. data/spec/active_reporter/report_spec.rb +597 -0
  62. data/spec/active_reporter/serializer/hash_table_spec.rb +45 -0
  63. data/spec/active_reporter/serializer/highcharts_spec.rb +113 -0
  64. data/spec/active_reporter/serializer/table_spec.rb +62 -0
  65. data/spec/dummy/README.rdoc +28 -0
  66. data/spec/dummy/Rakefile +6 -0
  67. data/spec/dummy/app/assets/config/manifest.js +0 -0
  68. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  69. data/spec/dummy/app/assets/stylesheets/application.css +26 -0
  70. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  71. data/spec/dummy/app/controllers/site_controller.rb +11 -0
  72. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  73. data/spec/dummy/app/models/author.rb +4 -0
  74. data/spec/dummy/app/models/comment.rb +4 -0
  75. data/spec/dummy/app/models/data_builder.rb +112 -0
  76. data/spec/dummy/app/models/post.rb +6 -0
  77. data/spec/dummy/app/models/post_report.rb +14 -0
  78. data/spec/dummy/app/views/layouts/application.html.erb +17 -0
  79. data/spec/dummy/app/views/site/report.html.erb +73 -0
  80. data/spec/dummy/bin/bundle +3 -0
  81. data/spec/dummy/bin/rails +4 -0
  82. data/spec/dummy/bin/rake +4 -0
  83. data/spec/dummy/bin/setup +29 -0
  84. data/spec/dummy/config.ru +4 -0
  85. data/spec/dummy/config/application.rb +26 -0
  86. data/spec/dummy/config/boot.rb +5 -0
  87. data/spec/dummy/config/database.yml +22 -0
  88. data/spec/dummy/config/environment.rb +5 -0
  89. data/spec/dummy/config/environments/development.rb +41 -0
  90. data/spec/dummy/config/environments/production.rb +79 -0
  91. data/spec/dummy/config/environments/test.rb +42 -0
  92. data/spec/dummy/config/initializers/assets.rb +11 -0
  93. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  94. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  95. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  96. data/spec/dummy/config/initializers/inflections.rb +16 -0
  97. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  98. data/spec/dummy/config/initializers/session_store.rb +3 -0
  99. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  100. data/spec/dummy/config/locales/en.yml +23 -0
  101. data/spec/dummy/config/routes.rb +57 -0
  102. data/spec/dummy/config/secrets.yml +22 -0
  103. data/spec/dummy/db/migrate/20150714202319_add_dummy_models.rb +25 -0
  104. data/spec/dummy/db/schema.rb +43 -0
  105. data/spec/dummy/db/seeds.rb +1 -0
  106. data/spec/dummy/log/test.log +37033 -0
  107. data/spec/dummy/public/404.html +67 -0
  108. data/spec/dummy/public/422.html +67 -0
  109. data/spec/dummy/public/500.html +66 -0
  110. data/spec/dummy/public/favicon.ico +0 -0
  111. data/spec/factories/factories.rb +29 -0
  112. data/spec/spec_helper.rb +40 -0
  113. data/spec/support/float.rb +8 -0
  114. metadata +385 -0
@@ -0,0 +1,102 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveReporter::Dimension::Base do
4
+ def new_dimension(dimension_params = {}, report_params = {}, opts = {})
5
+ report_params[:dimensions] = { foo: dimension_params }
6
+ ActiveReporter::Dimension::Base.new(
7
+ :foo,
8
+ OpenStruct.new(params: report_params),
9
+ opts
10
+ )
11
+ end
12
+
13
+ describe '#filter_values' do
14
+ it 'accepts one' do
15
+ dimension = new_dimension(only: 'bar')
16
+ expect(dimension.filter_values).to eq %w(bar)
17
+ end
18
+
19
+ it 'accepts many' do
20
+ dimension = new_dimension(only: %w(bar baz))
21
+ expect(dimension.filter_values).to eq %w(bar baz)
22
+ end
23
+
24
+ it 'determines #filtering?' do
25
+ dimension = new_dimension(only: %w(bar baz))
26
+ expect(dimension).to be_filtering
27
+
28
+ dimension = new_dimension
29
+ expect(dimension).not_to be_filtering
30
+ end
31
+ end
32
+
33
+ describe '#sort_order' do
34
+ it 'can be desc/asc, falls back to root, defaults to asc' do
35
+ dimension = new_dimension
36
+ expect(dimension.sort_order).to eq 'ASC'
37
+
38
+ dimension = new_dimension(sort_desc: true)
39
+ expect(dimension.sort_order).to eq 'DESC'
40
+
41
+ dimension = new_dimension(sort_desc: false)
42
+ expect(dimension.sort_order).to eq 'ASC'
43
+
44
+ dimension = new_dimension({}, sort_desc: true)
45
+ expect(dimension.sort_order).to eq 'DESC'
46
+
47
+ dimension = new_dimension({}, sort_desc: false)
48
+ expect(dimension.sort_order).to eq 'ASC'
49
+ end
50
+ end
51
+
52
+ describe '#null_order' do
53
+ it 'can be first/last, falls back to root, defaults to first (only if postgres)' do
54
+ if ActiveReporter.database_type == :postgres
55
+ dimension = new_dimension
56
+ expect(dimension.null_order).to eq 'NULLS FIRST'
57
+
58
+ dimension = new_dimension(nulls_last: true)
59
+ expect(dimension.null_order).to eq 'NULLS LAST'
60
+
61
+ dimension = new_dimension(nulls_last: false)
62
+ expect(dimension.null_order).to eq 'NULLS FIRST'
63
+
64
+ dimension = new_dimension({}, nulls_last: true)
65
+ expect(dimension.null_order).to eq 'NULLS LAST'
66
+
67
+ dimension = new_dimension({}, nulls_last: false)
68
+ expect(dimension.null_order).to eq 'NULLS FIRST'
69
+ else
70
+ dimension = new_dimension
71
+ expect(dimension.null_order).to be_blank
72
+ end
73
+ end
74
+ end
75
+
76
+ describe '#relate' do
77
+ it 'defaults to the identity function' do
78
+ dimension = new_dimension
79
+ expect(dimension.relate(5)).to eq 5
80
+ end
81
+
82
+ it 'can be overridden, e.g. for joins' do
83
+ dimension = new_dimension({}, {}, relation: ->(r) { r + 5 })
84
+ expect(dimension.relate(5)).to eq 10
85
+ end
86
+ end
87
+
88
+ describe '#expression' do
89
+ it 'defaults to treating name as a column of the report klass table' do
90
+ dimension = ActiveReporter::Dimension::Base.new(
91
+ :bar,
92
+ OpenStruct.new(table_name: 'foo')
93
+ )
94
+ expect(dimension.expression).to eq('foo.bar')
95
+ end
96
+
97
+ it 'can be overridden' do
98
+ dimension = new_dimension({}, {}, table_name: :baz, attribute: :bat)
99
+ expect(dimension.expression).to eq 'baz.bat'
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,83 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveReporter::Dimension::Bin::Set do
4
+ describe '.from_hash' do
5
+ it 'builds a bin from a hash or nil' do
6
+ bin = described_class.from_hash(min: 1, max: 2)
7
+ expect(bin.min).to eq 1
8
+ expect(bin.max).to eq 2
9
+
10
+ bin = described_class.from_hash(nil)
11
+ expect(bin.min).to eq nil
12
+ expect(bin.max).to eq nil
13
+ end
14
+ end
15
+
16
+ describe '.from_sql' do
17
+ it 'builds a bin from a bin text string' do
18
+ bin = described_class.from_sql("1,2")
19
+ expect(bin.min).to eq '1'
20
+ expect(bin.max).to eq '2'
21
+
22
+ bin = described_class.from_sql("1,")
23
+ expect(bin.min).to eq '1'
24
+ expect(bin.max).to eq nil
25
+
26
+ bin = described_class.from_sql(",2")
27
+ expect(bin.min).to eq nil
28
+ expect(bin.max).to eq '2'
29
+
30
+ bin = described_class.from_sql(",")
31
+ expect(bin.min).to eq nil
32
+ expect(bin.max).to eq nil
33
+ end
34
+ end
35
+
36
+ describe '#contains_sql' do
37
+ it 'returns SQL checking if expr is in the bin' do
38
+ bin = described_class.new(1, 2)
39
+ expect(bin.contains_sql('foo')).to eq "(foo >= 1 AND foo < 2)"
40
+
41
+ bin = described_class.new(1, nil)
42
+ expect(bin.contains_sql('foo')).to eq "foo >= 1"
43
+
44
+ bin = described_class.new(nil, 2)
45
+ expect(bin.contains_sql('foo')).to eq "foo < 2"
46
+
47
+ bin = described_class.new(nil, nil)
48
+ expect(bin.contains_sql('foo')).to eq "foo IS NULL"
49
+ end
50
+ end
51
+
52
+ describe '#to_json' do
53
+ it 'reexpresses the bin as a hash' do
54
+ bin = described_class.new(1, 2)
55
+ json = { a: bin }.to_json
56
+ expect(JSON.parse(json)).to eq('a' => { 'min' => 1, 'max' => 2 })
57
+ end
58
+ end
59
+
60
+ describe 'hashing' do
61
+ it 'works with hashes' do
62
+ bin1 = described_class.new(1, 2)
63
+ bin2 = described_class.new(1, 2)
64
+ bin3 = { min: 1, max: 2 }
65
+
66
+ h = { bin3 => 'foo' }
67
+ expect(h[bin1]).to eq 'foo'
68
+ expect(h[bin2]).to eq 'foo'
69
+ expect(h[bin3]).to eq 'foo'
70
+ end
71
+
72
+ it 'works with nil' do
73
+ bin1 = described_class.new(nil, nil)
74
+ bin2 = described_class.new(nil, nil)
75
+ bin3 = nil
76
+
77
+ h = { bin3 => 'foo' }
78
+ expect(h[bin1]).to eq 'foo'
79
+ expect(h[bin2]).to eq 'foo'
80
+ expect(h[bin3]).to eq 'foo'
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveReporter::Dimension::Bin::Table do
4
+ let(:bin_set) { ActiveReporter::Dimension::Bin::Set }
5
+
6
+ describe '#filter' do
7
+ it 'ORs together predicates across bins' do
8
+ table = described_class.new([
9
+ bin_set.new(nil, nil),
10
+ bin_set.new(0, nil),
11
+ bin_set.new(nil, 10),
12
+ bin_set.new(3, 5)
13
+ ])
14
+
15
+ sql = table.filter(Post, 'x').to_sql
16
+
17
+ expect(sql).to include "WHERE (x IS NULL OR x >= 0 OR x < 10 OR (x >= 3 AND x < 5))"
18
+ end
19
+ end
20
+
21
+ describe '#group' do
22
+ it 'joins to a union of bin rows, then groups by the range' do
23
+ table = described_class.new([
24
+ bin_set.new(nil, nil),
25
+ bin_set.new(0, nil),
26
+ bin_set.new(nil, 10),
27
+ bin_set.new(3, 5)
28
+ ])
29
+
30
+ sql = table.group(Post, 'likes', 'likes').to_sql
31
+
32
+ expect(sql).to start_with "SELECT likes_bin_table.bin_text AS likes"
33
+
34
+ if ActiveReporter.database_type == :mysql
35
+ expect(sql).to include "SELECT NULL AS min, NULL AS max, ',' AS bin_text"
36
+ expect(sql).to include "SELECT 0 AS min, NULL AS max, '0,' AS bin_text"
37
+ expect(sql).to include "SELECT NULL AS min, 10 AS max, ',10' AS bin_text"
38
+ expect(sql).to include "SELECT 3 AS min, 5 AS max, '3,5' AS bin_text"
39
+ else
40
+ expect(sql).to include "SELECT NULL AS min, NULL AS max, CAST(',' AS text) AS bin_text"
41
+ expect(sql).to include "SELECT 0 AS min, NULL AS max, CAST('0,' AS text) AS bin_text"
42
+ expect(sql).to include "SELECT NULL AS min, 10 AS max, CAST(',10' AS text) AS bin_text"
43
+ expect(sql).to include "SELECT 3 AS min, 5 AS max, CAST('3,5' AS text) AS bin_text"
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,77 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveReporter::Dimension::Bin do
4
+ def new_dimension(dimension_params = {}, report_params = {}, opts = {})
5
+ report_params[:dimensions] = { foo: dimension_params }
6
+ ActiveReporter::Dimension::Bin.new(:foo,
7
+ OpenStruct.new(params: report_params),
8
+ opts
9
+ )
10
+ end
11
+
12
+ def expect_error(&block)
13
+ expect { yield }.to raise_error(ActiveReporter::InvalidParamsError)
14
+ end
15
+
16
+ describe 'param validation' do
17
+ it 'yells unless :bin_count is numeric' do
18
+ expect_error { new_dimension(bin_count: 'hey') }
19
+ expect_error { new_dimension(bin_count: nil) }
20
+ new_dimension(bin_count: 5)
21
+ new_dimension(bin_count: 1.24)
22
+ end
23
+ end
24
+
25
+ describe '#min/max' do
26
+ it 'finds the extremes in filter_values' do
27
+ dimension = new_dimension(only: [{ min: 1, max: 3 }, { min: -3 }, { min: 17, max: 40 }])
28
+ expect(dimension.min).to eq -3
29
+ expect(dimension.max).to eq 40
30
+ end
31
+
32
+ it 'falls back to the smallest value in the data' do
33
+ dimension = ActiveReporter::Dimension::Bin.new(:likes,
34
+ OpenStruct.new(records: Post, params: {}),
35
+ model: :post, attribute: :likes
36
+ )
37
+ expect(dimension.min).to be_nil
38
+ expect(dimension.max).to be_nil
39
+ create(:post, likes: 3)
40
+ create(:post, likes: 10)
41
+ create(:post, likes: 1)
42
+ expect(dimension.min).to eq 1
43
+ expect(dimension.max).to eq 10
44
+ end
45
+ end
46
+
47
+ describe '#group_values' do
48
+ it 'defaults to dividing the domain into bins of bin_width' do
49
+ dimension = new_dimension(only: { min: 0, max: 3 })
50
+ allow(dimension).to receive(:bin_width).and_return(1)
51
+ allow(dimension).to receive(:data_contains_nil?).and_return(false)
52
+ expect(dimension.group_values).to eq [
53
+ { min: 0, max: 1 },
54
+ { min: 1, max: 2 },
55
+ { min: 2, max: 3 }
56
+ ]
57
+ end
58
+
59
+ it 'is inclusive of max if data-driven' do
60
+ dimension = new_dimension(only: { min: 0 })
61
+ allow(dimension.report).to receive(:records).and_return(Post)
62
+ allow(dimension).to receive(:expression).and_return('posts.likes')
63
+ allow(dimension).to receive(:bin_width).and_return(1)
64
+ create(:post, likes: 2)
65
+ expect(dimension.group_values).to eq [
66
+ { min: 0, max: 1 },
67
+ { min: 1, max: 2 },
68
+ { min: 2, max: 3 }
69
+ ]
70
+ end
71
+
72
+ it 'can be customized' do
73
+ dimension = new_dimension(bins: { min: 0, max: 1 })
74
+ expect(dimension.group_values).to eq [{ min: 0, max: 1 }]
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveReporter::Dimension::Category do
4
+ def author_dimension(report)
5
+ described_class.new(:author, report, model: :authors, attribute: :name, relation: ->(r) { r.joins(
6
+ "LEFT OUTER JOIN authors ON authors.id = posts.author_id") })
7
+ end
8
+
9
+ describe '#filter' do
10
+ it 'filters to rows matching at least one value' do
11
+ p1 = create(:post, author: 'Alice')
12
+ p2 = create(:post, author: 'Bob')
13
+ p3 = create(:post, author: nil)
14
+
15
+ def filter_by(author_values)
16
+ report = OpenStruct.new(
17
+ table_name: 'posts',
18
+ params: { dimensions: { author: { only: author_values } } }
19
+ )
20
+ dimension = author_dimension(report)
21
+ dimension.filter(dimension.relate(Post))
22
+ end
23
+
24
+ expect(filter_by(['Alice'])).to eq [p1]
25
+ expect(filter_by([nil])).to eq [p3]
26
+ expect(filter_by(['Alice', nil])).to eq [p1, p3]
27
+ expect(filter_by(['Alice', 'Bob'])).to eq [p1, p2]
28
+ expect(filter_by([])).to eq []
29
+ end
30
+ end
31
+
32
+ describe '#group' do
33
+ it 'groups the relation by the exact value of the SQL expression' do
34
+ p1 = create(:post, author: 'Alice')
35
+ p2 = create(:post, author: 'Alice')
36
+ p3 = create(:post, author: nil)
37
+ p4 = create(:post, author: 'Bob')
38
+ p5 = create(:post, author: 'Bob')
39
+ p6 = create(:post, author: 'Bob')
40
+
41
+ report = OpenStruct.new(table_name: 'posts', params: {})
42
+ dimension = author_dimension(report)
43
+
44
+ results = dimension.group(dimension.relate(Post)).select("COUNT(*) AS count").map do |r|
45
+ r.attributes.values_at(dimension.send(:sql_value_name), 'count')
46
+ end
47
+
48
+ expect(results).to eq [[nil, 1], ['Alice', 2], ['Bob', 3]]
49
+ end
50
+ end
51
+
52
+ describe '#group_values' do
53
+ it 'echoes filter_values if filtering' do
54
+ dimension = author_dimension(OpenStruct.new(params: {
55
+ dimensions: { author: { only: ['foo', 'bar'] } }
56
+ }))
57
+ expect(dimension.group_values).to eq %w(foo bar)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,94 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveReporter::Dimension::Enum do
4
+ def new_dimension(dimension_params = {}, report_params = {}, opts = { model: :post })
5
+ report_params[:dimensions] = dimension_params
6
+ ActiveReporter::Dimension::Enum.new(
7
+ :status,
8
+ OpenStruct.new(params: report_params, groupers: { status: {}, category: {} }, raw_data: {
9
+ ['published', 'post_count'] => 5, ['published', 'post_total'] => 500.00, ['published', 'post_average'] => 100.00,
10
+ ['archived', 'post_count'] => 7, ['archived', 'post_total'] => 530.25, ['archived', 'post_average'] => 75.75,
11
+ }),
12
+ opts
13
+ )
14
+ end
15
+
16
+ def status_dimension(report)
17
+ described_class.new(:status, report, model: :posts, attribute: :status)
18
+ end
19
+
20
+ def author_dimension(report)
21
+ described_class.new(:author, report, model: :authors, attribute: :name, relation: ->(r) { r.joins(
22
+ "LEFT OUTER JOIN authors ON authors.id = posts.author_id") })
23
+ end
24
+
25
+ describe '#group_values' do
26
+ # it 'echoes filter_values if filtering' do
27
+ # dimension = status_dimension(OpenStruct.new(params: {
28
+ # groupers: [:status]
29
+ # }))
30
+ # expect(dimension.group_values).to eq [nil, 'draft', 'unpublished', 'published', 'archived']
31
+ # end
32
+
33
+
34
+ it 'determines #filtering?' do
35
+ dimension = new_dimension({ status: {}, category: {} }, { groupers: [:status] })
36
+ expect(dimension).not_to be_filtering
37
+ expect(dimension.group_values).to match %w(published archived)
38
+ end
39
+ end
40
+
41
+ # describe '#all_values' do
42
+ # it 'returns model enum values' do
43
+ # dimension = author_dimension(OpenStruct.new(params: {
44
+ # dimensions: { author: { only: [nil, 'draft', 'unpublished', 'published', 'archived'] } }
45
+ # }))
46
+ # expect(dimension.all_values).to eq described_class.
47
+ # end
48
+ # end
49
+
50
+ # describe '#filter' do
51
+ # it 'filters to rows matching at least one value' do
52
+ # p1 = create(:post, author: 'Alice')
53
+ # p2 = create(:post, author: 'Bob')
54
+ # p3 = create(:post, author: nil)
55
+
56
+ # def filter_by(author_values)
57
+ # report = OpenStruct.new(
58
+ # table_name: 'posts',
59
+ # params: { dimensions: { author: { only: author_values } } }
60
+ # )
61
+ # dimension = author_dimension(report)
62
+ # dimension.filter(dimension.relate(Post))
63
+ # end
64
+
65
+ # expect(filter_by(['Alice'])).to eq [p1]
66
+ # expect(filter_by([nil])).to eq [p3]
67
+ # expect(filter_by(['Alice', nil])).to eq [p1, p3]
68
+ # expect(filter_by(['Alice', 'Bob'])).to eq [p1, p2]
69
+ # expect(filter_by([])).to eq []
70
+ # end
71
+ # end
72
+
73
+ # describe '#group' do
74
+ # it 'groups the relation by the exact value of the SQL expression' do
75
+ # p1 = create(:post, author: 'Alice')
76
+ # p2 = create(:post, author: 'Alice')
77
+ # p3 = create(:post, author: nil)
78
+ # p4 = create(:post, author: 'Bob')
79
+ # p5 = create(:post, author: 'Bob')
80
+ # p6 = create(:post, author: 'Bob')
81
+
82
+ # report = OpenStruct.new(table_name: 'posts', params: {})
83
+ # dimension = author_dimension(report)
84
+
85
+ # results = dimension.group(dimension.relate(Post)).select("COUNT(*) AS count").map do |r|
86
+ # r.attributes.values_at(dimension.send(:sql_value_name), 'count')
87
+ # end
88
+
89
+ # expect(results).to eq [[nil, 1], ['Alice', 2], ['Bob', 3]]
90
+ # end
91
+ # end
92
+
93
+
94
+ end