Skip to content

Instantly share code, notes, and snippets.

@faraazkhan
Created May 30, 2012 17:10
Show Gist options
  • Save faraazkhan/2837702 to your computer and use it in GitHub Desktop.
Save faraazkhan/2837702 to your computer and use it in GitHub Desktop.
Reporting Engine Sample Code
class Reporter
include ActionController::UrlWriter
include ActionController::Routing::Helpers
attr_accessor :models_to_include
def initialize(params)
@params = replace_placeholder_values(params.dup)
if @params['klass']
@query_object = self
self.models_to_include = []
@order, @order_metadata = build_order_conditions
@limit = @params.delete(:limit) || 1000
@params.delete(:include) # don't allow this
@filters = @params.delete(:filters)
@filters = @filters.first if @filters.present?
@klass = @params.delete('klass').constantize
elsif @params['custom_query']
@query_object = Kernel.const_get("CustomQueries").const_get(@params['custom_query']).new(@params, self)
@filters = @params.delete(:filters)
@filters = @filters.first if @filters.present?
end
end
def build_order_conditions
cols = {}
order = []
order_metadata = []
traits = user_selected_attributes
@params.each do |key, value|
if matches = key.to_s.match(/^(asc|desc)end_by_(.*)$/)
order[value.to_i] = [ traits.index(matches[2]), (matches[1] === 'asc' ? 1 : -1) ]
order_metadata[value.to_i] = [ matches[2], matches[1] ]
end
end
@params.delete_if { |key, value| key =~ /^(asc|desc)end_by_(.*)$/ }
return order, order_metadata
end
def generate(options = {})
format = options[:format] || :json
case format
when :csv
FasterCSV.generate do |csv|
csv << @query_object.user_selected_attributes.map { |elem| label(elem) }
@query_object.to_sorted_grid(:no_link => true).each do |row|
csv << row
end
end
else
{
:metadata => { :order => (@order_metadata || []), :columns => @query_object.user_selected_attributes.map{|att| label(att)} },
:data => @query_object.to_sorted_grid
}
end
end
def matching_records
klass.searchlogic(@params).all(:limit => @limit)
end
def user_selected_attributes
return @user_selected_attrs if @user_selected_attrs
@user_selected_attrs = @params.delete(:select) || []
@user_selected_attrs.delete_if { |a| a.blank? }
@user_selected_attrs
end
def klass
@klass ||= @params.delete('klass').try(:constantize)
end
def to_sorted_grid(options = {})
result = format( sort( filter( to_record_grid(options) ) ) )
if options[:no_link]
result.each { |r| r.slice!(0) }
end
result
end
# Redirect the user to the correct drill-down detail page for each
# type of record, instead of relying on the item at the tail end of
# the lineage.
def drilldown_url_for(record)
case record
when Complaint, RequiredAction, Mandate, MandateRespondent
polymorphic_path(record.lineage)
when Task, Meeting
polymorphic_path(record.workitem.lineage)
when EventRecord
url_for(record.auditable)
end
end
def to_record_grid(options = {})
records = matching_records
traits = user_selected_attributes
results = []
records.each do |record|
row = [drilldown_url_for(record)]
traits.each do |col|
obj = record
col.split('.').each do |msg|
break if msg.nil? || obj.nil? || (msg != 'count' && (obj.respond_to?(:traits) && !obj.class.traits.include?(msg)))
val = obj.__send__(msg)
val = val.name if msg == 'type'
obj = val
end
row << obj
end
@types ||= Reporter.extract_types(row)
results << row
end
results
end
def self.extract_types(row)
types = []
row.each_with_index do |item, i|
if item.is_a?(DateTime) || item.is_a?(ActiveSupport::TimeWithZone)
types[i] = 'DateTime'
elsif item.is_a?(Date)
types[i] = 'Date'
elsif item.is_a?(TrueClass) || item.is_a?(FalseClass)
types[i] = 'Boolean'
else
types[i] = item.class.to_s
end
end
types
end
def format(grid)
student_id_index = @user_selected_attrs.index('student_id')
grid.each do |row|
student_id_index ||= row.length
row.each_with_index do |item, i|
item ||= false if (student_id_index + 1) == i
next if item.nil?
if @types[i] == 'Boolean'
row[i] = item ? 'Yes' : 'No'
elsif @types[i] == 'Date'
row[i] = Date.parse(item.to_s).strftime("%m/%d/%Y")
elsif @types[i] == 'DateTime'
row[i] = DateTime.parse(item.to_s).strftime("%m/%d/%Y %l:%M%p")
elsif (student_id_index + 1) == i && row[i].blank?
row[i] = 'Student Not Identified'
end
end
end
grid
end
def extract_filter_metadata
filters = []
return filters unless @filters
traits = @query_object.user_selected_attributes
@filters.each do |key, value|
if matches = key.to_s.match(/^(.*)_(does_not_equal|equals|gte|lte)$/)
operator = ''
select_or_reject = ''
datatype_method = case matches[1]
when /_date_?/, /_at/
'to_date'
else
'to_s'
end
case matches[2]
when 'equals'
operator = '=='
select_or_reject = 'select'
when 'does_not_equal'
operator = '=='
select_or_reject = 'reject'
when 'gte'
operator = '>='
select_or_reject = 'select'
when 'lte'
operator = '<='
select_or_reject = 'select'
end
if traits.index(matches[1])
filters << [ select_or_reject, datatype_method, traits.index(matches[1]), operator, value ]
end
end
end
filters
end
def filter(grid)
filters = extract_filter_metadata
traits = @query_object.user_selected_attributes
#debugger if traits.include?("Timeliness Status")
filters.each do |filter|
if filter[0] == 'select'
grid = grid.select do |item|
item[filter[2] + 1].try(filter[1]).try(filter[3], filter[4].try(filter[1]))
end.compact
elsif filter[0] == 'reject'
grid = grid.reject do |item|
item[filter[2] + 1].try(filter[1]).try(filter[3], filter[4].try(filter[1]))
end.compact
end
end
# optional - be sensitive to datatype (int, date, etc)
grid
end
def sort(grid)
grid.sort! do |elem1, elem2|
ret_val = 0
@order.each do |colidx, order_type|
i = colidx + 1 # we have to ignore the id column
if elem1[i] != elem2[i]
ret_val = (order_type * (elem1[i] <=> elem2[i]))
break
end
end
ret_val
end
end
def label(attr)
return attr unless klass
attr = attr.sub(/\.to_(.*)$/,'')
attr_list = attr.split('.')
default_value = klass.human_attribute_name(attr.gsub('.','_')).titleize
keys = []
keys << attr_list[-2..-1].join('.') if attr_list[-2..-1]
keys << attr_list[-2] if attr_list[-2]
keys << attr_list.last
keys.map! {|key| "reporter.#{key}".to_sym }
keys << default_value
new_label = I18n.t keys.first, :default => keys
new_label
end
private
def replace_placeholder_values(params)
params.each do |key, value|
if value.is_a?(String) && value =~ /\<(.*)\>/
params[key] = replacement_value($~[1])
end
end
if params[:filters].present?
params[:filters].first.each do |key, value|
if value.is_a?(String) && value =~ /\<(.*)\>/
params[:filters].first[key] = replacement_value($~[1])
end
end
end
if params[:pre_filter].present?
params[:pre_filter].each do |key, value|
if value.is_a?(String) && value =~ /\<(.*)\>/
params[:pre_filter][key] = replacement_value($~[1])
end
end
end
return params
end
def replacement_value(placeholder)
case placeholder
when 'current_user_id'
User.current.id
when 'now'
Time.now
when /(\d+)_days_from_now/
$~[1].to_i.days.from_now.to_date.to_s
when /(\d+)_days_ago/
$~[1].to_i.days.ago.to_date.to_s
when /^(beginning|end)_of_month$/
DateTime.now.send(placeholder)
when 'court_year_start'
COURT_YEAR_START
when 'court_year_end'
COURT_YEAR_END
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment