OpenProject is the leading open source project management software.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
openproject/lib/report.rb

166 lines
4.1 KiB

require 'forwardable'
require 'proactive_autoloader'
class Report < ActiveRecord::Base
extend ProactiveAutoloader
extend Forwardable
include Enumerable
belongs_to :user
belongs_to :project
before_save :serialize
serialize :serialized, Hash
self.abstract_class = true # lets have subclasses have their own SQL tables
def self.accepted_properties
@@accepted_properties ||= []
end
def self.chain_initializer
@chain_initializer ||= []
end
def self.deserialize(hash, object = self.new)
object.tap do |q|
hash[:filters].each {|name, opts| q.filter(name, opts) }
hash[:group_bys].each {|name, opts| q.group_by(name, opts) }
end
end
def serialize
# have to take the reverse to retain the original order when deserializing
self.serialized = { :filters => filters.collect(&:serialize).reverse, :group_bys => group_bys.collect(&:serialize).reverse }
end
def deserialize
unless @chain
hash = serialized || serialize
self.class.deserialize(hash, self)
else
raise ArgumentError, "Cannot deserialize a report which already has a chain"
end
end
##
# Migrates this report to look like the given report.
# This may be used to alter report properties without
# creating a new report in a database.
def migrate(report)
[:@chain, :@query, :@transformer, :@walker, :@table, :@depths, :@chain_initializer].each do |inst_var|
instance_variable_set inst_var, (report.instance_variable_get inst_var)
end
end
def available_filters
self.class::Filter.all
end
def transformer
@transformer ||= self.class::Transformer.new self
end
def walker
@walker ||= self.class::Walker.new self
end
def add_chain(type, name, options)
chain type.const_get(name.to_s.camelcase), options
@transformer, @table, @depths, @walker = nil, nil, nil, nil
self
end
def chain(klass = nil, options = {})
build_new_chain unless @chain
@chain = klass.new @chain, options if klass
@chain = @chain.parent until @chain.top?
@chain
end
def build_new_chain
#FIXME: is there a better way to load all filter and groups?
self.class::Filter.all && self.class::GroupBy.all
minimal_chain!
self.class.chain_initializer.each { |block| block.call self }
self
end
def filter(name, options = {})
add_chain self.class::Filter, name, options
end
def group_by(name, options = {})
add_chain self.class::GroupBy, name, options.reverse_merge(:type => :column)
end
def column(name, options = {})
group_by name, options.merge(:type => :column)
end
def row(name, options = {})
group_by name, options.merge(:type => :row)
end
def table
@table = self.class::Table.new(self)
end
def group_bys
chain.select { |c| c.group_by? }
end
def filters
chain.select { |c| c.filter? }
end
def depth_of(name)
@depths ||= {}
@depths[name] ||= chain.inject(0) { |sum, child| child.type == name ? sum + 1 : sum }
end
def_delegators :transformer, :column_first, :row_first
def_delegators :chain, :empty_chain, :top, :bottom, :chain_collect, :sql_statement, :all_group_fields, :child, :clear, :result
def_delegators :result, :each_direct_result, :recursive_each, :recursive_each_with_level, :each, :each_row, :count,
:units, :size, :final_number
def_delegators :table, :row_index, :colum_index
def to_a
chain.to_a
end
def to_s
chain.to_s
end
def hash
report_string = ""
report_string.concat('filters: [')
report_string.concat(filters.map { |f|
f.class.underscore_name + f.operator.to_s + (f.values ? f.values.to_json : "")
}.sort.join(', '))
report_string.concat(']')
report_string.concat(', group_bys: {')
report_string.concat(group_bys.group_by(&:type).map { |t, gbs|
"#{t} : [#{gbs.collect(&:class).collect(&:underscore_name).join(', ')}]"
}.join(', '))
report_string.concat('}')
report_string.hash
end
def == another_report
hash == another_report.hash
end
private
def minimal_chain!
@chain = self.class::Filter::NoFilter.new
end
end