active_reporting 0.0.1 → 0.1.0

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
  SHA1:
3
- metadata.gz: fafbed6f29c91ec920f27d8d06b31658010a6508
4
- data.tar.gz: 71eae395b135e2d471d6d991110ae424b742f51f
3
+ metadata.gz: 9e776c2130450c3db98c5518529079744a8e3f7e
4
+ data.tar.gz: 8284510c02504a70d78e3db21ba9697ac95bece6
5
5
  SHA512:
6
- metadata.gz: de74521cced32429f624da2e9518d811ec200e2762c4484a968754ca99934b7c56680d93845ea761ecd3ae454c655e5ba133d35bc3905df1815f36720d00347a
7
- data.tar.gz: adfa7a3fbd3fb2e4552d91b1be8b8ec864e9424484378001f68a7cc11aff5c15cff59e46138e5f9febd41695e497a9ad5841641a52999b6f3f864eefc23074ba
6
+ metadata.gz: ab8d651c7b980707ad6d4af9736c444c1c021f3a3f6d967e7f9eb8398270c17579c0b3dd35049e426225a0448924fa49edb77d4db9d10e8784786ae710e3b1e6
7
+ data.tar.gz: 9b488e5c85ce172cf06103a244faaf6150c34039750c670d235792539c970642110f15692cf24fc3e714ec4a394f4d8fc922560f5b6b9c27d789779c63a125b5
data/.codeclimate.yml ADDED
@@ -0,0 +1,15 @@
1
+ engines:
2
+ duplication:
3
+ enabled: true
4
+ config:
5
+ languages:
6
+ - ruby
7
+ fixme:
8
+ enabled: true
9
+ rubocop:
10
+ enabled: true
11
+ ratings:
12
+ paths:
13
+ - "**.rb"
14
+ exclude_paths:
15
+ - test/
data/.gitignore CHANGED
@@ -7,3 +7,4 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ .DS_STORE
data/.rubocop.yml ADDED
@@ -0,0 +1,25 @@
1
+ AllCops:
2
+ Exclude:
3
+ - test/**/*
4
+ TargetRubyVersion: 2.2
5
+
6
+ Metrics/LineLength:
7
+ Max: 120
8
+
9
+ Metrics/ParameterLists:
10
+ Max: 8
11
+
12
+ MethodLength:
13
+ Max: 20
14
+
15
+ Style/ClassAndModuleChildren:
16
+ Enabled: false
17
+
18
+ Style/Documentation:
19
+ Enabled: false
20
+
21
+ Style/PredicateName:
22
+ Enabled: false
23
+
24
+ Style/RaiseArgs:
25
+ Enabled: false
data/.travis.yml CHANGED
@@ -1,5 +1,16 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
- - 2.2.4
5
- before_install: gem install bundler -v 1.12.5
4
+ - 2.2.7
5
+ - 2.3.4
6
+ - 2.4.1
7
+ gemfiles:
8
+ - gemfiles/4.2.gemfile
9
+ - gemfiles/5.0.gemfile
10
+ matrix:
11
+ exclude:
12
+ - rvm: 2.2
13
+ gemfile: gemfiles/5.0.gemfile
14
+ script:
15
+ - bundle exec rake
16
+
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## 0.1.0 (2017-04-16)
2
+
3
+ * Initial release
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at t27duck@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile CHANGED
@@ -1,4 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ gem 'simplecov', require: false
4
+
3
5
  # Specify your gem's dependencies in active_reporting.gemspec
4
6
  gemspec
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2016 Tony Drake
3
+ Copyright (c) 2017 Tony Drake
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,8 +1,12 @@
1
+ [![Build Status](https://travis-ci.org/t27duck/active_reporting.svg?branch=master)](https://travis-ci.org/t27duck/active_reporting)
2
+
3
+ [![Code Climate](https://codeclimate.com/github/t27duck/active_reporting/badges/gpa.svg)](https://codeclimate.com/github/t27duck/active_reporting)
4
+
1
5
  # ActiveReporting
2
6
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/active_reporting`. To experiment with that code, run `bin/console` for an interactive prompt.
7
+ ActiveReporting implements various terminology used in Relational Online Analytical Processing (Commonly referred to as ROLAP) with ActiveRecord. It provides a DSL to describe reports and analytics on your data.
4
8
 
5
- TODO: Delete this and the text above, and describe your gem
9
+ ActiveReporting officially supports MySQL, PostgreSQL, and SQLite.
6
10
 
7
11
  ## Installation
8
12
 
@@ -20,9 +24,301 @@ Or install it yourself as:
20
24
 
21
25
  $ gem install active_reporting
22
26
 
23
- ## Usage
27
+ ## What is "Reporting"?
28
+
29
+ Reporting is the collection and presentation data so that it can be analyzed. Our databases only store one thing: data. Data is great for computers but mostly worthless to humans. What ActiveReoprting does is turn that *data* into *information* to help humans make decisions.
30
+
31
+ ## Terminology
32
+
33
+ ROLAP uses a set of terms to describe how a report is generated. ActiveReporting implements them in the closest way possible in Ruby-land.
34
+
35
+ ### Fact table (can be sometimes called fact model)
36
+
37
+ A fact table is the primary table where information is derived from in a report. It commonly contains fact columns (usually numeric values) and dimension columns (foreign keys to other tables or values that can be grouped together).
38
+
39
+ SQL Equivalent: FROM
40
+ Rails: ActiveRecord model
41
+
42
+ ### Dimension
43
+
44
+ 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.
45
+
46
+ Examples:
47
+ * A sales rep on a fact table of orders
48
+ * A state of an order on a state machine
49
+ * The manufacture on a fact table of widgets
50
+
51
+ SQL Equivalent: JOIN, GROUP BY
52
+ Rails: ActiveRecord relation or attribute
53
+
54
+ ### Dimension Hierarchy
55
+
56
+ A hierarchy for a dimension is related attributes that live on a dimension table used to drill down and drill up through a dimension.
57
+
58
+ Examples:
59
+ * Dates: Date, Month, Year, Quarter
60
+ * Mobile Phone: Model, Manufacture, OS, Wireless Technology
61
+
62
+ ### Dimension Member (also known as dimension labels)
63
+
64
+ 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.
65
+
66
+ Examples:
67
+ * 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.
68
+
69
+ ### Dimension Filter (or just "filter")
70
+
71
+ This isn't really an official term, but I like using it to describe further filtering of dimensionable data.
72
+
73
+ SQL Equivalent: WHERE
74
+ Rails: `where()`, scopes, etc.
75
+
76
+ ### Measure
77
+
78
+ A measure is a column in a fact table (usually a numeric value) used in aggregations such as sum, maximum, average, etc.
79
+
80
+ Examples:
81
+ * Total amount in a sale
82
+ * Number of units used in a transaction
83
+
84
+ SQL Equivalent: Column in the fact table used in an aggregation function
85
+ Rails: ActiveRecord attribute
86
+
87
+ ### Metric
88
+
89
+ A metric is a measured value and the subject of the report. It is the result of *the* question you want answered.
90
+
91
+ SQL Equivalent: A query result
92
+ Rails: The result of an ActiveRecord query
93
+
94
+ ### Star Schema
95
+
96
+ Star schema is a way of structuring your relational data. It is one of the most common forms of organization for relational data warehousing. The layout of a star schema consists of a fact table referencing one or more dimension tables. When laid out in an entity relationship diagram, it resembles a star.
97
+
98
+ [TODO: ADD PICTURE HERE]
99
+
100
+ More information: https://en.wikipedia.org/wiki/Star_schema
101
+
102
+ ### Snowflake Schema
103
+
104
+ Snowflake schema is a super class of star schema. A fact table still resides in the middle of the diagram, but dimension tables are normalized out into multiple tables resulting in the resemblance of a snowflake.
105
+
106
+ [TODO: ADD PICTURE HERE]
107
+
108
+ More information: https://en.wikipedia.org/wiki/Snowflake_schema
109
+
110
+ ActiveReporting is built with star schema in mind, but will work with snowflake.
111
+
112
+ ## Configuration
113
+
114
+ Configure ActiveReporting via block configuration or by setting individual settings:
115
+
116
+ ```ruby
117
+ ActiveReporting::Configuration.config do |c|
118
+ c.setting = value
119
+ end
120
+ ```
121
+
122
+ ```ruby
123
+ ActiveReporting::Configuration.setting = value
124
+ ```
125
+
126
+ ### Configuration Options
127
+
128
+ `default_dimension_label` - If a fact model does not have a default label set for when it's used as a dimension, this value will be used. (Default: `:name`)
129
+
130
+ `default_measure` - If a fact model does not specify a measure to use for aggregates, this value will be used. (Default: `:value`)
131
+
132
+ `ransack_fallback` - If the ransack gem is loaded, allow all unknown dimension filters to be delegated to ransack. (Default: `false`)
133
+
134
+ `metric_lookup_class` - The name of a constant used to lookup prebuilt `Reporting::Metric` objects by name. The constant should define a class method called `#lookup` which can take a string or symbol of the metric name. (Default: `::Metric`)
135
+
136
+
137
+ ## ActiveReporting::FactModel
138
+
139
+ In ActiveReporting, a fact model stores configuration information on how it can be used in reports. We use the term fact model instead of fact table because this class "models how the fact table interacts with dimensions and other reporting features".
140
+
141
+ You can put these classes anywhere you want in your app, though I recommend putting them in `app/fact_models`
142
+
143
+ ### Linking a fact model to an ActiveRecord model
144
+
145
+ Every fact model links to an ActiveRecord model. This is done either by naming convention or by explicitly declaring the model.
146
+
147
+ This naming convention is `[ModelName]FactModel`. Meaning if you have an ActiveRecord model named `Ticket`, you'll then have a `TicketFactModel` to link them together.
148
+
149
+ ```ruby
150
+ class TicketFactModel < ActiveRecord::FactModel
151
+
152
+ end
153
+ ```
154
+
155
+ Alternatively, you may manually specify the model manually with `use_model`
156
+
157
+ ```ruby
158
+ class TicketFactModel < ActiveRecord::FactModel
159
+ use_model SomeOtherModel
160
+ # OR you may pass in a string or symbol
161
+ # use_model :some_other_model
162
+ # use_model 'some_other_model'
163
+ # use_model 'SomeOtherModel'
164
+ end
165
+ ```
166
+
167
+ ### Configuring a fact model's measure
168
+
169
+ 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.
24
170
 
25
- TODO: Write usage instructions here
171
+ ```ruby
172
+ class OrderFactModel < ActiveReporting::FactModel
173
+ measure = :total
174
+ end
175
+ ```
176
+
177
+ ## Configuring Dimensions
178
+
179
+ ### Declaring dimensions on a fact model
180
+
181
+ You must declare what a fact model is dimensional by. A valid dimension is a column on the fact model's ActiveRecord model or a `belongs_to`/`has_one through` relationship. `has_many` relationships do not work (well) at all.
182
+
183
+ ```ruby
184
+ class TicketFactModel < ActiveReporting::FactModel
185
+ dimension :creator # belongs_to relationship
186
+ dimension :assignee # belongs_to relationship
187
+ dimension :category # Column on the tickets table
188
+ end
189
+ ```
190
+
191
+ ### When a fact model is used as a dimension
192
+
193
+ When another fact model uses a relationship as a dimension, that ActiveRecord model's fact model class can hold configuration information for how to act when used as a dimension.
194
+
195
+ By default, it is assumed a dimension's label is a column called `name`. This can be changed on the fact model.
196
+
197
+ ```ruby
198
+ class UserFactModel < ActiveReporting::FactModel
199
+ default_dimension_label :username
200
+ end
201
+ ```
202
+
203
+ ### Dimension Hierarchies
204
+
205
+ For dimensions that can have a hierarchy (such as a mobile phone), you can declare the what columns make it up. This will allow reports to dimension against a fact model and be able to use different labels to group by.
206
+
207
+ ```ruby
208
+ class PhoneFactModel < ActiveReporting::FactModel
209
+ dimension_hierarchy [:model_name, :manufacturer, :os, :wireless_technology]
210
+ end
211
+ ```
212
+
213
+ ## Configuring Dimension Filters
214
+
215
+ A dimension filter provides filtering for a report. In SQL-land, this is the `WHERE` clause.
216
+
217
+ Available dimension filters are defined on a `FactModel`. They can be implemented via a similar syntax to a Rails scope, link to the fact model's ActiveRecord model's scope, or delegate to ransack.
218
+
219
+ ```ruby
220
+ class TicketFactModel < ActiveReporting::FactModel
221
+ dimension_filter :open
222
+ dimension_filter :for_category_name, ->(x) { joins(:category).where(categories: {name: x}) }
223
+ dimension_filter :subject_cont, :ransack
224
+ end
225
+ ```
226
+
227
+ The first example exposes the `Ticket.open` scope to the fact model allowing it to be used as a dimension filter.
228
+
229
+ The second example defines a lambda to be invoked like a Rails scope. It joins against the `category` relationship on `Ticket` and filters by the category's name.
230
+
231
+ The third example defines a filter called "subject_cont" and will delegate it to ransack when called.
232
+
233
+ Only dimension filters defined in the fact model may be used. Whitelisting available filters allows for more control over what the user may filter by. Giving the user full control to call any scope or method from the ActiveRecord model could lead to unexpected results, poor performing queries, or possible security concerns.
234
+
235
+ If ransack is available, you may flag a fact model to delegate all unknown dimension filters to ransack.
236
+
237
+ ```ruby
238
+ class TicketFactModel < ActiveReporting::FactModel
239
+ use_ransack_for_unknown_dimension_filters
240
+ end
241
+ ```
242
+
243
+ ## ActiveReporting::Metric
244
+
245
+ A `Metric` is the basic building block used to describe a question you want to answer. At minimum, a metric needs a name, a fact table and an aggregate. You can expand a metric further by including dimensions and dimension filters.
246
+
247
+ ```ruby
248
+ my_metric = ActiveReporting::Metric.new(
249
+ :order_total,
250
+ fact_model: OrderFactModel,
251
+ aggregate: :sum
252
+ )
253
+ ```
254
+
255
+ `name` - This is the identifying name of the metric.
256
+
257
+ `fact_model` - An `ActiveReporting::FactModel` class
258
+
259
+ `aggregate` - The SQL aggregate used to calculate the metric. Supported aggregates include count, max, min, avg, and sum. (Default: `:count`)
260
+
261
+ `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}]`)
262
+
263
+ `dimension_filter` - A hash were the keys are dimension filter names and the values are the values passed into the filter.
264
+
265
+ `metric_filter` - An additional HAVING clause to be tacked on to the end of the query. This allows for the further filtering of the end results based on the value of the aggregate. (Examples: `{gt: 3}`, `{eq: 5}`, `{lte: 7}`)
266
+
267
+ `order_by_dimension` - Allows you to set the ordering of the results based on a dimension label. (Examples: `{author: :desc}`, `{sales_ref: :asc}`)
268
+
269
+ ## ActiveReporting::Report
270
+
271
+ A `Report` takes an `ActiveReporting::Metric` and ties everything together. It is responsible for building and executing the query to generate a result. The result is an simple array of hashing.
272
+
273
+ ```ruby
274
+ metric = ActiveReporting::Metric.new(
275
+ :order_count,
276
+ fact_model: OrderFactModel,
277
+ dimension: [:sales_rep],
278
+ dimension_filter: {months_ago: 1}
279
+ )
280
+
281
+ report = ActiveReporting.new(metric)
282
+ report.run
283
+ => [{order_count: 12, sales_rep: 'Fred Jones', sales_rep_identifier: 123},{order_count: 17, sales_rep: 'Mary Sue', sales_rep_identifier: 123}]
284
+ ```
285
+
286
+ 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`.
287
+
288
+ `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`)
289
+
290
+ `dimension_filter` - A hash that will be merged with the `Metric`'s dimension filters.
291
+
292
+ `dimensions` - An array of additional dimensions which are merged with the `Metric`'s dimensions.
293
+
294
+ `metric_filter` - Sets the HAVING clause of the final query and is merged with the `Metric`'s metric filter.
295
+
296
+ ```ruby
297
+ metric = ActiveReporting::Metric.new(
298
+ :order_count,
299
+ fact_model: OrderFactModel,
300
+ dimension: [:sales_rep],
301
+ dimension_filter: {months_ago: 1}
302
+ )
303
+
304
+ report = ActiveReporting.new(metric, dimension_filter: {from_region: 'North'}, dimension_identifiers: false)
305
+ report.run
306
+ => [{order_count: 17, sales_rep: 'Mary Sue'}]
307
+ ```
308
+
309
+ 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`.
310
+
311
+ ```ruby
312
+ class StoredMetrics
313
+ def lookup(metric_name)
314
+ # Code to construct and return an `ActiveReporting::Metric` object
315
+ end
316
+ end
317
+
318
+ ActiveReporting::Configuration.metric_lookup_class = StoredMetrics
319
+
320
+ report = ActiveReporting::Report.new(:a_stored_metric, ...)
321
+ ```
26
322
 
27
323
  ## Development
28
324
 
@@ -32,7 +328,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
32
328
 
33
329
  ## Contributing
34
330
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/active_reporting.
331
+ 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.
36
332
 
37
333
 
38
334
  ## License