effective_reports 0.1.0
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 +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
|