class Repor::Report

Attributes

params[R]

Public Class Methods

aggregator(name, aggregator_class, opts = {}) click to toggle source
# File lib/repor/report.rb, line 110
def aggregator(name, aggregator_class, opts = {})
  aggregators[name.to_sym] = { axis_class: aggregator_class, opts: opts }
end
aggregators() click to toggle source
# File lib/repor/report.rb, line 106
def aggregators
  @aggregators ||= {}
end
autoreport_association_name_columns(reflection) click to toggle source

override this to change which columns of the association are used to auto-label it

# File lib/repor/report.rb, line 182
def autoreport_association_name_columns(reflection)
  %w(name email title)
end
autoreport_column(column) click to toggle source

can override this method to skip or change certain column declarations

# File lib/repor/report.rb, line 159
def autoreport_column(column)
  return if column.name == 'id'
  belongs_to_ref = klass.reflections.find { |_, a| a.foreign_key == column.name }
  if belongs_to_ref
    name, ref = belongs_to_ref
    name_col = (ref.klass.column_names & autoreport_association_name_columns(ref)).first
    if name_col
      name_expr = "#{ref.klass.table_name}.#{name_col}"
      category_dimension name, expression: name_expr, relation: ->(r) { r.joins(name) }
    else
      category_dimension column.name
    end
  elsif column.cast_type.type == :datetime
    time_dimension column.name
  elsif column.cast_type.number?
    number_dimension column.name
  else
    category_dimension column.name
  end
end
autoreport_on(class_or_name) click to toggle source

autoreporting will automatically define dimensions based on columns

# File lib/repor/report.rb, line 152
def autoreport_on(class_or_name)
  report_on class_or_name
  klass.columns.each(&method(:autoreport_column))
  count_aggregator :count if aggregators.blank?
end
default_class() click to toggle source
# File lib/repor/report.rb, line 130
def default_class
  self.name.demodulize.sub(/Report$/, '').constantize
end
dimension(name, dimension_class, opts = {}) click to toggle source
# File lib/repor/report.rb, line 102
def dimension(name, dimension_class, opts = {})
  dimensions[name.to_sym] = { axis_class: dimension_class, opts: opts }
end
dimensions() click to toggle source
# File lib/repor/report.rb, line 98
def dimensions
  @dimensions ||= {}
end
inherited(subclass) click to toggle source

ensure subclasses gain any aggregators or dimensions defined on their parents

# File lib/repor/report.rb, line 145
def inherited(subclass)
  instance_values.each do |ivar, ival|
    subclass.instance_variable_set(:"@#{ivar}", ival.dup)
  end
end
klass() click to toggle source
# File lib/repor/report.rb, line 134
def klass
  @klass ||= default_class
rescue NameError
  raise NameError, "must specify a class to report on, e.g. `report_on Post`"
end
new(params = {}) click to toggle source
# File lib/repor/report.rb, line 7
def initialize(params = {})
  @params = params.deep_symbolize_keys.deep_dup
  deep_strip_blanks(@params) unless @params[:strip_blanks] == false
  validate_params!
end
report_on(class_or_name) click to toggle source
# File lib/repor/report.rb, line 140
def report_on(class_or_name)
  @klass = class_or_name.to_s.constantize
end

Public Instance Methods

aggregator() click to toggle source
# File lib/repor/report.rb, line 25
def aggregator
  @aggregator ||= aggregators[aggregator_name]
end
aggregator_name() click to toggle source
# File lib/repor/report.rb, line 21
def aggregator_name
  params.fetch(:aggregator, default_aggregator_name).to_sym
end
aggregators() click to toggle source
# File lib/repor/report.rb, line 17
def aggregators
  @aggregators ||= build_axes(self.class.aggregators)
end
base_relation() click to toggle source
# File lib/repor/report.rb, line 47
def base_relation
  params.fetch(:relation, klass.all)
end
data() click to toggle source
# File lib/repor/report.rb, line 93
def data
  nested_data
end
dimensions() click to toggle source
# File lib/repor/report.rb, line 13
def dimensions
  @dimensions ||= build_axes(self.class.dimensions)
end
filters() click to toggle source
# File lib/repor/report.rb, line 39
def filters
  @filters ||= dimensions.values.select(&:filtering?)
end
flat_data() click to toggle source

flat hash of { [x1, x2, x3] => y }

# File lib/repor/report.rb, line 83
def flat_data
  @flat_data ||= Hash[group_values.map { |x| [x, raw_data[x]] }]
end
group_values() click to toggle source
# File lib/repor/report.rb, line 77
def group_values
  @group_values ||= all_combinations_of(groupers.map(&:group_values))
end
grouper_names() click to toggle source
# File lib/repor/report.rb, line 29
def grouper_names
  names = params.fetch(:groupers, default_grouper_names)
  names = names.is_a?(Hash) ? names.values : Array.wrap(names)
  names.map(&:to_sym)
end
groupers() click to toggle source
# File lib/repor/report.rb, line 35
def groupers
  @groupers ||= dimensions.values_at(*grouper_names)
end
groups() click to toggle source
# File lib/repor/report.rb, line 67
def groups
  @groups ||= groupers.reduce(records) do |relation, dimension|
    dimension.group(relation)
  end
end
nested_data() click to toggle source

nested array of

{ key: x3, values: [{ key: x2, values: [{ key: x1, value: y }

}] }]

# File lib/repor/report.rb, line 89
def nested_data
  @nested_data ||= nest_data
end
raw_data() click to toggle source
# File lib/repor/report.rb, line 73
def raw_data
  @raw_data ||= aggregator.aggregate(groups)
end
records() click to toggle source
# File lib/repor/report.rb, line 61
def records
  @records ||= filters.reduce(relation) do |relation, dimension|
    dimension.filter(relation)
  end
end
relation() click to toggle source
# File lib/repor/report.rb, line 55
def relation
  @relation ||= relators.reduce(base_relation) do |relation, dimension|
    dimension.relate(relation)
  end
end
relators() click to toggle source
# File lib/repor/report.rb, line 43
def relators
  filters | groupers
end
table_name() click to toggle source
# File lib/repor/report.rb, line 51
def table_name
  klass.table_name
end

Private Instance Methods

all_combinations_of(values) click to toggle source
# File lib/repor/report.rb, line 191
        def all_combinations_of(values)
  values[0].product(*values[1..-1])
end
build_axes(axes) click to toggle source
# File lib/repor/report.rb, line 187
        def build_axes(axes)
  axes.map { |name, h| [name, h[:axis_class].new(name, self, h[:opts])] }.to_h
end
deep_strip_blanks(hash, depth = 0) click to toggle source
# File lib/repor/report.rb, line 250
        def deep_strip_blanks(hash, depth = 0)
  raise "very deep hash or, more likely, internal error" if depth > 100
  hash.delete_if do |key, value|
    strippable_blank?(
      case value
      when Hash then deep_strip_blanks(value, depth + 1)
      when Array then value.reject!(&method(:strippable_blank?))
      else value
      end
    )
  end
end
default_aggregator_name() click to toggle source
# File lib/repor/report.rb, line 235
        def default_aggregator_name
  aggregators.keys.first
end
default_grouper_names() click to toggle source
# File lib/repor/report.rb, line 239
        def default_grouper_names
  [dimensions.keys.first]
end
invalid_param!(param_key, message) click to toggle source
# File lib/repor/report.rb, line 231
        def invalid_param!(param_key, message)
  raise InvalidParamsError, "Invalid value for params[:#{param_key}]: #{message}"
end
nest_data(groupers=self.groupers, prefix=[]) click to toggle source
# File lib/repor/report.rb, line 195
        def nest_data(groupers=self.groupers, prefix=[])
  head, rest = groupers.last, groupers[0..-2]
  head.group_values.map do |x|
    if rest.any?
      { key: x, values: nest_data(rest, [x]+prefix) }
    else
      { key: x, value: raw_data[([x]+prefix)] }
    end
  end
end
strippable_blank?(value) click to toggle source
# File lib/repor/report.rb, line 243
        def strippable_blank?(value)
  case value
  when String, Array, Hash then value.blank?
  else false
  end
end
validate_params!() click to toggle source
# File lib/repor/report.rb, line 206
        def validate_params!
  incomplete_msg = "You must declare at least one aggregator and one " \
    "dimension to initialize a report. See the README for more details."

  if aggregators.blank?
    raise Repor::InvalidParamsError, "#{self.class} doesn't have any " \
      "aggregators declared! #{incomplete_msg}"
  end

  if dimensions.blank?
    raise Repor::InvalidParamsError, "#{self.class} doesn't have any " \
      "dimensions declared! #{incomplete_msg}"
  end

  unless aggregator.present?
    invalid_param!(:aggregator,
      "#{aggregator_name} is not a valid aggregator (should be in #{aggregators.keys})")
  end

  unless groupers.all?(&:present?)
    invalid_param!(:groupers,
      "one of #{grouper_names} is not a valid dimension (should all be in #{dimensions.keys})")
  end
end