active_reporting 0.4.1 → 0.6.1
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.
- checksums.yaml +4 -4
- data/.travis.yml +5 -17
- data/CHANGELOG.md +45 -0
- data/Gemfile +1 -17
- data/README.md +50 -22
- data/active_reporting.gemspec +1 -1
- data/lib/active_reporting.rb +1 -0
- data/lib/active_reporting/dimension.rb +0 -7
- data/lib/active_reporting/fact_model.rb +2 -0
- data/lib/active_reporting/report.rb +8 -5
- data/lib/active_reporting/reporting_dimension.rb +127 -37
- data/lib/active_reporting/version.rb +1 -1
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bd8ff0998a757ff875a17baad2fcb148ace4239da0781e1b7fbdd4c6367cef2c
|
4
|
+
data.tar.gz: 316ee68af897e1b866b79b28d3f0889f116982a48b94a123cde5170ac851b4e1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 73f92eed3389912530e9ff393e761bbc6e86a84e1c6beff4428de03f295a2fe91f2b230d25d8a3d0b783761b099dca43b4bc62ea05538a469c925eae5c9044d2
|
7
|
+
data.tar.gz: b1217306b02aa8e40b78cea0d0d31dca84621c4b676e79a638bf8644891e301a3d850e2f645d9c0b745724c4a03c0837936cbe1776e9794a89e4e73580589256
|
data/.travis.yml
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
sudo: false
|
2
2
|
language: ruby
|
3
|
+
services:
|
4
|
+
- mysql
|
3
5
|
addons:
|
4
6
|
postgresql: "9.6"
|
5
7
|
rvm:
|
6
|
-
- 2.
|
7
|
-
- 2.
|
8
|
-
- 2.
|
8
|
+
- 2.5.8
|
9
|
+
- 2.6.6
|
10
|
+
- 2.7.1
|
9
11
|
env:
|
10
12
|
- RAILS=6-0 DB=sqlite
|
11
13
|
- RAILS=6-0 DB=pg
|
@@ -13,20 +15,6 @@ env:
|
|
13
15
|
- RAILS=5-2 DB=sqlite
|
14
16
|
- RAILS=5-2 DB=pg
|
15
17
|
- RAILS=5-2 DB=mysql
|
16
|
-
- RAILS=5-1 DB=sqlite
|
17
|
-
- RAILS=5-1 DB=pg
|
18
|
-
- RAILS=5-1 DB=mysql
|
19
|
-
- RAILS=4-2 DB=sqlite
|
20
|
-
- RAILS=4-2 DB=pg
|
21
|
-
- RAILS=4-2 DB=mysql
|
22
|
-
matrix:
|
23
|
-
exclude:
|
24
|
-
- rvm: 2.4.6
|
25
|
-
env: RAILS=6-0 DB=mysql
|
26
|
-
- rvm: 2.4.6
|
27
|
-
env: RAILS=6-0 DB=pg
|
28
|
-
- rvm: 2.4.6
|
29
|
-
env: RAILS=6-0 DB=sqlite
|
30
18
|
before_script:
|
31
19
|
- psql -c 'create database active_reporting_test;' -U postgres
|
32
20
|
- mysql -e 'create database active_reporting_test collate utf8_general_ci;'
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,48 @@
|
|
1
|
+
## 0.6.1 (2020-08-29)
|
2
|
+
|
3
|
+
### Misc
|
4
|
+
|
5
|
+
* Add `date` as an option for datetime drills - *germanotm*
|
6
|
+
|
7
|
+
## 0.6.0 (2020-08-21)
|
8
|
+
|
9
|
+
### Features
|
10
|
+
|
11
|
+
* Support to implicit hierarchical on datetime columns in MySQL (#33) - *germanotm*
|
12
|
+
* Added `{ datetime_drill: :month }` option for reporting dimentions to explicitly - *germanotm*
|
13
|
+
|
14
|
+
This depricates the use of key-value only use for report dimension options (ie, `dimensions: [{ dim: single_option }]`).
|
15
|
+
Instead, use `dimensions: [{ dim: { option: value} }]` See the README for all reporting dimension options.
|
16
|
+
|
17
|
+
## 0.5.1 (2020-06-31)
|
18
|
+
|
19
|
+
### Features
|
20
|
+
|
21
|
+
* Allow dimensions defined in a `Metric` to use LEFT OUTER JOINs via a new `:join_method` option (#32) - *germanotm*
|
22
|
+
|
23
|
+
### Misc
|
24
|
+
|
25
|
+
* Fixed warning about initialized variables
|
26
|
+
* Fixed Ruby 2.7 warning
|
27
|
+
|
28
|
+
## 0.5.0 (2020-06-30)
|
29
|
+
|
30
|
+
### Bug Fixes
|
31
|
+
|
32
|
+
* Fix Missing quotation marks in column names causing SQL errors on MYSQL (#30) - *germanotm*
|
33
|
+
|
34
|
+
### Misc
|
35
|
+
|
36
|
+
* Update matrix to only supported Rubies and Rails versions. Rails 5.2+ and Ruby 2.5+ are officially supported now.
|
37
|
+
|
38
|
+
## 0.4.2 (2019-11-01)
|
39
|
+
|
40
|
+
### Misc
|
41
|
+
|
42
|
+
* Test against Rails 6.0 final
|
43
|
+
* Fixed deprecated call to `to_hash` - *joshforbes*
|
44
|
+
* Corrected readme entry for `dimesions` option for `ActiveReporting::Metric` - *joshforbes*
|
45
|
+
|
1
46
|
## 0.4.1 (2019-05-28)
|
2
47
|
|
3
48
|
### Features
|
data/Gemfile
CHANGED
@@ -8,24 +8,8 @@ rails = ENV['RAILS'] || '5-2'
|
|
8
8
|
db = ENV['DB'] || 'sqlite'
|
9
9
|
|
10
10
|
case rails
|
11
|
-
when '4-2'
|
12
|
-
gem 'activerecord', '~> 4.2.0'
|
13
|
-
when '5-1'
|
14
|
-
gem 'activerecord', '~> 5.1.0'
|
15
11
|
when '5-2'
|
16
12
|
gem 'activerecord', '~> 5.2.0'
|
17
13
|
when '6-0'
|
18
|
-
gem 'activerecord', '6.0.0
|
19
|
-
end
|
20
|
-
|
21
|
-
case rails
|
22
|
-
when '4-2'
|
23
|
-
case ENV['DB']
|
24
|
-
when 'pg'
|
25
|
-
gem 'pg', '~> 0.18'
|
26
|
-
when 'mysql'
|
27
|
-
gem 'mysql2', '~> 0.3.18'
|
28
|
-
when 'sqlite'
|
29
|
-
gem 'sqlite3', '~> 1.3.0'
|
30
|
-
end
|
14
|
+
gem 'activerecord', '~> 6.0.0'
|
31
15
|
end
|
data/README.md
CHANGED
@@ -8,9 +8,9 @@ ActiveReporting implements various terminology used in Relational Online Analyti
|
|
8
8
|
|
9
9
|
ActiveReporting officially supports MySQL, PostgreSQL, and SQLite.
|
10
10
|
|
11
|
-
ActiveReporting officially supports Ruby 2.
|
11
|
+
ActiveReporting officially supports Ruby 2.5 and later. Other versions may work, but are not supported.
|
12
12
|
|
13
|
-
ActiveReporting officially supports Rails
|
13
|
+
ActiveReporting officially supports Rails 5.2, and 6.0. Other versions may work, but are not supported.
|
14
14
|
|
15
15
|
## Installation
|
16
16
|
|
@@ -49,8 +49,9 @@ Rails: ActiveRecord model
|
|
49
49
|
A dimension is a point of data used to "slice and dice" data from a fact model. It's either a column that lives on the fact table or a foreign key to another table.
|
50
50
|
|
51
51
|
Examples:
|
52
|
-
|
53
|
-
* A
|
52
|
+
|
53
|
+
* A sales rep on a fact table of sales
|
54
|
+
* A state of an sale on a state machine
|
54
55
|
* The manufacture on a fact table of widgets
|
55
56
|
|
56
57
|
SQL Equivalent: JOIN, GROUP BY
|
@@ -62,6 +63,7 @@ Rails: ActiveRecord relation or attribute
|
|
62
63
|
A hierarchy for a dimension is related attributes that live on a dimension table used to drill down and drill up through a dimension.
|
63
64
|
|
64
65
|
Examples:
|
66
|
+
|
65
67
|
* Dates: Date, Month, Year, Quarter
|
66
68
|
* Mobile Phone: Model, Manufacture, OS, Wireless Technology
|
67
69
|
|
@@ -70,6 +72,7 @@ Examples:
|
|
70
72
|
This is information related to a dimension. When the dimension lives on the fact table, the label is the column used. When the dimension is a related table, the label is a column representing the hierarchy level.
|
71
73
|
|
72
74
|
Examples:
|
75
|
+
|
73
76
|
* When dimensioning blog posts by category, the dimension is the category_id which leads to the categories table. The label would be the category name.
|
74
77
|
|
75
78
|
### Dimension Filter (or just "filter")
|
@@ -85,6 +88,7 @@ Rails: `where()`, scopes, etc.
|
|
85
88
|
A measure is a column in a fact table (usually a numeric value) used in aggregations such as sum, maximum, average, etc.
|
86
89
|
|
87
90
|
Examples:
|
91
|
+
|
88
92
|
* Total amount in a sale
|
89
93
|
* Number of units used in a transaction
|
90
94
|
|
@@ -178,7 +182,7 @@ end
|
|
178
182
|
ActiveReporting assumes the column of a fact model used for summing, averaging, etc. is called `value`. This may be changed on a fact model using `measure=`. You may pass in a string or symbol of the column you wish to use for aggregations.
|
179
183
|
|
180
184
|
```ruby
|
181
|
-
class
|
185
|
+
class SaleFactModel < ActiveReporting::FactModel
|
182
186
|
self.measure = :total
|
183
187
|
end
|
184
188
|
```
|
@@ -219,11 +223,9 @@ class PhoneFactModel < ActiveReporting::FactModel
|
|
219
223
|
end
|
220
224
|
```
|
221
225
|
|
222
|
-
###
|
226
|
+
### Drill down / Roll up (Drill up) with datetime columns
|
223
227
|
|
224
|
-
The fastest approach to group by certain date metrics is to create so-called "date dimensions". For
|
225
|
-
those Postgres users that are restricted from organizing their data in this way, Postgres provides
|
226
|
-
a way to group by `datetime` column data on the fly using the `date_trunc` function.
|
228
|
+
The fastest approach to group by certain date metrics is to create so-called "date dimensions" and add on columns for each desired hierarchy. For those users that are restricted from organizing their data in this way, ActiveRporting provides a `datetime_drill` option that can be passed with the dimension on the metric definition to drill datetime columns.
|
227
229
|
|
228
230
|
To use, declare a datetime dimension on a fact model as normal:
|
229
231
|
|
@@ -233,7 +235,24 @@ class UserFactModel < ActiveReporting::FactModel
|
|
233
235
|
end
|
234
236
|
```
|
235
237
|
|
236
|
-
When creating a metric, ActiveReporting will recognize
|
238
|
+
When creating a metric, ActiveReporting will recognize the following datetime hierarchies: (See example under the metric section, below.)
|
239
|
+
|
240
|
+
- microseconds
|
241
|
+
- milliseconds
|
242
|
+
- second
|
243
|
+
- minute
|
244
|
+
- hour
|
245
|
+
- day
|
246
|
+
- week
|
247
|
+
- month
|
248
|
+
- quarter
|
249
|
+
- year
|
250
|
+
- decade
|
251
|
+
- century
|
252
|
+
- millennium
|
253
|
+
- date
|
254
|
+
|
255
|
+
Under the hood Active Reporting uses specific database functions to manipulate datetime columns. Postgres provides a way to group by `datetime` column data on the fly using the [`date_trunc` function](https://www.postgresql.org/docs/8.1/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC). On Mysql this can be done using [Date and Time Functions](https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html).
|
237
256
|
|
238
257
|
*NOTE*: PRs welcomed to support this functionality in other databases.
|
239
258
|
|
@@ -273,8 +292,8 @@ A `Metric` is the basic building block used to describe a question you want to a
|
|
273
292
|
|
274
293
|
```ruby
|
275
294
|
my_metric = ActiveReporting::Metric.new(
|
276
|
-
:
|
277
|
-
fact_model:
|
295
|
+
:sale_total,
|
296
|
+
fact_model: SaleFactModel,
|
278
297
|
aggregate: :sum
|
279
298
|
)
|
280
299
|
```
|
@@ -285,7 +304,17 @@ my_metric = ActiveReporting::Metric.new(
|
|
285
304
|
|
286
305
|
`aggregate` - The SQL aggregate used to calculate the metric. Supported aggregates include count, max, min, avg, and sum. (Default: `:count`)
|
287
306
|
|
288
|
-
`dimensions` - An array of dimensions used for the metric. When given just a symbol, the default dimension label will be used for the dimension.
|
307
|
+
`dimensions` - An array of dimensions used for the metric. When given just a symbol, the default dimension label will be used for the dimension.
|
308
|
+
|
309
|
+
You may pass a hash instead of a symbol to customize the dimension options (example: { dimension_name: { option1: value, option2: value}}). The avaliable options are:
|
310
|
+
|
311
|
+
- `field` - Specify the hierarchy level that should be used instead the default dimension label. Ex: `[:sales_rep, {mobile_phone: { field :manufacture }}]`. If you use a hash instead of a Symbol to define a hierarchy the `field` item must be a valid field in your table.
|
312
|
+
|
313
|
+
- `name` - You may costumize the label alias, by default the dimension name will be used. The `name` can be whatever label you want. Ex :`[{sale_date: { field: :month, name: :a_custom_name_for_month }}]`.
|
314
|
+
|
315
|
+
- `join_method` - You may choose the join_method with the dimension. The default value for join_method is :joins which does a standard "INNER JOIN", but you can pass a :left_outer_joins to use "LEFT OUTER JOIN" instead. Ex: `[{sales_rep: { join_method: :left_outer_joins }}]`
|
316
|
+
|
317
|
+
- `datetime_drill` - To drill up and down over datetime column you may pass a `datetime_drill`. Ex: `[:sales_rep, { order: { field: :created_at, datetime_drill: :month }}]`. This option will perform an implicit drill over datetime columns and not a date dimension relationship.
|
289
318
|
|
290
319
|
`dimension_filter` - A hash were the keys are dimension filter names and the values are the values passed into the filter.
|
291
320
|
|
@@ -303,7 +332,7 @@ end
|
|
303
332
|
my_metric = ActiveReporting::Metric.new(
|
304
333
|
:my_total,
|
305
334
|
fact_model: UserFactModel,
|
306
|
-
dimensions: [{ created_at: :quarter } ]
|
335
|
+
dimensions: [{ created_at: { datetime_drill: :quarter }} ]
|
307
336
|
)
|
308
337
|
```
|
309
338
|
|
@@ -313,20 +342,20 @@ A `Report` takes an `ActiveReporting::Metric` and ties everything together. It i
|
|
313
342
|
|
314
343
|
```ruby
|
315
344
|
metric = ActiveReporting::Metric.new(
|
316
|
-
:
|
317
|
-
fact_model:
|
345
|
+
:sale_count,
|
346
|
+
fact_model: SaleFactModel,
|
318
347
|
dimensions: [:sales_rep],
|
319
348
|
dimension_filter: {months_ago: 1}
|
320
349
|
)
|
321
350
|
|
322
351
|
report = ActiveReporting::Report.new(metric)
|
323
352
|
report.run
|
324
|
-
=> [{
|
353
|
+
=> [{sale_count: 12, sales_rep: 'Fred Jones', sales_rep_identifier: 123},{sale_count: 17, sales_rep: 'Mary Sue', sales_rep_identifier: 123}]
|
325
354
|
```
|
326
355
|
|
327
356
|
A `Report` may also take additional arguments to merge with the `Metric`'s information. This can be user input for additional filters, or to expand on a base `Metric`.
|
328
357
|
|
329
|
-
`dimension_identifiers` - When true, the result will include the database identifier columns of the dimensions. For example, when running a report for the total number of
|
358
|
+
`dimension_identifiers` - When true, the result will include the database identifier columns of the dimensions. For example, when running a report for the total number of sales dimensioned by sales rep, the rep's IDs from the `sales_reps` table will be included. (Default `true`)
|
330
359
|
|
331
360
|
`dimension_filter` - A hash that will be merged with the `Metric`'s dimension filters.
|
332
361
|
|
@@ -336,15 +365,15 @@ A `Report` may also take additional arguments to merge with the `Metric`'s infor
|
|
336
365
|
|
337
366
|
```ruby
|
338
367
|
metric = ActiveReporting::Metric.new(
|
339
|
-
:
|
340
|
-
fact_model:
|
368
|
+
:sale_count,
|
369
|
+
fact_model: SaleFactModel,
|
341
370
|
dimensions: [:sales_rep],
|
342
371
|
dimension_filter: {months_ago: 1}
|
343
372
|
)
|
344
373
|
|
345
374
|
report = ActiveReporting::Report.new(metric, dimension_filter: {from_region: 'North'}, dimension_identifiers: false)
|
346
375
|
report.run
|
347
|
-
=> [{
|
376
|
+
=> [{sale_count: 17, sales_rep: 'Mary Sue'}]
|
348
377
|
```
|
349
378
|
|
350
379
|
It may be more DRY to store ready-made metrics in a database table or stored in memory to use as the bases for various reports. You can pass a string or symbol into a `Report` instead of a `Metric` to look up an pre-made metric. This is done by passing the symbol or string into the `lookup` class method on the constant defined in `ActiveReporting::Configuration.metric_lookup_class`.
|
@@ -376,7 +405,6 @@ appropriate `DB` environment variable, e.g. `DB=pg rake test`.
|
|
376
405
|
|
377
406
|
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/active_reporting. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
378
407
|
|
379
|
-
|
380
408
|
## License
|
381
409
|
|
382
410
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/active_reporting.gemspec
CHANGED
@@ -31,7 +31,7 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.add_development_dependency 'minitest', '~> 5.0'
|
32
32
|
spec.add_development_dependency 'mysql2'
|
33
33
|
spec.add_development_dependency 'pg'
|
34
|
-
spec.add_development_dependency 'rake'
|
34
|
+
spec.add_development_dependency 'rake'
|
35
35
|
spec.add_development_dependency 'ransack'
|
36
36
|
spec.add_development_dependency 'sqlite3'
|
37
37
|
end
|
data/lib/active_reporting.rb
CHANGED
@@ -30,13 +30,6 @@ module ActiveReporting
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
-
# Whether the dimension is a datetime column
|
34
|
-
#
|
35
|
-
# @return [Boolean]
|
36
|
-
def datetime?
|
37
|
-
@datetime ||= type == TYPES[:degenerate] && model.column_for_attribute(@name).type == :datetime
|
38
|
-
end
|
39
|
-
|
40
33
|
# Tells if the dimension is hierarchical
|
41
34
|
#
|
42
35
|
# @return [Boolean]
|
@@ -85,6 +85,7 @@ module ActiveReporting
|
|
85
85
|
#
|
86
86
|
# @return [Symbol]
|
87
87
|
def self.dimension_label
|
88
|
+
@dimension_label ||= nil
|
88
89
|
@dimension_label || Configuration.default_dimension_label
|
89
90
|
end
|
90
91
|
|
@@ -155,6 +156,7 @@ module ActiveReporting
|
|
155
156
|
#
|
156
157
|
# @return [Boolean]
|
157
158
|
def self.ransack_fallback
|
159
|
+
@ransack_fallback ||= false
|
158
160
|
@ransack_fallback || Configuration.ransack_fallback
|
159
161
|
end
|
160
162
|
private_class_method :ransack_fallback
|
@@ -36,7 +36,7 @@ module ActiveReporting
|
|
36
36
|
private ######################################################################
|
37
37
|
|
38
38
|
def build_data
|
39
|
-
@data = model.connection.exec_query(statement.to_sql).
|
39
|
+
@data = model.connection.exec_query(statement.to_sql).to_a
|
40
40
|
apply_dimension_callbacks
|
41
41
|
@data
|
42
42
|
end
|
@@ -52,7 +52,8 @@ module ActiveReporting
|
|
52
52
|
def statement
|
53
53
|
parts = {
|
54
54
|
select: select_statement,
|
55
|
-
joins: dimension_joins,
|
55
|
+
joins: dimension_joins(ReportingDimension::JOIN_METHODS[:joins]),
|
56
|
+
left_outer_joins: dimension_joins(ReportingDimension::JOIN_METHODS[:left_outer_joins]),
|
56
57
|
group: group_by_statement,
|
57
58
|
having: having_statement,
|
58
59
|
order: order_by_statement
|
@@ -84,8 +85,9 @@ module ActiveReporting
|
|
84
85
|
end
|
85
86
|
end
|
86
87
|
|
87
|
-
def dimension_joins
|
88
|
-
@dimensions.select { |d| d.type == :standard
|
88
|
+
def dimension_joins(join_method)
|
89
|
+
@dimensions.select { |d| d.type == Dimension::TYPES[:standard] && d.join_method == join_method }.
|
90
|
+
map { |d| d.name.to_sym }
|
89
91
|
end
|
90
92
|
|
91
93
|
def group_by_statement
|
@@ -142,8 +144,9 @@ module ActiveReporting
|
|
142
144
|
@dimensions.each do |dimension|
|
143
145
|
callback = dimension.label_callback
|
144
146
|
next unless callback
|
147
|
+
key = "#{dimension.name}_#{dimension.label}"
|
145
148
|
@data.each do |hash|
|
146
|
-
hash[
|
149
|
+
hash[key] = callback.call(hash[key])
|
147
150
|
end
|
148
151
|
end
|
149
152
|
end
|
@@ -4,21 +4,32 @@ require 'forwardable'
|
|
4
4
|
module ActiveReporting
|
5
5
|
class ReportingDimension
|
6
6
|
extend Forwardable
|
7
|
-
SUPPORTED_DBS = %w[PostgreSQL PostGIS].freeze
|
7
|
+
SUPPORTED_DBS = %w[PostgreSQL PostGIS Mysql2].freeze
|
8
8
|
# Values for the Postgres `date_trunc` method.
|
9
9
|
# See https://www.postgresql.org/docs/10/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
|
10
10
|
DATETIME_HIERARCHIES = %i[microseconds milliseconds second minute hour day week month quarter year decade
|
11
|
-
century millennium].freeze
|
12
|
-
|
11
|
+
century millennium date].freeze
|
12
|
+
JOIN_METHODS = { joins: :joins, left_outer_joins: :left_outer_joins }.freeze
|
13
|
+
attr_reader :join_method, :label
|
14
|
+
|
15
|
+
def_delegators :@dimension, :name, :type, :klass, :association, :model, :hierarchical?
|
13
16
|
|
14
17
|
def self.build_from_dimensions(fact_model, dimensions)
|
15
18
|
Array(dimensions).map do |dim|
|
16
|
-
dimension_name,
|
19
|
+
dimension_name, options = dim.is_a?(Hash) ? Array(dim).flatten : [dim, nil]
|
17
20
|
found_dimension = fact_model.dimensions[dimension_name.to_sym]
|
18
21
|
|
19
|
-
raise(UnknownDimension, "Dimension '#{
|
20
|
-
|
21
|
-
|
22
|
+
raise(UnknownDimension, "Dimension '#{dimension_name}' not found on fact model '#{fact_model}'") if found_dimension.nil?
|
23
|
+
|
24
|
+
# Ambiguous behavior with string option for degenerate and standard dimension
|
25
|
+
if !options.is_a?(Hash) && found_dimension.type == Dimension::TYPES[:degenerate]
|
26
|
+
ActiveSupport::Deprecation.warn <<~EOS
|
27
|
+
direct use of implict hierarchies is deprecated and will be removed in future versions. \
|
28
|
+
Please use `:datetime_drill` option instead.
|
29
|
+
EOS
|
30
|
+
options = { datetime_drill: options }
|
31
|
+
end
|
32
|
+
new(found_dimension, **label_config(options))
|
22
33
|
end
|
23
34
|
end
|
24
35
|
|
@@ -26,24 +37,30 @@ module ActiveReporting
|
|
26
37
|
# the field on that dimension. With a hash you can
|
27
38
|
# customize the name of the label
|
28
39
|
#
|
29
|
-
# @param [Symbol|Hash]
|
30
|
-
def self.label_config(
|
31
|
-
|
40
|
+
# @param [Symbol|Hash] options
|
41
|
+
def self.label_config(options)
|
42
|
+
unless options.is_a?(Hash)
|
43
|
+
return { label: options }
|
44
|
+
end
|
32
45
|
|
33
46
|
{
|
34
|
-
label:
|
35
|
-
label_name:
|
47
|
+
label: options[:field],
|
48
|
+
label_name: options[:name],
|
49
|
+
join_method: options[:join_method],
|
50
|
+
datetime_drill: options[:datetime_drill]
|
36
51
|
}
|
37
52
|
end
|
38
53
|
|
39
54
|
# @param dimension [ActiveReporting::Dimension]
|
40
55
|
# @option label [Maybe<Symbol>] Hierarchical dimension to be used as a label
|
41
56
|
# @option label_name [Maybe<Symbol|String>] Hierarchical dimension custom name
|
42
|
-
def initialize(dimension, label: nil, label_name: nil)
|
57
|
+
def initialize(dimension, label: nil, label_name: nil, join_method: nil, datetime_drill: nil)
|
43
58
|
@dimension = dimension
|
44
59
|
|
45
60
|
determine_label_field(label)
|
61
|
+
determine_datetime_drill(datetime_drill)
|
46
62
|
determine_label_name(label_name)
|
63
|
+
determine_join_method(join_method)
|
47
64
|
end
|
48
65
|
|
49
66
|
# The foreign key to use in queries
|
@@ -57,10 +74,8 @@ module ActiveReporting
|
|
57
74
|
#
|
58
75
|
# @return [Array]
|
59
76
|
def select_statement(with_identifier: true)
|
60
|
-
|
61
|
-
|
62
|
-
ss = ["#{label_fragment} AS #{@label_name}"]
|
63
|
-
ss << "#{identifier_fragment} AS #{name}_identifier" if with_identifier
|
77
|
+
ss = ["#{label_fragment} AS #{label_fragment_alias}"]
|
78
|
+
ss << "#{identifier_fragment} AS #{identifier_fragment_alias}" if with_identifier && type == Dimension::TYPES[:standard]
|
64
79
|
ss
|
65
80
|
end
|
66
81
|
|
@@ -68,10 +83,8 @@ module ActiveReporting
|
|
68
83
|
#
|
69
84
|
# @return [Array]
|
70
85
|
def group_by_statement(with_identifier: true)
|
71
|
-
return [degenerate_fragment] if type == Dimension::TYPES[:degenerate]
|
72
|
-
|
73
86
|
group = [label_fragment]
|
74
|
-
group << identifier_fragment if with_identifier
|
87
|
+
group << identifier_fragment if with_identifier && type == Dimension::TYPES[:standard]
|
75
88
|
group
|
76
89
|
end
|
77
90
|
|
@@ -81,7 +94,6 @@ module ActiveReporting
|
|
81
94
|
def order_by_statement(direction:)
|
82
95
|
direction = direction.to_s.upcase
|
83
96
|
raise "Ording direction should be 'asc' or 'desc'" unless %w[ASC DESC].include?(direction)
|
84
|
-
return "#{degenerate_fragment} #{direction}" if type == Dimension::TYPES[:degenerate]
|
85
97
|
"#{label_fragment} #{direction}"
|
86
98
|
end
|
87
99
|
|
@@ -96,24 +108,47 @@ module ActiveReporting
|
|
96
108
|
|
97
109
|
def determine_label_field(label_field)
|
98
110
|
@label = if label_field.present? && validate_hierarchical_label(label_field)
|
99
|
-
label_field.to_sym
|
111
|
+
type == Dimension::TYPES[:degenerate] ? name : label_field.to_sym
|
112
|
+
elsif type == Dimension::TYPES[:degenerate]
|
113
|
+
name
|
100
114
|
else
|
101
115
|
dimension_fact_model.dimension_label || Configuration.default_dimension_label
|
102
116
|
end
|
103
117
|
end
|
104
118
|
|
105
119
|
def determine_label_name(label_name)
|
106
|
-
|
120
|
+
|
121
|
+
if label_name
|
122
|
+
@label_name = label_name
|
123
|
+
else
|
124
|
+
@label_name = name
|
125
|
+
@label_name += "_#{@label}" if (type == Dimension::TYPES[:standard] && @label != :name)
|
126
|
+
@label_name += "_#{@datetime_drill}" if @datetime_drill
|
127
|
+
end
|
128
|
+
@label_name
|
107
129
|
end
|
108
130
|
|
109
|
-
def
|
110
|
-
|
111
|
-
|
112
|
-
|
131
|
+
def determine_datetime_drill(datetime_drill)
|
132
|
+
return unless datetime_drill
|
133
|
+
validate_supported_database_for_datetime_hierarchies
|
134
|
+
validate_against_datetime_hierarchies(datetime_drill)
|
135
|
+
validate_label_is_datetime
|
136
|
+
@datetime_drill = datetime_drill
|
137
|
+
end
|
138
|
+
|
139
|
+
def determine_join_method(join_method)
|
140
|
+
if join_method.blank?
|
141
|
+
@join_method = ReportingDimension::JOIN_METHODS[:joins]
|
142
|
+
elsif ReportingDimension::JOIN_METHODS.include?(join_method)
|
143
|
+
@join_method = join_method
|
113
144
|
else
|
114
|
-
|
115
|
-
validate_against_fact_model_properties(hierarchical_label)
|
145
|
+
raise UnknownJoinMethod, "Method '#{join_method}' not included in '#{ReportingDimension::JOIN_METHODS.values}'"
|
116
146
|
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def validate_hierarchical_label(hierarchical_label)
|
150
|
+
validate_dimension_is_hierachical(hierarchical_label)
|
151
|
+
validate_against_fact_model_properties(hierarchical_label)
|
117
152
|
true
|
118
153
|
end
|
119
154
|
|
@@ -134,27 +169,82 @@ module ActiveReporting
|
|
134
169
|
raise InvalidDimensionLabel, "#{hierarchical_label} is not a valid datetime grouping label in #{name}"
|
135
170
|
end
|
136
171
|
|
172
|
+
def validate_label_is_datetime
|
173
|
+
return if dimension_fact_model.model.column_for_attribute(@label).type == :datetime
|
174
|
+
raise InvalidDimensionLabel, "'#{@label}' is not a datetime column"
|
175
|
+
end
|
176
|
+
|
137
177
|
def validate_against_fact_model_properties(hierarchical_label)
|
138
178
|
return if dimension_fact_model.hierarchical_levels.include?(hierarchical_label.to_sym)
|
139
179
|
raise InvalidDimensionLabel, "#{hierarchical_label} is not a hierarchical label in #{name}"
|
140
180
|
end
|
141
181
|
|
142
|
-
def
|
143
|
-
|
144
|
-
|
182
|
+
def datetime_drill_label_fragment(column)
|
183
|
+
if model.connection.adapter_name == "Mysql2"
|
184
|
+
datetime_drill_mysql(column)
|
185
|
+
else # Postgress
|
186
|
+
datetime_drill_postgress(column)
|
187
|
+
end
|
145
188
|
end
|
146
189
|
|
147
|
-
def
|
148
|
-
|
149
|
-
|
190
|
+
def datetime_drill_postgress(column)
|
191
|
+
case @datetime_drill.to_sym
|
192
|
+
when :date
|
193
|
+
"DATE('#{column}')"
|
194
|
+
else
|
195
|
+
"DATE_TRUNC('#{@datetime_drill}', #{column})"
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def datetime_drill_mysql(column)
|
200
|
+
case @datetime_drill.to_sym
|
201
|
+
when :microseconds
|
202
|
+
"MICROSECOND(#{column})"
|
203
|
+
when :milliseconds
|
204
|
+
"MICROSECOND(#{column}) DIV 1000"
|
205
|
+
when :second
|
206
|
+
"SECOND(#{column})"
|
207
|
+
when :minute
|
208
|
+
"MINUTE(#{column})"
|
209
|
+
when :hour
|
210
|
+
"HOUR(#{column})"
|
211
|
+
when :day
|
212
|
+
"DAY(#{column})"
|
213
|
+
when :week
|
214
|
+
"WEEKDAY(#{column})"
|
215
|
+
when :month
|
216
|
+
"MONTH(#{column})"
|
217
|
+
when :quarter
|
218
|
+
"QUARTER(#{column})"
|
219
|
+
when :year
|
220
|
+
"YEAR(#{column})"
|
221
|
+
when :decade
|
222
|
+
"YEAR(#{column}) DIV 10"
|
223
|
+
when :century
|
224
|
+
"YEAR(#{column}) DIV 100"
|
225
|
+
when :millennium
|
226
|
+
"YEAR(#{column}) DIV 1000"
|
227
|
+
when :date
|
228
|
+
"DATE(#{column})"
|
229
|
+
end
|
150
230
|
end
|
151
231
|
|
152
232
|
def identifier_fragment
|
153
|
-
"#{klass.quoted_table_name}.#{klass.primary_key}"
|
233
|
+
"#{klass.quoted_table_name}.#{model.connection.quote_column_name(klass.primary_key)}"
|
234
|
+
end
|
235
|
+
|
236
|
+
def identifier_fragment_alias
|
237
|
+
"#{model.connection.quote_column_name("#{name}_identifier")}"
|
154
238
|
end
|
155
239
|
|
156
240
|
def label_fragment
|
157
|
-
"#{klass.quoted_table_name}.#{@label}"
|
241
|
+
fragment = "#{klass.quoted_table_name}.#{model.connection.quote_column_name(@label)}"
|
242
|
+
fragment = datetime_drill_label_fragment(fragment) if @datetime_drill
|
243
|
+
fragment
|
244
|
+
end
|
245
|
+
|
246
|
+
def label_fragment_alias
|
247
|
+
"#{model.connection.quote_column_name(@label_name)}"
|
158
248
|
end
|
159
249
|
|
160
250
|
def dimension_fact_model
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_reporting
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tony Drake
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-08-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -98,16 +98,16 @@ dependencies:
|
|
98
98
|
name: rake
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
|
-
- - "
|
101
|
+
- - ">="
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: '
|
103
|
+
version: '0'
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
|
-
- - "
|
108
|
+
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: '
|
110
|
+
version: '0'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: ransack
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -185,7 +185,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
185
185
|
- !ruby/object:Gem::Version
|
186
186
|
version: '0'
|
187
187
|
requirements: []
|
188
|
-
rubygems_version: 3.0.
|
188
|
+
rubygems_version: 3.0.3
|
189
189
|
signing_key:
|
190
190
|
specification_version: 4
|
191
191
|
summary: Add relational OLAP-like functionality for ActiveRecord
|