effective_reports 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +110 -0
- data/Rakefile +18 -0
- data/app/assets/config/effective_reports_manifest.js +3 -0
- data/app/assets/javascripts/effective_reports/base.js +15 -0
- data/app/assets/javascripts/effective_reports.js +1 -0
- data/app/assets/stylesheets/effective_reports/base.scss +0 -0
- data/app/assets/stylesheets/effective_reports.scss +1 -0
- data/app/controllers/admin/reports_controller.rb +16 -0
- data/app/datatables/admin/effective_reports_datatable.rb +31 -0
- data/app/datatables/effective_report_datatable.rb +21 -0
- data/app/helpers/effective_reports_helper.rb +60 -0
- data/app/mailers/effective/reports_mailer.rb +38 -0
- data/app/models/concerns/acts_as_reportable.rb +72 -0
- data/app/models/effective/report.rb +94 -0
- data/app/models/effective/report_column.rb +89 -0
- data/app/models/effective/report_scope.rb +48 -0
- data/app/views/admin/reports/_form.html.haml +8 -0
- data/app/views/admin/reports/_form_report.html.haml +194 -0
- data/app/views/admin/reports/_layout.html.haml +2 -0
- data/app/views/admin/reports/_report.html.haml +17 -0
- data/config/effective_reports.rb +37 -0
- data/config/routes.rb +16 -0
- data/db/migrate/01_create_effective_reports.rb.erb +54 -0
- data/db/seeds.rb +1 -0
- data/lib/effective_reports/engine.rb +18 -0
- data/lib/effective_reports/version.rb +3 -0
- data/lib/effective_reports.rb +27 -0
- data/lib/generators/effective_reports/install_generator.rb +32 -0
- data/lib/generators/templates/effective_reports_mailer_preview.rb +4 -0
- data/lib/tasks/effective_reports_tasks.rake +8 -0
- metadata +270 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ff9625e424780a9a874037cce46b15b10cf69eb0d515fb6e426d61076099990c
|
4
|
+
data.tar.gz: a0b984c522429e79878d24064dbd24b3835c7777aa741654ab30a1c1ded90058
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 63940ee370287f6b3b517ce8b7f3a113b1b817615d6443f21fa045165b152b9da19bb925a25abf58f7be63a9172d0770b36d9c0d8e9c3a2f7980d5c614d9ab19
|
7
|
+
data.tar.gz: 7e0494cadaa8e2e98bcf79b0dce96cfd21e518448fdccb8386d335ffac7fe350fe076468a8ede1f61cc1ed85bbafa96d2291d6520aa073e70d191a2da00e2a3a
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2023 Code and Effect Inc.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
# Effective Reports
|
2
|
+
|
3
|
+
A dynamic ActiveRecord report builder.
|
4
|
+
|
5
|
+
## Getting Started
|
6
|
+
|
7
|
+
This requires Rails 6+ and Twitter Bootstrap 4 and just works with Devise.
|
8
|
+
|
9
|
+
Please first install the [effective_datatables](https://github.com/code-and-effect/effective_datatables) gem.
|
10
|
+
|
11
|
+
Please download and install the [Twitter Bootstrap4](http://getbootstrap.com)
|
12
|
+
|
13
|
+
Add to your Gemfile:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem 'effective_reports'
|
17
|
+
```
|
18
|
+
|
19
|
+
Run the bundle command to install it:
|
20
|
+
|
21
|
+
```console
|
22
|
+
bundle install
|
23
|
+
```
|
24
|
+
|
25
|
+
Then run the generator:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
rails generate effective_reports:install
|
29
|
+
```
|
30
|
+
|
31
|
+
The generator will install an initializer which describes all configuration options and creates a database migration.
|
32
|
+
|
33
|
+
If you want to tweak the table names, manually adjust both the configuration file and the migration now.
|
34
|
+
|
35
|
+
Then migrate the database:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
rake db:migrate
|
39
|
+
```
|
40
|
+
|
41
|
+
Please add the following to your User model:
|
42
|
+
|
43
|
+
```
|
44
|
+
acts_as_reportable
|
45
|
+
|
46
|
+
# { active: nil, inactive: nil, with_first_name: :string, not_in_good_standing: :boolean }
|
47
|
+
def reportable_scopes
|
48
|
+
{}
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
and
|
53
|
+
|
54
|
+
```
|
55
|
+
Add a link to the admin menu:
|
56
|
+
|
57
|
+
```haml
|
58
|
+
- if can? :admin, :effective_reports
|
59
|
+
- if can? :index, Effective::Reports
|
60
|
+
= nav_link_to 'Reports', effective_reports.admin_reports_path
|
61
|
+
```
|
62
|
+
|
63
|
+
## Configuration
|
64
|
+
|
65
|
+
## Authorization
|
66
|
+
|
67
|
+
All authorization checks are handled via the effective_resources gem found in the `config/initializers/effective_resources.rb` file.
|
68
|
+
|
69
|
+
## Effective Roles
|
70
|
+
|
71
|
+
This gem works with effective roles for the representative roles.
|
72
|
+
|
73
|
+
Configure your `config/initializers/effective_roles.rb` something like this:
|
74
|
+
|
75
|
+
```
|
76
|
+
```
|
77
|
+
|
78
|
+
## Permissions
|
79
|
+
|
80
|
+
The permissions you actually want to define are as follows (using CanCan):
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
if user.persisted?
|
84
|
+
end
|
85
|
+
|
86
|
+
if user.admin?
|
87
|
+
can :admin, :effective_reports
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
## License
|
92
|
+
|
93
|
+
MIT License. Copyright [Code and Effect Inc.](http://www.codeandeffect.com/)
|
94
|
+
|
95
|
+
## Testing
|
96
|
+
|
97
|
+
Run tests by:
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
rails test
|
101
|
+
```
|
102
|
+
|
103
|
+
## Contributing
|
104
|
+
|
105
|
+
1. Fork it
|
106
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
107
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
108
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
109
|
+
5. Bonus points for test coverage
|
110
|
+
6. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
|
3
|
+
APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
|
4
|
+
load "rails/tasks/engine.rake"
|
5
|
+
|
6
|
+
load "rails/tasks/statistics.rake"
|
7
|
+
|
8
|
+
require "bundler/gem_tasks"
|
9
|
+
|
10
|
+
require "rake/testtask"
|
11
|
+
|
12
|
+
Rake::TestTask.new(:test) do |t|
|
13
|
+
t.libs << 'test'
|
14
|
+
t.pattern = 'test/**/*_test.rb'
|
15
|
+
t.verbose = false
|
16
|
+
end
|
17
|
+
|
18
|
+
task default: :test
|
@@ -0,0 +1,15 @@
|
|
1
|
+
// Show and hide the filter values when click filter by column
|
2
|
+
// This additional JS is required because the f.show_if doesn't work quite right
|
3
|
+
$(document).on('change', "[name^='effective_report[report_columns_attributes]'][name$='[filter]']", function(event) {
|
4
|
+
let $filter = $(event.currentTarget);
|
5
|
+
let $values = $filter.closest('.row').find('.effective-report-filter');
|
6
|
+
let $inputs = $values.find('input,textarea,select,button');
|
7
|
+
|
8
|
+
if($filter.is(':checked')) {
|
9
|
+
$values.show();
|
10
|
+
$inputs.removeAttr('disabled');
|
11
|
+
} else {
|
12
|
+
$values.hide();
|
13
|
+
$inputs.prop('disabled', true);
|
14
|
+
}
|
15
|
+
});
|
@@ -0,0 +1 @@
|
|
1
|
+
//= require_tree ./effective_reports
|
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
@import 'effective_reports/base';
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Admin
|
2
|
+
class ReportsController < ApplicationController
|
3
|
+
before_action(:authenticate_user!) if defined?(Devise)
|
4
|
+
before_action { EffectiveResources.authorize!(self, :admin, :effective_reports) }
|
5
|
+
|
6
|
+
include Effective::CrudController
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def permitted_params
|
11
|
+
model = (params.key?(:effective_report) ? :effective_report: :report)
|
12
|
+
params.require(model).permit!
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Admin
|
2
|
+
class EffectiveReportsDatatable < Effective::Datatable
|
3
|
+
|
4
|
+
datatable do
|
5
|
+
order :id
|
6
|
+
col :id, visible: false
|
7
|
+
|
8
|
+
col :created_at, visible: false
|
9
|
+
col :created_by, visible: false
|
10
|
+
|
11
|
+
col :title
|
12
|
+
col :description
|
13
|
+
|
14
|
+
col :reportable_class_name, label: 'Resource', search: EffectiveReports.reportable_classes.map(&:to_s), visible: false
|
15
|
+
|
16
|
+
col :report_columns, label: 'Columns', visible: false
|
17
|
+
col :report_scopes, label: 'Scopes', visible: false
|
18
|
+
|
19
|
+
col(:current_rows_count) do |report|
|
20
|
+
report.collection().count
|
21
|
+
end
|
22
|
+
|
23
|
+
actions_col
|
24
|
+
end
|
25
|
+
|
26
|
+
collection do
|
27
|
+
Effective::Report.deep.all
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# This renders a datatable based off a report
|
2
|
+
|
3
|
+
class EffectiveReportDatatable < Effective::Datatable
|
4
|
+
datatable do
|
5
|
+
skip_save_state! # Forgets the previous show/hide columns settings
|
6
|
+
|
7
|
+
report.report_columns.each do |column|
|
8
|
+
col(column.name, as: column.as.to_sym)
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
collection do
|
14
|
+
report.collection()
|
15
|
+
end
|
16
|
+
|
17
|
+
def report
|
18
|
+
Effective::Report.find(attributes[:report_id])
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module EffectiveReportsHelper
|
2
|
+
|
3
|
+
def reportable_boolean_collection
|
4
|
+
[['Yes', true], ['No', false]]
|
5
|
+
end
|
6
|
+
|
7
|
+
def reportable_attributes_collection(attributes)
|
8
|
+
macros = [:belongs_to, :belongs_to_polymorphic, :has_many, :has_one]
|
9
|
+
|
10
|
+
{
|
11
|
+
'Attributes' => attributes.select { |_, type| macros.exclude?(type) }.map { |att, _| [att, att] }.sort,
|
12
|
+
'Associations' => attributes.select { |_, type| macros.include?(type) }.map { |att, _| [att, att] }.sort,
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
def reportable_scopes_collection(scopes)
|
17
|
+
{
|
18
|
+
'Basic' => scopes.select { |_, type| type.blank? }.map { |scope, _| [scope, scope] }.sort,
|
19
|
+
'Advanced' => scopes.select { |_, type| type.present? }.map { |scope, _| [scope, scope] }.sort
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def reportable_operations_collection(type)
|
24
|
+
case type
|
25
|
+
when :boolean
|
26
|
+
[
|
27
|
+
['Equals', :eq],
|
28
|
+
['Does Not Equal', :not_eq]
|
29
|
+
]
|
30
|
+
when :string
|
31
|
+
[
|
32
|
+
['Equals =', :eq],
|
33
|
+
['Does Not Equal !=', :not_eq],
|
34
|
+
['Includes', :matches],
|
35
|
+
['Does Not Include', :does_not_match],
|
36
|
+
['Starts with', :starts_with],
|
37
|
+
['Ends with', :ends_with]
|
38
|
+
]
|
39
|
+
when :integer, :price, :date, :decimal
|
40
|
+
[
|
41
|
+
['Equals =', :eq],
|
42
|
+
['Does Not Equal !=', :not_eq],
|
43
|
+
['Greater than >', :gt],
|
44
|
+
['Greater than or equal to >=', :gteq],
|
45
|
+
['Less than <', :lt],
|
46
|
+
['Less than or equal to <', :lteq],
|
47
|
+
]
|
48
|
+
when :belongs_to, :belongs_to_polymorphic, :has_many, :has_one
|
49
|
+
[
|
50
|
+
['ID(s) Equals =', :eq],
|
51
|
+
['Matches', :matches],
|
52
|
+
['Does Not Match', :does_not_match],
|
53
|
+
['SQL', :sql],
|
54
|
+
]
|
55
|
+
else
|
56
|
+
raise("unexpected reportable operations collection type: #{type || 'nil'}")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Effective
|
2
|
+
class ReportsMailer < EffectiveReports.parent_mailer_class
|
3
|
+
|
4
|
+
include EffectiveMailer
|
5
|
+
include EffectiveEmailTemplatesMailer if EffectiveReports.use_effective_email_templates
|
6
|
+
|
7
|
+
# def reports_submitted(resource, opts = {})
|
8
|
+
# @assigns = assigns_for(resource)
|
9
|
+
# @applicant = resource
|
10
|
+
|
11
|
+
# subject = subject_for(__method__, "Reports Submitted - #{resource}", resource, opts)
|
12
|
+
# headers = headers_for(resource, opts)
|
13
|
+
|
14
|
+
# mail(to: resource.user.email, subject: subject, **headers)
|
15
|
+
# end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def assigns_for(resource)
|
20
|
+
if resource.kind_of?(Effective::Reports)
|
21
|
+
return reports_assigns(resource)
|
22
|
+
end
|
23
|
+
|
24
|
+
raise('unexpected resource')
|
25
|
+
end
|
26
|
+
|
27
|
+
def reports_assigns(resource)
|
28
|
+
raise('expected an reports') unless resource.class.respond_to?(:effective_reports_resource?)
|
29
|
+
|
30
|
+
values = {
|
31
|
+
date: reports.created_at.strftime('%F')
|
32
|
+
}.compact
|
33
|
+
|
34
|
+
{ reports: values }
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# ActsAsReportable
|
2
|
+
#
|
3
|
+
# Mark your model with 'acts_as_reportable' to be included in the reports
|
4
|
+
|
5
|
+
module ActsAsReportable
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
PRICE_NAME_ATTRIBUTES = ['price', 'subtotal', 'tax', 'total', 'current_revenue', 'current_revenue_subtotal', 'current_revenue_tax', 'deferred_revenue', 'deferred_revenue_subtotal', 'deferred_revenue_tax', 'amount_owing', 'surcharge']
|
9
|
+
|
10
|
+
module Base
|
11
|
+
def acts_as_reportable(options = nil)
|
12
|
+
include ::ActsAsReportable
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
def acts_as_reportable?; true; end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Instance Methods
|
21
|
+
|
22
|
+
# { id: :integer, price: :price, created_at: :date }
|
23
|
+
def reportable_attributes
|
24
|
+
all_reportable_attributes || {}
|
25
|
+
end
|
26
|
+
|
27
|
+
# { active: nil, inactive: nil, with_first_name: :string, not_in_good_standing: :boolean }
|
28
|
+
def reportable_scopes
|
29
|
+
{}
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def all_reportable_attributes
|
35
|
+
columns = (self.class.columns_hash rescue {})
|
36
|
+
names = (self.attributes rescue {})
|
37
|
+
reflections = (self.class.reflect_on_all_associations rescue [])
|
38
|
+
|
39
|
+
atts = names.inject({}) do |h, (name, _)|
|
40
|
+
type = columns[name].type
|
41
|
+
|
42
|
+
type = case type
|
43
|
+
when :datetime then :date
|
44
|
+
when :integer then ((PRICE_NAME_ATTRIBUTES.include?(name) || name.include?('price')) ? :price : :integer)
|
45
|
+
when :text then :string
|
46
|
+
else type
|
47
|
+
end
|
48
|
+
|
49
|
+
h[name.to_sym] = type; h
|
50
|
+
end
|
51
|
+
|
52
|
+
associated = reflections.inject({}) do |h, reflection|
|
53
|
+
case reflection
|
54
|
+
when ActiveRecord::Reflection::BelongsToReflection
|
55
|
+
if reflection.options[:polymorphic]
|
56
|
+
h[reflection.name.to_sym] = :belongs_to_polymorphic
|
57
|
+
else
|
58
|
+
h[reflection.name.to_sym] = :belongs_to
|
59
|
+
end
|
60
|
+
when ActiveRecord::Reflection::HasManyReflection
|
61
|
+
h[reflection.name.to_sym] = :has_many
|
62
|
+
when ActiveRecord::Reflection::HasOneReflection
|
63
|
+
h[reflection.name.to_sym] = :has_one
|
64
|
+
when ActiveRecord::Reflection::ThroughReflection
|
65
|
+
h[reflection.name.to_sym] = :has_many
|
66
|
+
end; h
|
67
|
+
end
|
68
|
+
|
69
|
+
atts.merge(associated)
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Effective
|
2
|
+
class Report < ActiveRecord::Base
|
3
|
+
self.table_name = EffectiveReports.reports_table_name.to_s
|
4
|
+
|
5
|
+
belongs_to :created_by, polymorphic: true
|
6
|
+
|
7
|
+
has_many :report_columns, -> { ReportColumn.sorted }, inverse_of: :report, dependent: :delete_all
|
8
|
+
accepts_nested_attributes_for :report_columns, allow_destroy: true, reject_if: proc { |atts| atts['name'].blank? }
|
9
|
+
|
10
|
+
has_many :report_scopes, -> { ReportScope.sorted }, inverse_of: :report, dependent: :delete_all
|
11
|
+
accepts_nested_attributes_for :report_scopes, allow_destroy: true, reject_if: proc { |atts| atts['name'].blank? }
|
12
|
+
|
13
|
+
log_changes if respond_to?(:log_changes)
|
14
|
+
|
15
|
+
DATATYPES = [:boolean, :date, :decimal, :integer, :price, :string, :belongs_to, :belongs_to_polymorphic, :has_many, :has_one]
|
16
|
+
|
17
|
+
# Arel::Predications.instance_methods
|
18
|
+
OPERATIONS = [:eq, :not_eq, :matches, :does_not_match, :starts_with, :ends_with, :gt, :gteq, :lt, :lteq, :sql]
|
19
|
+
|
20
|
+
effective_resource do
|
21
|
+
title :string
|
22
|
+
reportable_class_name :string
|
23
|
+
|
24
|
+
timestamps
|
25
|
+
end
|
26
|
+
|
27
|
+
scope :deep, -> { includes(:report_columns, :report_scopes) }
|
28
|
+
scope :sorted, -> { order(:title) }
|
29
|
+
|
30
|
+
validates :title, presence: true, uniqueness: true
|
31
|
+
validates :reportable_class_name, presence: true
|
32
|
+
|
33
|
+
def to_s
|
34
|
+
title.presence || 'report'
|
35
|
+
end
|
36
|
+
|
37
|
+
def reportable
|
38
|
+
reportable_class_name.constantize if reportable_class_name.present?
|
39
|
+
end
|
40
|
+
|
41
|
+
def filtered_report_columns
|
42
|
+
report_columns.select(&:filter?)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Used to build the Reports form
|
46
|
+
# { id: :integer, archived: :boolean }
|
47
|
+
def reportable_attributes
|
48
|
+
attributes = Hash((reportable.new.reportable_attributes if reportable))
|
49
|
+
|
50
|
+
attributes.each do |attribute, type|
|
51
|
+
raise("#{reportable}.reportable_attributes #{attribute} => #{type || 'nil'} is invalid. Key must be a symbol") unless attribute.kind_of?(Symbol)
|
52
|
+
raise("#{reportable}.reportable_attributes :#{attribute} => #{type || 'nil'} is invalid. Value must be one of #{DATATYPES.map { |s| ":#{s}"}.join(', ')}") unless DATATYPES.include?(type)
|
53
|
+
end
|
54
|
+
|
55
|
+
attributes
|
56
|
+
end
|
57
|
+
|
58
|
+
# { active: nil, inactive: nil, with_first_name: :string, not_in_good_standing: :boolean }
|
59
|
+
def reportable_scopes
|
60
|
+
scopes = Hash((reportable.new.reportable_scopes if reportable))
|
61
|
+
|
62
|
+
scopes.each do |scope, type|
|
63
|
+
raise("#{reportable}.reportable_scopes #{scope} => #{type || 'nil'} is invalid. Key must be a symbol") unless scope.kind_of?(Symbol)
|
64
|
+
raise("#{reportable}.reportable_scopes :#{scope} => #{type || 'nil'} is invalid. Value must be one of #{DATATYPES.map { |s| ":#{s}"}.join(', ')}") if type.present? && !DATATYPES.include?(type)
|
65
|
+
raise("#{reportable} must respond to reportable scope :#{name}") unless reportable.respond_to?(scope)
|
66
|
+
end
|
67
|
+
|
68
|
+
scopes
|
69
|
+
end
|
70
|
+
|
71
|
+
# The klass to base the collection from
|
72
|
+
def collection
|
73
|
+
collection = reportable.all
|
74
|
+
|
75
|
+
# Apply Scopes
|
76
|
+
report_scopes.each do |scope|
|
77
|
+
collection = (scope.value.nil? ? collection.send(scope.name) : collection.send(scope.name, scope.value))
|
78
|
+
end
|
79
|
+
|
80
|
+
# Apply Includes
|
81
|
+
report_columns.select(&:as_associated?).each do |column|
|
82
|
+
collection = collection.includes(column.name)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Apply Filters
|
86
|
+
report_columns.select(&:filter?).each do |column|
|
87
|
+
collection = Resource.new(collection).search(column.name, column.value, operation: column.operation)
|
88
|
+
end
|
89
|
+
|
90
|
+
collection
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Effective
|
2
|
+
class ReportColumn < ActiveRecord::Base
|
3
|
+
self.table_name = EffectiveReports.report_columns_table_name.to_s
|
4
|
+
|
5
|
+
belongs_to :report
|
6
|
+
|
7
|
+
log_changes(to: :report) if respond_to?(:log_changes)
|
8
|
+
|
9
|
+
effective_resource do
|
10
|
+
name :string
|
11
|
+
as :string
|
12
|
+
position :integer
|
13
|
+
|
14
|
+
filter :boolean
|
15
|
+
operation :string
|
16
|
+
|
17
|
+
value_associated :text
|
18
|
+
value_boolean :boolean
|
19
|
+
value_date :date
|
20
|
+
value_decimal :decimal
|
21
|
+
value_integer :integer
|
22
|
+
value_price :integer
|
23
|
+
value_string :string
|
24
|
+
|
25
|
+
timestamps
|
26
|
+
end
|
27
|
+
|
28
|
+
scope :deep, -> { includes(:report) }
|
29
|
+
scope :sorted, -> { order(:position) }
|
30
|
+
|
31
|
+
before_validation(if: -> { report.present? }) do
|
32
|
+
self.position ||= (report.report_columns.map(&:position).compact.max || -1) + 1
|
33
|
+
end
|
34
|
+
|
35
|
+
before_validation(if: -> { filter? == false }) do
|
36
|
+
assign_attributes(operation: nil, value_associated: nil, value_boolean: nil, value_date: nil, value_decimal: nil, value_integer: nil, value_price: nil, value_string: nil)
|
37
|
+
end
|
38
|
+
|
39
|
+
validates :name, presence: true
|
40
|
+
validates :as, presence: true, inclusion: { in: Report::DATATYPES.map(&:to_s) }
|
41
|
+
validates :position, presence: true
|
42
|
+
validates :operation, presence: true, if: -> { filter? }
|
43
|
+
|
44
|
+
validate(if: -> { filter? }) do
|
45
|
+
if value.blank? && (value != false)
|
46
|
+
self.errors.add(:value_date, "can't be blank")
|
47
|
+
self.errors.add(:value_decimal, "can't be blank")
|
48
|
+
self.errors.add(:value_integer, "can't be blank")
|
49
|
+
self.errors.add(:value_price, "can't be blank")
|
50
|
+
self.errors.add(:value_string, "can't be blank")
|
51
|
+
self.errors.add(:value_associated, "can't be blank")
|
52
|
+
self.errors.add(:value_boolean, "can't be blank")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_s
|
57
|
+
[name, operation_label, value].compact.join(' ').presence || 'report column'
|
58
|
+
end
|
59
|
+
|
60
|
+
def as_associated?
|
61
|
+
[:belongs_to, :belongs_to_polymorphic, :has_many, :has_one].include?(as.to_sym)
|
62
|
+
end
|
63
|
+
|
64
|
+
def value
|
65
|
+
value_date || value_decimal || value_integer || value_price || value_string.presence || value_associated.presence || value_boolean
|
66
|
+
end
|
67
|
+
|
68
|
+
def operation_label
|
69
|
+
return unless operation.present?
|
70
|
+
|
71
|
+
case operation.to_sym
|
72
|
+
when :eq then '='
|
73
|
+
when :not_eq then '!='
|
74
|
+
when :matches then '~='
|
75
|
+
when :does_not_match then '!~='
|
76
|
+
when :starts_with then 'starts with'
|
77
|
+
when :ends_with then 'ends with'
|
78
|
+
when :gt then '>'
|
79
|
+
when :gteq then '>='
|
80
|
+
when :lt then '<'
|
81
|
+
when :lteq then '<='
|
82
|
+
when :sql then 'sql'
|
83
|
+
else
|
84
|
+
raise("unexpected operation: #{operation}")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Effective
|
2
|
+
class ReportScope < ActiveRecord::Base
|
3
|
+
self.table_name = EffectiveReports.report_scopes_table_name.to_s
|
4
|
+
|
5
|
+
belongs_to :report
|
6
|
+
|
7
|
+
log_changes(to: :report) if respond_to?(:log_changes)
|
8
|
+
|
9
|
+
effective_resource do
|
10
|
+
name :string
|
11
|
+
advanced :boolean # The scope is a 0 arity symbol when false, or a 1 arity hash when true
|
12
|
+
|
13
|
+
value_boolean :boolean
|
14
|
+
value_date :date
|
15
|
+
value_decimal :decimal
|
16
|
+
value_integer :integer
|
17
|
+
value_price :integer
|
18
|
+
value_string :string
|
19
|
+
|
20
|
+
timestamps
|
21
|
+
end
|
22
|
+
|
23
|
+
scope :deep, -> { includes(:report) }
|
24
|
+
scope :sorted, -> { order(:name) }
|
25
|
+
|
26
|
+
validates :name, presence: true
|
27
|
+
|
28
|
+
validate(if: -> { advanced? }) do
|
29
|
+
if value.blank? && (value != false)
|
30
|
+
self.errors.add(:value_date, "can't be blank")
|
31
|
+
self.errors.add(:value_decimal, "can't be blank")
|
32
|
+
self.errors.add(:value_integer, "can't be blank")
|
33
|
+
self.errors.add(:value_price, "can't be blank")
|
34
|
+
self.errors.add(:value_string, "can't be blank")
|
35
|
+
self.errors.add(:value_boolean, "can't be blank")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
name.presence || 'report scope'
|
41
|
+
end
|
42
|
+
|
43
|
+
def value
|
44
|
+
value_date || value_decimal || value_integer || value_price || value_string.presence || value_boolean
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|