active_reporter 0.6.0 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Rakefile +9 -9
- data/lib/active_reporter/aggregator/array.rb +1 -1
- data/lib/active_reporter/aggregator/count.rb +2 -2
- data/lib/active_reporter/aggregator/count_if.rb +2 -2
- data/lib/active_reporter/aggregator/ratio.rb +1 -1
- data/lib/active_reporter/aggregator.rb +9 -9
- data/lib/active_reporter/calculator.rb +2 -2
- data/lib/active_reporter/dimension/base.rb +3 -3
- data/lib/active_reporter/dimension/bin/set.rb +3 -3
- data/lib/active_reporter/dimension/bin/table.rb +1 -1
- data/lib/active_reporter/dimension/bin.rb +23 -15
- data/lib/active_reporter/dimension/category.rb +1 -1
- data/lib/active_reporter/dimension/enum.rb +1 -1
- data/lib/active_reporter/dimension/number.rb +3 -3
- data/lib/active_reporter/dimension/time.rb +4 -4
- data/lib/active_reporter/dimension.rb +8 -8
- data/lib/active_reporter/evaluator.rb +2 -2
- data/lib/active_reporter/inflector.rb +1 -1
- data/lib/active_reporter/report/aggregation.rb +12 -12
- data/lib/active_reporter/report/definition.rb +6 -6
- data/lib/active_reporter/report/validation.rb +28 -13
- data/lib/active_reporter/report.rb +3 -3
- data/lib/active_reporter/serializer/base.rb +6 -6
- data/lib/active_reporter/serializer/csv.rb +2 -2
- data/lib/active_reporter/serializer/form_field.rb +5 -5
- data/lib/active_reporter/serializer/highcharts.rb +6 -6
- data/lib/active_reporter/serializer.rb +7 -7
- data/lib/active_reporter/tracker/base.rb +1 -1
- data/lib/active_reporter/tracker/value.rb +1 -1
- data/lib/active_reporter/tracker.rb +3 -3
- data/lib/active_reporter/version.rb +1 -1
- data/lib/active_reporter.rb +7 -3
- data/spec/acceptance/data_spec.rb +49 -49
- data/spec/active_reporter/aggregator_spec.rb +37 -37
- data/spec/active_reporter/dimension/base_spec.rb +29 -29
- data/spec/active_reporter/dimension/bin/set_spec.rb +29 -29
- data/spec/active_reporter/dimension/bin/table_spec.rb +7 -7
- data/spec/active_reporter/dimension/bin_spec.rb +12 -12
- data/spec/active_reporter/dimension/category_spec.rb +22 -22
- data/spec/active_reporter/dimension/enum_spec.rb +12 -12
- data/spec/active_reporter/dimension/number_spec.rb +11 -11
- data/spec/active_reporter/dimension/time_spec.rb +20 -20
- data/spec/active_reporter/report_spec.rb +162 -162
- data/spec/active_reporter/serializer/hash_table_spec.rb +13 -13
- data/spec/active_reporter/serializer/highcharts_spec.rb +30 -30
- data/spec/active_reporter/serializer/table_spec.rb +22 -22
- data/spec/dummy/Rakefile +1 -1
- data/spec/dummy/app/models/post_report.rb +1 -1
- data/spec/dummy/app/views/layouts/application.html.erb +2 -2
- data/spec/dummy/app/views/site/report.html.erb +5 -5
- data/spec/dummy/bin/bundle +2 -2
- data/spec/dummy/bin/rails +3 -3
- data/spec/dummy/bin/rake +2 -2
- data/spec/dummy/bin/setup +2 -2
- data/spec/dummy/config/application.rb +4 -4
- data/spec/dummy/config/boot.rb +3 -3
- data/spec/dummy/config/database.yml +3 -3
- data/spec/dummy/config/environment.rb +1 -1
- data/spec/dummy/config/environments/production.rb +4 -4
- data/spec/dummy/config/environments/test.rb +1 -1
- data/spec/dummy/config/initializers/assets.rb +1 -1
- data/spec/dummy/config/initializers/inflections.rb +4 -4
- data/spec/dummy/config/initializers/session_store.rb +1 -1
- data/spec/dummy/config/locales/en.yml +2 -2
- data/spec/dummy/config/routes.rb +9 -9
- data/spec/dummy/config.ru +1 -1
- data/spec/dummy/log/test.log +49678 -75977
- data/spec/spec_helper.rb +13 -13
- metadata +64 -44
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: d4aa407f8ba1f34e957ec83b9f365d6e122b009aced4cd35a673532a86489c52
         | 
| 4 | 
            +
              data.tar.gz: 19b0aa5a2940567b8fb1673a6cea58781992a4ba9273e85653fd7ab095fc7c6c
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 65a85a49868dd40a57c22daba7f7f7a94d397547bc9a56001297598d9a8f5d1ee698b5f6f64cc17ffa3ccc51f5da29e17fb57498b9892b7ce8837912ac994c1a
         | 
| 7 | 
            +
              data.tar.gz: d10150011dcc2ca60e6cdf4d2a0f7788ad7c11d99a45fc24a81f2117258e11c94a935c75300d5a999d69ae95d1475f6c8ba68ec029e456f0167cf7d09cddc707
         | 
    
        data/Rakefile
    CHANGED
    
    | @@ -1,22 +1,22 @@ | |
| 1 1 | 
             
            begin
         | 
| 2 | 
            -
              require  | 
| 2 | 
            +
              require "bundler/setup"
         | 
| 3 3 | 
             
            rescue LoadError
         | 
| 4 | 
            -
              puts  | 
| 4 | 
            +
              puts "You must `gem install bundler` and `bundle install` to run rake tasks"
         | 
| 5 5 | 
             
            end
         | 
| 6 6 |  | 
| 7 | 
            -
            require  | 
| 7 | 
            +
            require "rdoc/task"
         | 
| 8 8 |  | 
| 9 9 | 
             
            RDoc::Task.new(:rdoc) do |rdoc|
         | 
| 10 | 
            -
              rdoc.rdoc_dir =  | 
| 11 | 
            -
              rdoc.title    =  | 
| 12 | 
            -
              rdoc.options <<  | 
| 13 | 
            -
              rdoc.rdoc_files.include( | 
| 14 | 
            -
              rdoc.rdoc_files.include( | 
| 10 | 
            +
              rdoc.rdoc_dir = "rdoc"
         | 
| 11 | 
            +
              rdoc.title    = "ActiveReporter"
         | 
| 12 | 
            +
              rdoc.options << "--line-numbers"
         | 
| 13 | 
            +
              rdoc.rdoc_files.include("README.rdoc")
         | 
| 14 | 
            +
              rdoc.rdoc_files.include("lib/**/*.rb")
         | 
| 15 15 | 
             
            end
         | 
| 16 16 |  | 
| 17 17 | 
             
            Bundler::GemHelper.install_tasks
         | 
| 18 18 |  | 
| 19 | 
            -
            require  | 
| 19 | 
            +
            require "rspec/core/rake_task"
         | 
| 20 20 |  | 
| 21 21 | 
             
            RSpec::Core::RakeTask.new(:spec)
         | 
| 22 22 |  | 
| @@ -2,7 +2,7 @@ module ActiveReporter | |
| 2 2 | 
             
              module Aggregator
         | 
| 3 3 | 
             
                class Array < ActiveReporter::Aggregator::Base
         | 
| 4 4 | 
             
                  def aggregate(groups)
         | 
| 5 | 
            -
                    fail InvalidParamsError,  | 
| 5 | 
            +
                    fail InvalidParamsError, "array aggregator is only supported in Postgres" unless ActiveReporter.database_type == :postgres
         | 
| 6 6 | 
             
                    super
         | 
| 7 7 | 
             
                  end
         | 
| 8 8 |  | 
| @@ -2,7 +2,7 @@ module ActiveReporter | |
| 2 2 | 
             
              module Aggregator
         | 
| 3 3 | 
             
                class Count < ActiveReporter::Aggregator::Base
         | 
| 4 4 | 
             
                  def function
         | 
| 5 | 
            -
                    "COUNT(#{ | 
| 5 | 
            +
                    "COUNT(#{"DISTINCT" if distinct} #{expression})"
         | 
| 6 6 | 
             
                  end
         | 
| 7 7 |  | 
| 8 8 | 
             
                  def default_value
         | 
| @@ -16,7 +16,7 @@ module ActiveReporter | |
| 16 16 | 
             
                  end
         | 
| 17 17 |  | 
| 18 18 | 
             
                  def column
         | 
| 19 | 
            -
                    opts.fetch(:column,  | 
| 19 | 
            +
                    opts.fetch(:column, "id")
         | 
| 20 20 | 
             
                  end
         | 
| 21 21 | 
             
                end
         | 
| 22 22 | 
             
              end
         | 
| @@ -2,7 +2,7 @@ module ActiveReporter | |
| 2 2 | 
             
              module Aggregator
         | 
| 3 3 | 
             
                class CountIf < ActiveReporter::Aggregator::Count
         | 
| 4 4 | 
             
                  def function
         | 
| 5 | 
            -
                    "COUNT(#{expression} IN (#{values.map(&:to_s).join( | 
| 5 | 
            +
                    "COUNT(#{expression} IN (#{values.map(&:to_s).join(",")}) OR NULL)"
         | 
| 6 6 | 
             
                  end
         | 
| 7 7 |  | 
| 8 8 | 
             
                  def default_value
         | 
| @@ -16,7 +16,7 @@ module ActiveReporter | |
| 16 16 | 
             
                  end
         | 
| 17 17 |  | 
| 18 18 | 
             
                  def column
         | 
| 19 | 
            -
                    super ||  | 
| 19 | 
            +
                    super || "id"
         | 
| 20 20 | 
             
                  end
         | 
| 21 21 | 
             
                end
         | 
| 22 22 | 
             
              end
         | 
| @@ -1,9 +1,9 @@ | |
| 1 | 
            -
            require  | 
| 2 | 
            -
            require  | 
| 3 | 
            -
            require  | 
| 4 | 
            -
            require  | 
| 5 | 
            -
            require  | 
| 6 | 
            -
            require  | 
| 7 | 
            -
            require  | 
| 8 | 
            -
            require  | 
| 9 | 
            -
            require  | 
| 1 | 
            +
            require "active_reporter/aggregator/base"
         | 
| 2 | 
            +
            require "active_reporter/aggregator/array"
         | 
| 3 | 
            +
            require "active_reporter/aggregator/average"
         | 
| 4 | 
            +
            require "active_reporter/aggregator/count"
         | 
| 5 | 
            +
            require "active_reporter/aggregator/count_if"
         | 
| 6 | 
            +
            require "active_reporter/aggregator/max"
         | 
| 7 | 
            +
            require "active_reporter/aggregator/min"
         | 
| 8 | 
            +
            require "active_reporter/aggregator/ratio"
         | 
| 9 | 
            +
            require "active_reporter/aggregator/sum"
         | 
| @@ -1,2 +1,2 @@ | |
| 1 | 
            -
            require  | 
| 2 | 
            -
            require  | 
| 1 | 
            +
            require "active_reporter/calculator/base"
         | 
| 2 | 
            +
            require "active_reporter/calculator/ratio"
         | 
| @@ -71,7 +71,7 @@ module ActiveReporter | |
| 71 71 | 
             
                  end
         | 
| 72 72 |  | 
| 73 73 | 
             
                  def order(relation)
         | 
| 74 | 
            -
                    relation.order("#{order_expression} #{sort_order} #{null_order}")
         | 
| 74 | 
            +
                    relation.order(Arel.sql("#{order_expression} #{sort_order} #{null_order}"))
         | 
| 75 75 | 
             
                  end
         | 
| 76 76 |  | 
| 77 77 | 
             
                  def sort_desc?
         | 
| @@ -79,7 +79,7 @@ module ActiveReporter | |
| 79 79 | 
             
                  end
         | 
| 80 80 |  | 
| 81 81 | 
             
                  def sort_order
         | 
| 82 | 
            -
                    sort_desc? ?  | 
| 82 | 
            +
                    sort_desc? ? "DESC" : "ASC"
         | 
| 83 83 | 
             
                  end
         | 
| 84 84 |  | 
| 85 85 | 
             
                  def nulls_last?
         | 
| @@ -90,7 +90,7 @@ module ActiveReporter | |
| 90 90 |  | 
| 91 91 | 
             
                  def null_order
         | 
| 92 92 | 
             
                    return unless ActiveReporter.database_type == :postgres
         | 
| 93 | 
            -
                    nulls_last? ?  | 
| 93 | 
            +
                    nulls_last? ? "NULLS LAST" : "NULLS FIRST"
         | 
| 94 94 | 
             
                  end
         | 
| 95 95 |  | 
| 96 96 | 
             
                  def params
         | 
| @@ -21,7 +21,7 @@ module ActiveReporter | |
| 21 21 | 
             
                        when /^([^,]+),(.+)$/ then new($1, $2)
         | 
| 22 22 | 
             
                        when /^([^,]+),$/     then new($1, nil)
         | 
| 23 23 | 
             
                        when /^,(.+)$/        then new(nil, $1)
         | 
| 24 | 
            -
                        when  | 
| 24 | 
            +
                        when ",", nil         then new(nil, nil)
         | 
| 25 25 | 
             
                        else
         | 
| 26 26 | 
             
                          raise "Unexpected SQL bin format #{value}"
         | 
| 27 27 | 
             
                        end
         | 
| @@ -104,8 +104,8 @@ module ActiveReporter | |
| 104 104 |  | 
| 105 105 | 
             
                    def [](key)
         | 
| 106 106 | 
             
                      case key.to_s
         | 
| 107 | 
            -
                      when  | 
| 108 | 
            -
                      when  | 
| 107 | 
            +
                      when "min" then min
         | 
| 108 | 
            +
                      when "max" then max
         | 
| 109 109 | 
             
                      end
         | 
| 110 110 | 
             
                    end
         | 
| 111 111 |  | 
| @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            require  | 
| 1 | 
            +
            require "active_reporter/dimension/base"
         | 
| 2 2 |  | 
| 3 3 | 
             
            module ActiveReporter
         | 
| 4 4 | 
             
              module Dimension
         | 
| @@ -9,15 +9,34 @@ module ActiveReporter | |
| 9 9 | 
             
                    self.class::MAX_BINS
         | 
| 10 10 | 
             
                  end
         | 
| 11 11 |  | 
| 12 | 
            +
                  # report values are greater than or equal to min, grouped by bin_width
         | 
| 12 13 | 
             
                  def min
         | 
| 13 14 | 
             
                    @min ||= filter_min || report.records.minimum(expression)
         | 
| 14 15 | 
             
                  end
         | 
| 15 16 | 
             
                  alias bin_start min
         | 
| 16 17 |  | 
| 18 | 
            +
                  # report values are less than max, grouped by bin_width
         | 
| 17 19 | 
             
                  def max
         | 
| 18 20 | 
             
                    @max ||= filter_max || report.records.maximum(expression)
         | 
| 19 21 | 
             
                  end
         | 
| 20 22 |  | 
| 23 | 
            +
                  def bin_end
         | 
| 24 | 
            +
                    @bin_end ||= if max.blank? || min.blank? || min > max
         | 
| 25 | 
            +
                      nil
         | 
| 26 | 
            +
                    else
         | 
| 27 | 
            +
                      bin_edge = bin_start + bin_width
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                      loop do
         | 
| 30 | 
            +
                        break if bin_edge >= max
         | 
| 31 | 
            +
                        bin_edge += bin_width
         | 
| 32 | 
            +
                      end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                      bin_edge += bin_width unless filter_values_for(:max).present? # # # figure out why we need this??
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                      bin_edge
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
             | 
| 21 40 | 
             
                  def filter_min
         | 
| 22 41 | 
             
                    filter_values_for(:min).min
         | 
| 23 42 | 
             
                  end
         | 
| @@ -95,23 +114,12 @@ module ActiveReporter | |
| 95 114 | 
             
                  end
         | 
| 96 115 |  | 
| 97 116 | 
             
                  def autopopulate_bins
         | 
| 98 | 
            -
                    return [] if bin_start.blank? ||  | 
| 99 | 
            -
             | 
| 100 | 
            -
                    bin_max = filter_values_for(:max).present? ? (max - bin_width) : max
         | 
| 117 | 
            +
                    return [] if bin_start.blank? || bin_end.blank?
         | 
| 101 118 |  | 
| 102 | 
            -
                    bin_count = ( | 
| 119 | 
            +
                    bin_count = ((bin_end - bin_start)/(bin_width)).to_i
         | 
| 103 120 | 
             
                    invalid_param!(:bin_width, "is too small for the domain; would generate #{bin_count.to_i} bins") if bin_count > max_bins
         | 
| 104 121 |  | 
| 105 | 
            -
                     | 
| 106 | 
            -
                    bins = []
         | 
| 107 | 
            -
             | 
| 108 | 
            -
                    loop do
         | 
| 109 | 
            -
                      break if bin_edge > bin_max
         | 
| 110 | 
            -
             | 
| 111 | 
            -
                      bin = { min: bin_edge, max: bin_edge + bin_width }
         | 
| 112 | 
            -
                      bins << bin
         | 
| 113 | 
            -
                      bin_edge = bin[:max]
         | 
| 114 | 
            -
                    end
         | 
| 122 | 
            +
                    bins = bin_count.times.map { |i| { min: (bin_start + (bin_width*i)), max: (bin_start + (bin_width*i.next)) } }
         | 
| 115 123 |  | 
| 116 124 | 
             
                    bins.reverse! if sort_desc?
         | 
| 117 125 | 
             
                    ( nulls_last? ? bins.push(nil) : bins.unshift(nil) ) if data_contains_nil?
         | 
| @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            require  | 
| 1 | 
            +
            require "active_reporter/dimension/bin"
         | 
| 2 2 |  | 
| 3 3 | 
             
            module ActiveReporter
         | 
| 4 4 | 
             
              module Dimension
         | 
| @@ -9,8 +9,8 @@ module ActiveReporter | |
| 9 9 | 
             
                    super
         | 
| 10 10 |  | 
| 11 11 | 
             
                    if params.key?(:bin_width)
         | 
| 12 | 
            -
                      invalid_param!(:bin_width,  | 
| 13 | 
            -
                      invalid_param!(:bin_width,  | 
| 12 | 
            +
                      invalid_param!(:bin_width, "must be numeric") unless ActiveReporter.numeric?(params[:bin_width])
         | 
| 13 | 
            +
                      invalid_param!(:bin_width, "must be greater than 0") unless params[:bin_width].to_f > 0
         | 
| 14 14 | 
             
                    end
         | 
| 15 15 | 
             
                  end
         | 
| 16 16 |  | 
| @@ -1,12 +1,12 @@ | |
| 1 | 
            -
            require  | 
| 2 | 
            -
            require  | 
| 1 | 
            +
            require "active_reporter/inflector"
         | 
| 2 | 
            +
            require "active_reporter/dimension/bin"
         | 
| 3 3 |  | 
| 4 4 | 
             
            module ActiveReporter
         | 
| 5 5 | 
             
              module Dimension
         | 
| 6 6 | 
             
                class Time < Bin
         | 
| 7 7 | 
             
                  STEPS = %i(seconds minutes hours days weeks months years)
         | 
| 8 8 | 
             
                  BIN_STEPS = (STEPS - [:seconds]).map { |step| step.to_s.singularize(:_gem_active_reporter) }
         | 
| 9 | 
            -
                  DURATION_PATTERN = /\A\d+ (?:#{STEPS.map{ |step| "#{step}?" }.join( | 
| 9 | 
            +
                  DURATION_PATTERN = /\A\d+ (?:#{STEPS.map{ |step| "#{step}?" }.join("|")})\z/
         | 
| 10 10 |  | 
| 11 11 | 
             
                  def validate_params!
         | 
| 12 12 | 
             
                    super
         | 
| @@ -78,7 +78,7 @@ module ActiveReporter | |
| 78 78 |  | 
| 79 79 | 
             
                  class Set < Bin::Set
         | 
| 80 80 | 
             
                    def parse(value)
         | 
| 81 | 
            -
                      ::Time.zone.parse(value.to_s.gsub('"',  | 
| 81 | 
            +
                      ::Time.zone.parse(value.to_s.gsub('"', ""))
         | 
| 82 82 | 
             
                    end
         | 
| 83 83 |  | 
| 84 84 | 
             
                    def cast(value)
         | 
| @@ -1,8 +1,8 @@ | |
| 1 | 
            -
            require  | 
| 2 | 
            -
            require  | 
| 3 | 
            -
            require  | 
| 4 | 
            -
            require  | 
| 5 | 
            -
            require  | 
| 6 | 
            -
            require  | 
| 7 | 
            -
            require  | 
| 8 | 
            -
            require  | 
| 1 | 
            +
            require "active_reporter/dimension/base"
         | 
| 2 | 
            +
            require "active_reporter/dimension/bin"
         | 
| 3 | 
            +
            require "active_reporter/dimension/bin/set"
         | 
| 4 | 
            +
            require "active_reporter/dimension/bin/table"
         | 
| 5 | 
            +
            require "active_reporter/dimension/time"
         | 
| 6 | 
            +
            require "active_reporter/dimension/number"
         | 
| 7 | 
            +
            require "active_reporter/dimension/category"
         | 
| 8 | 
            +
            require "active_reporter/dimension/enum"
         | 
| @@ -1,2 +1,2 @@ | |
| 1 | 
            -
            require  | 
| 2 | 
            -
            require  | 
| 1 | 
            +
            require "active_reporter/evaluator/base"
         | 
| 2 | 
            +
            require "active_reporter/evaluator/block"
         | 
| @@ -29,7 +29,6 @@ module ActiveReporter | |
| 29 29 |  | 
| 30 30 | 
             
                  def source_data
         | 
| 31 31 | 
             
                    @source_data ||= aggregators.values.reduce(groups) do |relation, aggregator|
         | 
| 32 | 
            -
                      # append each aggregator into the base relation (groups)
         | 
| 33 32 | 
             
                      relation.merge(aggregator.aggregate(base_relation))
         | 
| 34 33 | 
             
                    end
         | 
| 35 34 | 
             
                  end
         | 
| @@ -39,12 +38,11 @@ module ActiveReporter | |
| 39 38 | 
             
                  def aggregate
         | 
| 40 39 | 
             
                    tracker_dimension_key = :_tracker_dimension
         | 
| 41 40 |  | 
| 42 | 
            -
                    if trackable? && trackers.any?
         | 
| 41 | 
            +
                    if trackable? && trackers.any? && prior_bin_report.source_data.present?
         | 
| 43 42 | 
             
                      prior_obj = prior_bin_report.source_data.first
         | 
| 44 43 | 
             
                      prior_row = prior_bin_report.hashed_data.first.with_indifferent_access
         | 
| 45 44 |  | 
| 46 | 
            -
                       | 
| 47 | 
            -
                      prior_row[tracker_dimension_key] = results_key_prefix[0..-2]
         | 
| 45 | 
            +
                      prior_row[tracker_dimension_key] = groupers.without(tracker_dimension).map { |g| g.extract_sql_value(prior_obj) }
         | 
| 48 46 | 
             
                    else
         | 
| 49 47 | 
             
                      prior_obj = nil
         | 
| 50 48 | 
             
                      prior_row = {}
         | 
| @@ -87,7 +85,7 @@ module ActiveReporter | |
| 87 85 | 
             
                      # "author.id" value (bin) changes the tracker is reset so we do not track changes from the last day of each
         | 
| 88 86 | 
             
                      # "author.id" to the first day of the next "author.id".
         | 
| 89 87 | 
             
                      if trackable?
         | 
| 90 | 
            -
                        current_row[tracker_dimension_key] =  | 
| 88 | 
            +
                        current_row[tracker_dimension_key] = groupers.without(tracker_dimension).map { |g| g.extract_sql_value(current_obj) }
         | 
| 91 89 |  | 
| 92 90 | 
             
                        if current_row[tracker_dimension_key] == prior_row[tracker_dimension_key] && bins_are_adjacent?(current_obj, prior_obj)
         | 
| 93 91 | 
             
                          trackers.each do |name, tracker|
         | 
| @@ -199,11 +197,11 @@ module ActiveReporter | |
| 199 197 |  | 
| 200 198 | 
             
                    results.deep_merge!(results.collect do |row, value|
         | 
| 201 199 | 
             
                      calculators.collect do |name, calculator|
         | 
| 202 | 
            -
                        row_data = hash_raw_row(row, value, [ | 
| 200 | 
            +
                        row_data = hash_raw_row(row, value, ["totals"])
         | 
| 203 201 | 
             
                        calc_report = parent_report.total_report
         | 
| 204 202 |  | 
| 205 203 | 
             
                        parent_row = match_parent_row_for_calculator(row_data, calc_report, calculator)
         | 
| 206 | 
            -
                        [[ | 
| 204 | 
            +
                        [["totals", name.to_s], calculator.calculate(row_data, parent_row)] unless parent_row.nil?
         | 
| 207 205 | 
             
                      end
         | 
| 208 206 | 
             
                    end.flatten(1).to_h) unless parent_report.nil?
         | 
| 209 207 |  | 
| @@ -241,9 +239,9 @@ module ActiveReporter | |
| 241 239 | 
             
                    # tracker? Even if there is a "correct" method for one report it may not be correct for a different report. The
         | 
| 242 240 | 
             
                    # same problem applies to strings. Which character is after "z"? The ASCII hex value is "{", which would work
         | 
| 243 241 | 
             
                    # fine for ordering, but maybe not for determining when a tracker should be reset. Additionally, we need to
         | 
| 244 | 
            -
                    # deal with strings of different lengths. Alphabetically you could order  | 
| 245 | 
            -
                    # when to reset the tracker? If we get a new value of  | 
| 246 | 
            -
                    # tracker value for the  | 
| 242 | 
            +
                    # deal with strings of different lengths. Alphabetically you could order "A", "AA", "AAA", "B" but how do know
         | 
| 243 | 
            +
                    # when to reset the tracker? If we get a new value of "AAAA" we have entirelly new values used to calculate the
         | 
| 244 | 
            +
                    # tracker value for the "B" row, effectivally making the tracker values irrelevent.
         | 
| 247 245 | 
             
                    # Even going back to the integer example, the value allowed to be stored increments by 1, but there is no
         | 
| 248 246 | 
             
                    # guerentee that these are the actual values being used in the field.
         | 
| 249 247 | 
             
                    # For these reasons we will not attempt to track any dimension that does not specifically specify a bin width.
         | 
| @@ -270,7 +268,7 @@ module ActiveReporter | |
| 270 268 | 
             
                  end
         | 
| 271 269 |  | 
| 272 270 | 
             
                  def trackable?
         | 
| 273 | 
            -
                    @trackable ||= tracker_dimension | 
| 271 | 
            +
                    @trackable ||= tracker_dimension&.min.present?
         | 
| 274 272 | 
             
                  end
         | 
| 275 273 |  | 
| 276 274 | 
             
                  def evaluatable?
         | 
| @@ -278,7 +276,9 @@ module ActiveReporter | |
| 278 276 | 
             
                  end
         | 
| 279 277 |  | 
| 280 278 | 
             
                  def tracker_dimension
         | 
| 281 | 
            -
                    @tracker_dimension ||= groupers. | 
| 279 | 
            +
                    @tracker_dimension ||= (groupers.reverse + dimensions.values).detect do |dimension|
         | 
| 280 | 
            +
                      dimension.is_a?(ActiveReporter::Dimension::Bin) && %i[min max bin_width].all? { |a| dimension.try(a).present? }
         | 
| 281 | 
            +
                    end
         | 
| 282 282 | 
             
                  end
         | 
| 283 283 |  | 
| 284 284 | 
             
                  def prior_bin_report
         | 
| @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            require  | 
| 1 | 
            +
            require "active_reporter/inflector"
         | 
| 2 2 |  | 
| 3 3 | 
             
            module ActiveReporter
         | 
| 4 4 | 
             
              class Report
         | 
| @@ -6,7 +6,7 @@ module ActiveReporter | |
| 6 6 | 
             
                  extend ActiveSupport::Concern
         | 
| 7 7 |  | 
| 8 8 | 
             
                  METRICS = %i[aggregator calculator dimension tracker evaluator].collect do |type|
         | 
| 9 | 
            -
                    metrics = Dir.glob(File.join(__dir__,  | 
| 9 | 
            +
                    metrics = Dir.glob(File.join(__dir__, "..", type.to_s, "*.rb")).collect { |file| File.basename(file, ".rb") }.without(*%w[base bin]).collect(&:to_sym).sort.freeze
         | 
| 10 10 | 
             
                    [type, const_set(type.to_s.upcase, metrics)]
         | 
| 11 11 | 
             
                  end.to_h.sort.freeze
         | 
| 12 12 |  | 
| @@ -102,7 +102,7 @@ module ActiveReporter | |
| 102 102 |  | 
| 103 103 | 
             
                    # block_evaluator(:chargeback_ratio) { |row| supplemental_report_data.detect { |data| data[:id] == row[:id] }[:count] / row[:count] }
         | 
| 104 104 | 
             
                    def evaluator(name, evaluator_class, opts = {})
         | 
| 105 | 
            -
                      raise  | 
| 105 | 
            +
                      raise "needs block" unless opts.include?(:block)
         | 
| 106 106 | 
             
                      evaluators[name.to_sym] = { axis_class: evaluator_class, opts: opts }
         | 
| 107 107 | 
             
                    end
         | 
| 108 108 |  | 
| @@ -124,20 +124,20 @@ module ActiveReporter | |
| 124 124 | 
             
                        class_eval <<-METRIC_HELPERS, __FILE__, __LINE__ + 1
         | 
| 125 125 | 
             
                          def #{mertic}_#{type}(name, opts = {}, &block)
         | 
| 126 126 | 
             
                            opts[:block] = block if block_given?
         | 
| 127 | 
            -
                            #{type}(name, #{(type.to_s +  | 
| 127 | 
            +
                            #{type}(name, #{(type.to_s + "/" + mertic.to_s.singularize(:_gem_active_reporter)).camelize.sub(/.*\./, "")}, opts)
         | 
| 128 128 | 
             
                          end
         | 
| 129 129 | 
             
                        METRIC_HELPERS
         | 
| 130 130 | 
             
                      end
         | 
| 131 131 | 
             
                    end
         | 
| 132 132 |  | 
| 133 133 | 
             
                    def default_report_model
         | 
| 134 | 
            -
                      name.demodulize.sub(/Report$/,  | 
| 134 | 
            +
                      name.demodulize.sub(/Report$/, "").constantize
         | 
| 135 135 | 
             
                    rescue NameError
         | 
| 136 136 | 
             
                      raise $!, "#{$!} cannot be used as `report_on` class, please configure `report_on` in the report class", $!.backtrace
         | 
| 137 137 | 
             
                    end
         | 
| 138 138 |  | 
| 139 139 | 
             
                    def default_model
         | 
| 140 | 
            -
                      name.demodulize.sub(/Report$/,  | 
| 140 | 
            +
                      name.demodulize.sub(/Report$/, "").constantize
         | 
| 141 141 | 
             
                    end
         | 
| 142 142 |  | 
| 143 143 | 
             
                    def report_model
         | 
| @@ -1,5 +1,5 @@ | |
| 1 | 
            -
            require  | 
| 2 | 
            -
            require  | 
| 1 | 
            +
            require "active_reporter/inflector"
         | 
| 2 | 
            +
            require "active_reporter/invalid_params_error"
         | 
| 3 3 |  | 
| 4 4 | 
             
            module ActiveReporter
         | 
| 5 5 | 
             
              class Report
         | 
| @@ -19,11 +19,11 @@ module ActiveReporter | |
| 19 19 | 
             
                  end
         | 
| 20 20 |  | 
| 21 21 | 
             
                  def validate_configuration!
         | 
| 22 | 
            -
                    incomplete_message = [ | 
| 22 | 
            +
                    incomplete_message = ["You must declare at least one aggregator or tracker, and at lease one dimension to initialize a report", "See the README for more details"]
         | 
| 23 23 |  | 
| 24 24 | 
             
                    raise ActiveReporter::InvalidParamsError, ["#{self.class.name} does not declare any aggregators or trackers"].concat(incomplete_message).join(". ") if aggregators.empty?
         | 
| 25 25 | 
             
                    raise ActiveReporter::InvalidParamsError, ["#{self.class.name} does not declare any dimensions"].concat(incomplete_message).join(". ") if dimensions.except(:totals).empty?
         | 
| 26 | 
            -
                    raise ActiveReporter::InvalidParamsError,  | 
| 26 | 
            +
                    raise ActiveReporter::InvalidParamsError, "parent_report must be included in order to process calculations" if calculators.any? && parent_report.nil?
         | 
| 27 27 | 
             
                  end
         | 
| 28 28 |  | 
| 29 29 | 
             
                  def validate_aggregators!
         | 
| @@ -38,10 +38,13 @@ module ActiveReporter | |
| 38 38 | 
             
                    end
         | 
| 39 39 |  | 
| 40 40 | 
             
                    calculators.values.each do |calculator|
         | 
| 41 | 
            -
                       | 
| 41 | 
            +
                      case
         | 
| 42 | 
            +
                      when calculator.aggregator.nil?
         | 
| 42 43 | 
             
                        add_invalid_param_error(:calculator, ":#{calculator.name} must define an aggregator (should be in #{self.class.aggregator.keys})")
         | 
| 43 | 
            -
                       | 
| 44 | 
            +
                      when self.class.aggregators.exclude?(calculator.aggregator)
         | 
| 44 45 | 
             
                        add_invalid_param_error(:calculator, ":#{calculator.name} defines an invalid aggregator :#{calculator.aggregator} (should be in #{self.class.aggregators.keys})")
         | 
| 46 | 
            +
                      when params.include?(:aggregators) && aggregators.exclude?(calculator.aggregator)
         | 
| 47 | 
            +
                        params[:aggregators].push(calculator.aggregator)
         | 
| 45 48 | 
             
                      end
         | 
| 46 49 | 
             
                    end
         | 
| 47 50 | 
             
                  end
         | 
| @@ -52,10 +55,22 @@ module ActiveReporter | |
| 52 55 | 
             
                    end
         | 
| 53 56 |  | 
| 54 57 | 
             
                    trackers.values.each do |tracker|
         | 
| 55 | 
            -
                       | 
| 58 | 
            +
                      case
         | 
| 59 | 
            +
                      when tracker.aggregator.nil?
         | 
| 56 60 | 
             
                        add_invalid_param_error(:tracker, ":#{tracker.name} must define an aggregator (should be in #{self.class.aggregator.keys})")
         | 
| 57 | 
            -
                       | 
| 61 | 
            +
                      when self.class.aggregators.exclude?(tracker.aggregator)
         | 
| 58 62 | 
             
                        add_invalid_param_error(:tracker, ":#{tracker.name} defines an invalid aggregator :#{tracker.aggregator} (should be in #{self.class.aggregators.keys})")
         | 
| 63 | 
            +
                      when params.include?(:aggregators) && aggregators.exclude?(tracker.aggregator)
         | 
| 64 | 
            +
                        params[:aggregators].push(tracker.aggregator)
         | 
| 65 | 
            +
                      end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                      if tracker.opts.include?(:prior_aggregator)
         | 
| 68 | 
            +
                        case
         | 
| 69 | 
            +
                        when self.class.aggregators.exclude?(tracker.prior_aggregator)
         | 
| 70 | 
            +
                          add_invalid_param_error(:tracker, ":#{tracker.name} defines an invalid prior aggregator :#{tracker.prior_aggregator} (should be in #{self.class.aggregators.keys})")
         | 
| 71 | 
            +
                        when params.include?(:aggregators) && aggregators.exclude?(tracker.prior_aggregator)
         | 
| 72 | 
            +
                          params[:aggregators].push(tracker.prior_aggregator)
         | 
| 73 | 
            +
                        end
         | 
| 59 74 | 
             
                      end
         | 
| 60 75 | 
             
                    end
         | 
| 61 76 | 
             
                  end
         | 
| @@ -66,8 +81,8 @@ module ActiveReporter | |
| 66 81 | 
             
                      invalid_groupers_message = [
         | 
| 67 82 | 
             
                        [
         | 
| 68 83 | 
             
                          invalid_groupers.to_sentence,
         | 
| 69 | 
            -
                          (invalid_groupers.one? ?  | 
| 70 | 
            -
                        ].join( | 
| 84 | 
            +
                          (invalid_groupers.one? ? "is not a" : "are not"), "valid", "dimension".pluralize(invalid_groupers.count, :_gem_active_reporter)
         | 
| 85 | 
            +
                        ].join(" "),
         | 
| 71 86 | 
             
                        "declared dimension include #{dimensions.keys.to_sentence}"
         | 
| 72 87 | 
             
                      ].join(". ")
         | 
| 73 88 | 
             
                      add_invalid_param_error(:groupers, invalid_groupers_message)
         | 
| @@ -75,11 +90,11 @@ module ActiveReporter | |
| 75 90 | 
             
                  end
         | 
| 76 91 |  | 
| 77 92 | 
             
                  def validate_parent_report!
         | 
| 78 | 
            -
                    add_invalid_param_error(:parent_report,  | 
| 93 | 
            +
                    add_invalid_param_error(:parent_report, "must be an instance of ActiveReporter::Report") unless parent_report.nil? || parent_report.kind_of?(ActiveReporter::Report)
         | 
| 79 94 | 
             
                  end
         | 
| 80 95 |  | 
| 81 96 | 
             
                  def validate_total_report!
         | 
| 82 | 
            -
                    add_invalid_param_error(:total_report,  | 
| 97 | 
            +
                    add_invalid_param_error(:total_report, "must be an instance of ActiveReporter::Report") unless @total_report.nil? || @total_report.kind_of?(ActiveReporter::Report)
         | 
| 83 98 | 
             
                  end
         | 
| 84 99 |  | 
| 85 100 | 
             
                  private
         | 
| @@ -99,7 +114,7 @@ module ActiveReporter | |
| 99 114 | 
             
                  end
         | 
| 100 115 |  | 
| 101 116 | 
             
                  def error_message
         | 
| 102 | 
            -
                    (["The report configuration contains the following #{ | 
| 117 | 
            +
                    (["The report configuration contains the following #{"error".pluralize(errors.count, :_gem_active_reporter)}:"] + errors).join("\n - ")
         | 
| 103 118 | 
             
                  end
         | 
| 104 119 | 
             
                end
         | 
| 105 120 | 
             
              end
         | 
| @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            Dir.glob(File.join(__dir__,  | 
| 1 | 
            +
            Dir.glob(File.join(__dir__, "report", "*.rb")).each { |file| require file }
         | 
| 2 2 |  | 
| 3 3 | 
             
            module ActiveReporter
         | 
| 4 4 | 
             
              class Report
         | 
| @@ -18,8 +18,8 @@ module ActiveReporter | |
| 18 18 | 
             
                  # When using a Calculator you may need the parent report data. Pass in a ActiveReporter::Report object when
         | 
| 19 19 | 
             
                  # instantiating a new ActiveReporter::Report instance as :parent_report. This will allow you to calculate a data
         | 
| 20 20 | 
             
                  # based on the #total_report of this passed :parent_report. For example, if the parent report includes a sum
         | 
| 21 | 
            -
                  # aggregated  | 
| 22 | 
            -
                  # on a given row versus the total  | 
| 21 | 
            +
                  # aggregated "views" column, the child report can use Report::Calculator::Ratio to caluclate the ratio of "views"
         | 
| 22 | 
            +
                  # on a given row versus the total "views" from the parent report.
         | 
| 23 23 | 
             
                  @parent_report = @params.delete(:parent_report)
         | 
| 24 24 | 
             
                  @parent_groupers = @params.delete(:parent_groupers) || ( grouper_names & Array(parent_report&.grouper_names) )
         | 
| 25 25 |  | 
| @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            require  | 
| 1 | 
            +
            require "active_reporter/inflector"
         | 
| 2 2 |  | 
| 3 3 | 
             
            module ActiveReporter
         | 
| 4 4 | 
             
              module Serializer
         | 
| @@ -13,7 +13,7 @@ module ActiveReporter | |
| 13 13 | 
             
                  # on the aggregators or dimension name.
         | 
| 14 14 |  | 
| 15 15 | 
             
                  def human_aggregator_label(aggregators)
         | 
| 16 | 
            -
                    aggregators.keys.collect { |aggregator| aggregator.to_s.humanize }.join( | 
| 16 | 
            +
                    aggregators.keys.collect { |aggregator| aggregator.to_s.humanize }.join(" ")
         | 
| 17 17 | 
             
                  end
         | 
| 18 18 |  | 
| 19 19 | 
             
                  def human_dimension_label(dimension)
         | 
| @@ -62,8 +62,8 @@ module ActiveReporter | |
| 62 62 |  | 
| 63 63 | 
             
                  def time_formats
         | 
| 64 64 | 
             
                    {
         | 
| 65 | 
            -
                      minutes:  | 
| 66 | 
            -
                      weeks:  | 
| 65 | 
            +
                      minutes: "%F %k:%M", hours: "%F %k", days: "%F",
         | 
| 66 | 
            +
                      weeks: "week of %F", months: "%Y-%m", years: "%Y"
         | 
| 67 67 | 
             
                    }
         | 
| 68 68 | 
             
                  end
         | 
| 69 69 |  | 
| @@ -95,8 +95,8 @@ module ActiveReporter | |
| 95 95 | 
             
                    report.filters.flat_map do |dimension|
         | 
| 96 96 | 
             
                      human_dimension_label(dimension) + " = " + dimension.filter_values.map do |value|
         | 
| 97 97 | 
             
                        human_dimension_value_label(dimension, value)
         | 
| 98 | 
            -
                      end.to_sentence(last_word_connector:  | 
| 99 | 
            -
                    end.join( | 
| 98 | 
            +
                      end.to_sentence(last_word_connector: ", or ")
         | 
| 99 | 
            +
                    end.join("; ")
         | 
| 100 100 | 
             
                  end
         | 
| 101 101 | 
             
                end
         | 
| 102 102 | 
             
              end
         | 
| @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            require  | 
| 1 | 
            +
            require "csv"
         | 
| 2 2 |  | 
| 3 3 | 
             
            module ActiveReporter
         | 
| 4 4 | 
             
              module Serializer
         | 
| @@ -11,7 +11,7 @@ module ActiveReporter | |
| 11 11 | 
             
                  end
         | 
| 12 12 |  | 
| 13 13 | 
             
                  def save(filename = self.filename)
         | 
| 14 | 
            -
                    File.open(filename,  | 
| 14 | 
            +
                    File.open(filename, "w") { |f| f.write data }
         | 
| 15 15 | 
             
                  end
         | 
| 16 16 |  | 
| 17 17 | 
             
                  def filename
         |