active_reporting 0.4.1 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4470ea5ed19154c0f73c4205dfb251f327a076563eeee479a8ebf1410cfd6c81
4
- data.tar.gz: '038914792fde14a53347f7952336a68e3d2520a171dbb2a620342ba85dffc144'
3
+ metadata.gz: bd8ff0998a757ff875a17baad2fcb148ace4239da0781e1b7fbdd4c6367cef2c
4
+ data.tar.gz: 316ee68af897e1b866b79b28d3f0889f116982a48b94a123cde5170ac851b4e1
5
5
  SHA512:
6
- metadata.gz: 0dafa3b0a2d8a9731f14c51e1505bdf79b79cb52fb9b9ecabe3c8006340f946b60638090b775a830a8952f7dce1c32f75608eb488894e08d899e0cc6b5b0d5e5
7
- data.tar.gz: a4a2e65604d42d290582c50954aa66f991350bf5efbfd47887ae4534d4ed7811dc70538371cd693650124af7f1ef0843ebd4c62a84111527d35c268231a79a11
6
+ metadata.gz: 73f92eed3389912530e9ff393e761bbc6e86a84e1c6beff4428de03f295a2fe91f2b230d25d8a3d0b783761b099dca43b4bc62ea05538a469c925eae5c9044d2
7
+ data.tar.gz: b1217306b02aa8e40b78cea0d0d31dca84621c4b676e79a638bf8644891e301a3d850e2f645d9c0b745724c4a03c0837936cbe1776e9794a89e4e73580589256
@@ -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.4.6
7
- - 2.5.5
8
- - 2.6.3
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;'
@@ -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.rc1'
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.4 and later. Other versions may work, but are not supported.
11
+ ActiveReporting officially supports Ruby 2.5 and later. Other versions may work, but are not supported.
12
12
 
13
- ActiveReporting officially supports Rails 4.2, 5.1, and 5.2. Other versions may work, but are not supported.
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
- * A sales rep on a fact table of orders
53
- * A state of an order on a state machine
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 OrderFactModel < ActiveReporting::FactModel
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
- ### Implicit hierarchies with datetime columns (PostgreSQL support only)
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 implicit hierarchies for this dimension. The hierarchies correspond to the [values](https://www.postgresql.org/docs/8.1/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC) supported by PostgreSQL. (See example under the metric section, below.)
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
- :order_total,
277
- fact_model: OrderFactModel,
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. You may specify a hierarchy level by using a hash. (Examples: `[:sales_rep, {order_date: :month}]`). In hierarchies you can customize the label in this way: `[{order_date: { label: :month, name: :a_custom_name_for_month }}]`. If you use a hash instead of a Symbol to define a hierarchy the `label` item must be a valid field in your table. The `name` can be whatever you want.
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
- :order_count,
317
- fact_model: OrderFactModel,
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
- => [{order_count: 12, sales_rep: 'Fred Jones', sales_rep_identifier: 123},{order_count: 17, sales_rep: 'Mary Sue', sales_rep_identifier: 123}]
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 orders dimensioned by sales rep, the rep's IDs from the `sales_reps` table will be included. (Default `true`)
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
- :order_count,
340
- fact_model: OrderFactModel,
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
- => [{order_count: 17, sales_rep: 'Mary Sue'}]
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).
@@ -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', '~> 10.0'
34
+ spec.add_development_dependency 'rake'
35
35
  spec.add_development_dependency 'ransack'
36
36
  spec.add_development_dependency 'sqlite3'
37
37
  end
@@ -41,4 +41,5 @@ module ActiveReporting
41
41
  UnknownDimension = Class.new(StandardError)
42
42
  UnknownDimensionFilter = Class.new(StandardError)
43
43
  UnknownMetric = Class.new(StandardError)
44
+ UnknownJoinMethod = Class.new(StandardError)
44
45
  end
@@ -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).to_hash
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 }.map { |d| d.name.to_sym }
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[dimension.name.to_s] = callback.call(hash[dimension.name.to_s])
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
- def_delegators :@dimension, :name, :type, :klass, :association, :model, :hierarchical?, :datetime?
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, label = dim.is_a?(Hash) ? Array(dim).flatten : [dim, nil]
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 '#{dim}' not found on fact model '#{fact_model}'") if found_dimension.nil?
20
-
21
- new(found_dimension, label_config(label))
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] label
30
- def self.label_config(label)
31
- return { label: label } unless label.is_a?(Hash)
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: label[:field],
35
- label_name: 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
- return [degenerate_select_fragment] if type == Dimension::TYPES[:degenerate]
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
- @label_name = label_name ? "#{name}_#{label_name}" : name
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 validate_hierarchical_label(hierarchical_label)
110
- if datetime?
111
- validate_supported_database_for_datetime_hierarchies
112
- validate_against_datetime_hierarchies(hierarchical_label)
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
- validate_dimension_is_hierachical(hierarchical_label)
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 degenerate_fragment
143
- return "#{name}_#{@label}" if datetime?
144
- "#{model.quoted_table_name}.#{name}"
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 degenerate_select_fragment
148
- return "DATE_TRUNC('#{@label}', #{model.quoted_table_name}.#{name}) AS #{name}_#{@label}" if datetime?
149
- "#{model.quoted_table_name}.#{name}"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveReporting
4
- VERSION = '0.4.1'
4
+ VERSION = '0.6.1'
5
5
  end
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.1
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: 2019-05-28 00:00:00.000000000 Z
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: '10.0'
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: '10.0'
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.1
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