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/doc/refactoring.rb

252 lines
5.7 KiB

# This is no actual runnable ruby code.
# It merely presents our ideas to refactor the CostQuery model
class Operator
def self.new(name, &block)
all[name] ||= super
end
def self.all
@all ||= {}
end
def self.find(name)
Operator.all[name] or raise "Operator not defined"
end
def initialize(name, &block)
eigenclass.class_eval(&block)
end
def eigenclass
class << self
self
end
end
end
Operator.new(:=) do
# Example to define and register an operator
def sql_where()
end
end
class Filter
def self.new
# this class is abstract. instances are only allowed from child classes
raise "#{self.name} is an abstract class" if self == Filter
super
end
def self.column(column = nil)
@column = column if column
@column
end
def self.operators(*operators)
# Does it make sense to just store the names here and perform Operator.find
# if needed?
operators.each do |o|
o = Operator.find(o) unless o.is_a? Operator
@operators << o
end
end
# Store the default operator
# NOTE: this should be implemented explictly
cattr_accessor :default_operator
def self.from_hash
# deserialize a new filter object from a hash
# NOTE: this might also be used to create a filter object from browse
# parameters
# ...
end
def to_hash
# serialize self to a hash suitable for later deserialization with
# Filter.from_hash
# This can be used to save the filter to a database or to create a part of
# the query string in the view
# ...
end
attr_accessor :operator
def sql_select
# returns the default select part of a query
# This might be overwritten in child classes
# NOTE: this might be changed to an item of the :include array of an ActiveRecord::Base.find
"#{model.table_name}.#{db_field} as #{self.class.name.underscore}"
end
def sql_where()
# returns the default where part of a query
# This might be overwritten in child classes to provide special logic besides
# standard operators.
#
# NOTE: This should be suitable to be used in :conditions in an ActiveRecord::Base.find
Operator.find(operator).sql_where()
end
# self.model
# self.db_field
# available_values(project)
def sql_joins(otiginal_table)
# returns an array of all needed joins
# original_table is thw name of the original table of the join (e.g. time_entries or cost_entries)
# NOTE: this might be used to generate :include items
["JOIN issues ON #{table}.issue_id = issues.id", "JOIN users on #{table}.user_id = user.id"]
end
end
class FooFilter < FilterColumn
# This is an example definition of a folter column class
operators :=, :!=, :<=, :>=, :<&>
column :foo
model :issues
def sql_where(table)
if operator == "<&>"
# special logic
else
super
end
end
end
# another example which uses another layer ofg inheritance to provide filter types
class SimpleListFilter << FilterColumn
operators :=, :!=
end
class UserFilter < SimpleListFilter
column :user_id
end
# e.g. in CostQuery
def create_join_statement(table)
all_filters.map { |f| f.join_condition(table) }.flatten.uniq
end
# The following two classes represent the result of a group-by operation
class ReportGroupOfGroups < Array
def sum
@sum ||= inject(0) { |e| e.sum }
end
def count
@count ||= inject(0) { |e| e.cont }
end
def has_children?
true
end
def drill_down_filter
# this uses the parent pointer of the GroupBy instance
# ...
end
def recursive_each(level = 0, &block)
block.call(level, self)
each { |child| child.recursive_each(level + 1, &block) }
end
end
class ReportGroup
def initialize(hash_from_database)
@data = hash_from_database
end
def count
@data["count"]
end
def has_children?
false
end
def recursive_each(level = 0, &block)
block.call(level, self)
end
end
class GroupBy
# This provides the stared functionality of a group-by columnh
module BasicGroupBy
# Module to be used, if this instance is the group-by with the finest
# granularity (or the single one)
def filters
[filter_for_group, @based_on]
end
def results(columns = nil)
columns << my_column
"SELECT count() FROM #{@based_on.sql_statement} GROUP_BY #{columns.uniq.join(", ")}"
end
end
module GroupOfGroupBy
# Module to use for higher granularity
def results(columns = nil)
group @based_on.results(columns)
end
def filters()
[filter_for_group] << @based_on.filters
end
end
attr_accessor :parent
def initialize(based_on)
# NOTE: based_on should actually be an array of filters
if based_on.is_a? Filter
extend BasicGroupBy
elsif based_on.is_a? GroupBy
extend GroupOfGroupBy
# provide a parent pointer of the tree
based_on.parent = self
end
end
def filter_for_group
# create filter from group by from drill down
# NOTE: this does not make sense here (???)
# ...
end
end
class GroupByName < GroupBy
def results(columns)
columns.delete :dont_like
super
end
end
# Example to define a group by hierarchy
filter = Filter.new
erste_verschachtellung = GroupByName.new(filter)
zweite_verschachtellung = GroupByIssue.new(erste_verschachtellung)
# execute the call and get the results (as an instance of ReportGroupOfGroups or ReportGroup)
zweite_verschachtellung.results.sum
# get the respective filter for the drill down into this group
zweite_verschachtellung.results.first.drill_down_filter
# display all groups in a tree-like view
zweite_verschachtellung.recursive_each do |level, group|
puts ">"*level, group.count, group.sum
end