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,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cff7309f3eb428bd8bf2059d60d60158e49a1114b273cd646da5cfa666274598
4
+ data.tar.gz: ef9ce48d28afd9f7e64ab3302473367ab932f5f16a6ed28e613e42d746b46caa
5
+ SHA512:
6
+ metadata.gz: 1b366856d2b834fe2d27bfba9cede8018bfe1f9f5cfe3ef2464f4b65d9416a0014ba4007979924bc691dd2e1cd391ae970c03387f96365f5ac5572b4e4163c15
7
+ data.tar.gz: b87e54d19f354287f2fa16d3a8a693fc5216cdd8bae5d667092ac5c4676b132aa272ef6000d3b8dc31118a0856824c57a40e24e4488c3035935a4360d95fd1ad
@@ -0,0 +1,14 @@
1
+ Copyright 2015 Andrew Ross (https://github.com/asross/repor)
2
+ Copyright 2020 chaunce butterfield (https://github.com/chaunce/active_reporter)
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
5
+ (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
6
+ merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
7
+ furnished to do so, subject to the following conditions:
8
+
9
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
12
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
13
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
14
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,436 @@
1
+ # ActiveReporter [![Actions Status](https://github.com/chaunce/active_reporter/workflows/Ruby/badge.svg)](https://github.com/chaunce/active_reporter/actions)
2
+
3
+ `ActiveReporter` is a framework for aggregating data about
4
+ [Rails](http://rubyonrails.org) models backed by
5
+ [PostgreSQL](http://www.postgresql.org), [MySQL](https://www.mysql.com), or
6
+ [SQLite](https://www.sqlite.org) databases. It's designed to be flexible
7
+ enough to accommodate many use cases, but opinionated enough to avoid the need
8
+ for boilerplate.
9
+
10
+ `ActiveReporter` is based on the `repor` gem by Andrew Ross https://github.com/asross/repor
11
+
12
+ <!-- START doctoc generated TOC please keep comment here to allow auto update -->
13
+ <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
14
+
15
+
16
+ - [Basic usage](#basic-usage)
17
+ - [Building reports](#building-reports)
18
+ - [Defining reports](#defining-reports)
19
+ - [Base relation](#base-relation)
20
+ - [Dimensions (x-axes)](#dimensions-x-axes)
21
+ - [Filtering by dimensions](#filtering-by-dimensions)
22
+ - [Grouping by dimensions](#grouping-by-dimensions)
23
+ - [Customizing dimensions](#customizing-dimensions)
24
+ - [Aggregators (y-axes)](#aggregators-y-axes)
25
+ - [Customizing aggregators](#customizing-aggregators)
26
+ - [Serializing reports](#serializing-reports)
27
+ - [Contributing](#contributing)
28
+ - [License](#license)
29
+
30
+ <!-- END doctoc generated TOC please keep comment here to allow auto update -->
31
+
32
+ ## Basic usage
33
+
34
+ Here are some examples of how to define, run, and serialize a `ActiveReporter::Report`:
35
+
36
+ ```ruby
37
+ class PostReport < ActiveReporter::Report
38
+ report_on :Post
39
+
40
+ category_dimension :author, relation: ->(r) { r.joins(:author) },
41
+ expression: 'users.name'
42
+ number_dimension :likes
43
+ time_dimension :created_at
44
+
45
+ count_aggregator :number_of_posts
46
+ sum_aggregator :total_likes, expression: 'posts.likes'
47
+ array_aggregator :post_ids, expression: 'posts.id'
48
+ end
49
+
50
+ # show me # published posts from 2014-2015 with at least 4 likes, by author
51
+
52
+ report = PostReport.new(
53
+ relation: Post.published,
54
+ groupers: [:author],
55
+ aggregators: [:number_of_posts],
56
+ dimensions: {
57
+ likes: {
58
+ only: { min: 4 }
59
+ },
60
+ created_at: {
61
+ only: { min: '2014', max: '2015' }
62
+ }
63
+ }
64
+ )
65
+
66
+ puts report.data
67
+
68
+ # => [
69
+ # { key: 'James Joyce', value: 10 },
70
+ # { key: 'Margaret Atwood', value: 4 }
71
+ # { key: 'Toni Morrison', value: 5 }
72
+ # ]
73
+
74
+ # show me likes on specific authors' posts by author and year, from 1985-1987
75
+
76
+ report = PostReport.new(
77
+ groupers: [:author, :created_at],
78
+ aggregators: [:total_likes],
79
+ dimensions: {
80
+ created_at: {
81
+ only: { min: '1985', max: '1987' },
82
+ bin_width: 'year'
83
+ },
84
+ author: {
85
+ only: ['Edith Wharton', 'James Baldwin']
86
+ }
87
+ }
88
+ )
89
+
90
+ puts report.data
91
+
92
+ # => [{
93
+ # key: { min: Tue, 01 Jan 1985 00:00:00 UTC +00:00,
94
+ # max: Wed, 01 Jan 1986 00:00:00 UTC +00:00 },
95
+ # values: [
96
+ # { key: 'Edith Wharton', value: 35 },
97
+ # { key: 'James Baldwin', value: 13 }
98
+ # ]
99
+ # }, {
100
+ # key: { min: Wed, 01 Jan 1986 00:00:00 UTC +00:00,
101
+ # max: Thu, 01 Jan 1987 00:00:00 UTC +00:00 },
102
+ # values: [
103
+ # { key: 'Edith Wharton', value: 0 },
104
+ # { key: 'James Baldwin', value: 0 }
105
+ # ]
106
+ # }, {
107
+ # key: { min: Thu, 01 Jan 1987 00:00:00 UTC +00:00,
108
+ # max: Fri, 01 Jan 1988 00:00:00 UTC +00:00 },
109
+ # values: [
110
+ # { key: 'Edith Wharton', value: 0 },
111
+ # { key: 'James Baldwin', value: 19 }
112
+ # ]
113
+ # }]
114
+
115
+ csv_serializer = ActiveReporter::Serializer::Csv.new(report)
116
+ puts csv_serializer.csv_text
117
+
118
+ # => csv text string
119
+
120
+ chart_serializer = ActiveReporter::Serializer::Highcharts.new(report)
121
+ puts chart_serializer.highcharts_options
122
+
123
+ # => highcharts options hash
124
+ ```
125
+
126
+ To define a report, you declare dimensions (which represent attributes of your
127
+ data) and aggregators (which represent quantities you want to measure). To
128
+ run a report, you instantiate it with one aggregator and at least one dimension,
129
+ then inspect its `data`. You can also wrap it in a serializer to get results in
130
+ useful formats.
131
+
132
+ ## Building reports
133
+
134
+ Just call `ReportClass.new(params)`, where `params` is a hash with these keys:
135
+
136
+ - `aggregators` (required) is a list of the names of the aggregator(s) to aggregate by
137
+ - `groupers` (required) is a list of the names of the dimension(s) to group by
138
+ - `relation` (optional) provides an initial scope for the data
139
+ - `dimensions` (optional) holds dimension-specific filter or grouping options
140
+
141
+ See below for more details about dimension-specific parameters.
142
+
143
+ ## Defining reports
144
+
145
+ ### Base relation
146
+
147
+ A `ActiveReporter::Report` either needs to know what `ActiveRecord` class it is reporting
148
+ on, or it needs to know a `table_name` and a `base_relation`.
149
+
150
+ You can specify an `ActiveRecord` class by calling the `report_on` class method
151
+ with a class or class name, or if you prefer, you can override the other two as
152
+ instance methods.
153
+
154
+ By default, it will try to infer an `ActiveRecord` class from the report class
155
+ name by dropping `/Report$/` and constantizing.
156
+
157
+ ```ruby
158
+ class PostReport < ActiveReporter::Report
159
+ end
160
+
161
+ PostReport.new.table_name
162
+ # => 'posts'
163
+
164
+ PostReport.new.base_relation
165
+ # => Post.all
166
+
167
+ class PostStructuralReport < ActiveReporter::Report
168
+ report_on :Post
169
+
170
+ def base_relation
171
+ super.where(author: 'Foucault')
172
+ end
173
+ end
174
+
175
+ PostStructuralReport.new.table_name
176
+ # => 'posts'
177
+
178
+ PostStructuralReport.new.base_relation
179
+ # => Post.where(author: 'Foucault')
180
+ ```
181
+
182
+ Finally, you can also use `autoreport_on` if you'd like to automatically infer
183
+ dimensions from your columns and associations. `autoreport_on` will try to map
184
+ most columns to dimensions, and if the column in question is for a `belongs_to`
185
+ association, will even try to join and report on the association's name:
186
+
187
+ ```ruby
188
+ class PostReport < ActiveReporter::Report
189
+ autoreport_on Post
190
+ end
191
+
192
+ PostReport.new.dimensions.keys
193
+ # => %i[:created_at, :updated_at, :likes, :title, :author]
194
+
195
+ PostReport.new.dimensions[:author].expression
196
+ # => 'users.name'
197
+ ```
198
+
199
+ Autoreport behavior can be customized by overriding certain methods; see the
200
+ `ActiveReporter::Report` code for more information.
201
+
202
+ ### Dimensions (x-axes)
203
+
204
+ You define dimensions on your `ActiveReporter::Report` to represent attributes of your
205
+ data you're interested in. Dimensions objects can filter or group your relation
206
+ by a SQL expression, and accept/return simple Ruby values of various types.
207
+
208
+ There are several built-in types of dimensions:
209
+ - `Category`
210
+ - Groups/filters the relation by the discrete values of the `expression`
211
+ - `Number`
212
+ - Groups/filters the relation by binning a continuous numeric `expression`
213
+ - `Time`
214
+ - Like number dimensions, but the bins are increments of time
215
+
216
+ You define dimensions in your report class like this:
217
+
218
+ ```ruby
219
+ class PostReport < ActiveReporter::Report
220
+ category_dimension :status
221
+ number_dimension :author_rating, expression: 'users.rating',
222
+ relation: ->(r) { r.joins(:author) }
223
+ time_dimension :publication_date, expression: 'posts.published_at'
224
+ end
225
+ ```
226
+
227
+ The SQL expression a dimension uses defaults to:
228
+ ```ruby
229
+ "#{report.table_name}.#{dimension.name}"
230
+ ```
231
+
232
+ but this can be overridden by passing an `expression` option. Additionally, if
233
+ the filtering or grouping requires joins or other SQL operations, a custom
234
+ `relation` proc can be passed, which will be called beforehand.
235
+
236
+ #### Filtering by dimensions
237
+
238
+ All dimensions can be filtered to one or more values by passing in
239
+ `params[:dimensions][<dimension name>][:only]`.
240
+
241
+ `Category#only` should be passed the exact values you'd like to filter
242
+ to (or what will map to them after connection adapter quoting).
243
+
244
+ `Number` and `Time` are "bin" dimensions, and their `only`s
245
+ should be passed one or more bin ranges. Bin ranges should be hashes of at
246
+ least one of `min` and `max`, or they should just be `nil` to explicitly select
247
+ rows for which `expression` is null. Bin range filtering is `min`-inclusive but
248
+ `max`-exclusive. For `Number`, the bin values should be numbers or
249
+ strings of digits. For `Time`, the bin values should be dates/times or
250
+ `Time.zone.parse`-able strings.
251
+
252
+ #### Grouping by dimensions
253
+
254
+ To group by a dimension, pass its `name` to `params[:groupers]`.
255
+
256
+ For bin dimensions (`Number` and `Time`), where the values
257
+ being grouped by are ranges of numbers or times, you can specify additional
258
+ options to control the width and distribution of those bins. In particular,
259
+ you can pass values to:
260
+
261
+ - `params[:dimensions][<name>][:bins]`,
262
+ - `params[:dimensions][<name>][:bin_count]`, or
263
+ - `params[:dimensions][<name>][:bin_width]`
264
+
265
+ `bins` is the most general option; you can use it to divide the full domain of
266
+ the data into non-uniform, overlapping, and even null bin ranges. It should be
267
+ passed an array of the same min/max hashes or `nil` used in filtering.
268
+
269
+ `bin_count` will divide the domain of the data into a fixed number of bins. It
270
+ should be passed a positive integer.
271
+
272
+ `bin_width` will tile the domain with bins of a fixed width. It should be
273
+ passed a positive number for `Number`s and a "duration" for
274
+ `Time`s. Durations can either be strings of a number followed by a time
275
+ increment (minutes, hours, days, weeks, months, years), or they can be hashes
276
+ suitable for use with
277
+ [`ActiveSupport::TimeWithZone#advance`](http://apidock.com/rails/ActiveSupport/TimeWithZone/advance).
278
+ E.g.:
279
+
280
+ ```
281
+ params[:dimensions][<time dimension>][:bin_width] = '1 month'
282
+ params[:dimensions][<time dimension>][:bin_width] = { months: 2, hours: 2 }
283
+ ```
284
+
285
+ `Number`s will default to using 10 bins and `Time`s will
286
+ default to using a sensical increment of time given the domain; you can
287
+ customize this by overriding methods in those classes.
288
+
289
+ Note that when you inspect `report.data` after grouping by a bin dimension, you
290
+ will see the dimension values are actually `ActiveReporter::Bin::Base` objects,
291
+ which respond to `min`, `max`, and various json/Hash methods. These are meant
292
+ to provide a common interface for the different types of bins (double-bounded,
293
+ unbounded on one side, null) and handle mapping between SQL and Ruby
294
+ representations of their values. You may find bin objects useful in working
295
+ with report data, and they can also be customized.
296
+
297
+ If you want to change how `ActiveReporter` maps SQL values to the dimension values of
298
+ `report.data`, you can override `YourDimension#sanitize_sql_value`.
299
+
300
+ #### Customizing dimensions
301
+
302
+ You can define custom dimension classes by inheriting from one of the existing
303
+ ones:
304
+ ```ruby
305
+ class CaseInsensitiveCategoryDimension < ActiveReporter::Dimension::Category
306
+ def order_expression
307
+ "UPPER(#{super})"
308
+ end
309
+ end
310
+ ```
311
+
312
+ You can then use it in the definition of a report class like this:
313
+ ```ruby
314
+ class UserReport < ActiveReporter::Report
315
+ dimension :last_name, CaseInsensitiveCategoryDimension
316
+ end
317
+ ```
318
+
319
+ Common methods to override include `order_expression`, `sanitize_sql_value`,
320
+ `validate_params!`, `group_values`, and `default_bin_width`.
321
+
322
+ Note that if you inherit directly from `ActiveReporter::Dimension::Base`, you
323
+ will need to implement (at a minimum) `filter(relation)`, `group(relation)`, and
324
+ `group_values`. See the base dimension class for more details.
325
+
326
+ If you want custom behavior for bins, you can define `Set` and `Table`
327
+ classes nested inside your custom dimension classes (or override methods
328
+ directly on `ActiveReporter::Dimension::Bin::Set(Table)`,
329
+ `ActiveReporter::Dimension::Time::Set(Table)`, etc). See the relevant classes for more
330
+ details.
331
+
332
+ ### Aggregators (y-axes)
333
+
334
+ Aggregators take your groups and reduce them down to a single value. They
335
+ represent the quantities you're looking to measure across your dimensions.
336
+
337
+ There are several built-in types of aggregators:
338
+
339
+ - `Aggregator::Count`
340
+ - counts the number of distinct records in each group
341
+ - `Aggregator::Sum`
342
+ - sums an `expression` over each distinct record in each group
343
+ - `Aggregator::Average`
344
+ - sum divided by count
345
+ - `Aggregator::Min`
346
+ - finds the minimum value of `expression` in each group
347
+ - `Aggregator::Max`
348
+ - finds the maximum value of `expression` in each group
349
+ - `Aggregator::Array`
350
+ - returns an array of `expression` values in each group (PostgreSQL only)
351
+ - useful if you want to drill down into the data behind an aggregation
352
+
353
+ #### Customizing aggregators
354
+
355
+ By default, the `expression` will default to the aggregator name, but you can
356
+ achieve some level of customization by passing in `expression` or `relation`:
357
+
358
+ ```ruby
359
+ max_aggregator :max_likes, expression: 'posts.likes'
360
+
361
+ sum_aggregator :total_cost,
362
+ expression: 'invoices.hours_worked * invoices.hourly_rate'
363
+
364
+ avg_aggregator :mean_author_age, expression: 'AGE(users.dob)',
365
+ relation: ->(r) { r.joins(:author) }
366
+ ```
367
+
368
+ You can also define your own aggregator type if none of the existing ones meet
369
+ your needs:
370
+
371
+ ```ruby
372
+ class LengthAggregator < ActiveReporter::Aggregators::BaseAggregator
373
+ def aggregate(grouped_relation)
374
+ # check out the other aggregators for examples of what to do here.
375
+ end
376
+ end
377
+
378
+ # then:
379
+ aggregator :name_length, LengthAggregator, expression: 'posts.name'
380
+ ```
381
+
382
+ ## Serializing reports
383
+
384
+ After defining and running a report, you can wrap it in a serializer to get its
385
+ data in a more useful format.
386
+
387
+ `Serializer::Table` defines `caption`, `headers`, and `each_row`, which can be
388
+ used to construct a table. It also wraps dimension and aggregator names and
389
+ values in formatting methods, which can be overridden, e.g. if you would like to
390
+ use I18n for date or enum column formatting. You can override these methods on
391
+ `Serializer::Base` if you would like them to apply everywhere.
392
+
393
+ `Serializer::Csv` dumps the data from `Serializer::Table` to a CSV string or file.
394
+
395
+ `Serializer::Highcharts` can map reports with 1-3 grouping dimensions to options
396
+ for passing into the Highcharts charting library. Extra options included with
397
+ the raw data makes it easy to implement features like detailed tooltips and
398
+ drilldown.
399
+
400
+ `Serializer::FormField` represents report parameters as HTML form fields. Likely
401
+ you will want to implement your own form logic specific to your report class
402
+ and application design, but it provides an easy and somewhat extensible way to
403
+ get up and running.
404
+
405
+ See the serializer class files for more documentation.
406
+
407
+ ## Contributing
408
+
409
+ If you have suggestions for how to make any part of this library better, or if
410
+ you want to contribute extra dimensions, aggregators, serializers, please
411
+ submit them in a pull request (with test coverage).
412
+
413
+ To work on developing `ActiveReporter`, you will need to have Ruby and PostgreSQL,
414
+ MySQL, or SQLite3 installed. Then clone the repository and run:
415
+ ```sh
416
+ bundle install
417
+ cd spec/dummy
418
+ DB=<your db type> bundle exec rake db:create db:schema:load db:test:prepare
419
+ cd ../..
420
+ DB=<your db type> bundle exec rspec
421
+ ```
422
+
423
+ which will run the test suite. The options for `DB` are `sqlite`, `mysql`, and
424
+ `postgres` (the default). Preferably you should run it against all three, but
425
+ CI will also do so.
426
+
427
+ To see the dummy application in development mode, you can run:
428
+ ```sh
429
+ cd spec/dummy
430
+ DB=<your db type> bundle exec rake db:setup
431
+ DB=<your db type> bundle exec rails server
432
+ ```
433
+
434
+ ## License
435
+
436
+ [MIT](http://opensource.org/licenses/MIT)