Viewing file: ruby/reporting/Report.rb | Back to directory listing
Author: Loren Segal | Last modified: February 21 2006 12:00 am | Download

module Reporting
  
  class Base
    include ActionController::Pagination
    cattr_accessor :template_root
    @@template_root = "app/views/reports/"
    
    cattr_accessor :template_name
    @@template_name = nil
 
    cattr_accessor :logger
    @@logger = nil
 
    cattr_accessor :sql_tables
    @@sql_tables = []
    
    cattr_accessor :sql_group_by
    @@sql_group_by = ''
    
    cattr_accessor :sql_order_by
    @@sql_order_by = ''
    
    cattr_accessor :sql_conditions
    @@sql_conditions = ''
    
    cattr_accessor :sql_limit
    @@sql_limit = nil
    
    cattr_accessor :sql_offset
    @@sql_offset = 0
    
    cattr_accessor :columns_attr
    @@columns_attr = {}
    
    attr_accessor :results
    attr_accessor :pages
    attr_accessor :report_name
    attr_accessor :controller
 
    class << self
      def generate(controller_name, report_name = 'report', options = {})
        report = self.new(controller_name, report_name, options)
        report.render_view
      end
 
    private
      def columns(*args)
        @@columns_attr = HashArray.new(args)
      end
      
      def conditions(condition)
        @@sql_conditions = condition
      end
      
      def group_by(group)
        @@sql_group_by = group
      end
      
      def order_by(order)
        @@sql_order_by= order
      end
      
      def tables(*args)
        @@sql_tables = args.collect {|a| a.to_s }
      end
      
      def limit(max)
        @@sql_limit = max
      end
      
      def offset(off)
        @@sql_offset = off
      end
 
      def layout(name)
        @@template_name = name
      end
      
      def template_name
        @@template_name || (self == Reporting::Base ? 'default' : self.to_s.underscore)
      end
    end
 
    def override_sql_options!(options)
      default_keys = [:group_by, :order_by, :conditions, :tables, :limit, :offset]
      options.symbolize_keys.delete_if {|key, value| !default_keys.include? key }
      class_options = {
        :conditions => sql_conditions,
        :tables => sql_tables,
        :group_by => sql_group_by,
        :order_by => sql_order_by,
        :limit => sql_limit,
        :offset => sql_offset
      }
      class_options.update(options)
    end
 
    def initialize(controller_name, report_name, options)
      @results, @pages, @report_name, @controller = [], nil, report_name, controller_name
      sql = override_sql_options!(options)
      main_sql =  "FROM #{sql[:tables].join(", ")} " +
                  (sql[:conditions].blank? ? "" : "WHERE #{sql[:conditions]} ") +
                  (sql[:order_by].blank? ? "" :  "ORDER BY #{sql[:order_by]} ") +
                  (sql[:group_by].blank? ? "" :  "GROUP BY #{sql[:group_by]} ")
    
      if options[:pagination]
#        sql[:limit] ||= 10
#        options[:page] ||= 1
        count = ActiveRecord::Base.count_by_sql "SELECT COUNT(*) #{main_sql}"
        @pages = ActionController::Pagination::Paginator.new controller, count, sql[:limit], options[:pagination][:page]
        sql[:offset] = @pages.current.offset
      end
 
      limit = sql[:limit].blank? ? "" : "LIMIT #{sql[:offset]},#{sql[:limit]}"           
 
      ActiveRecord::Base.connection.execute("SELECT #{columns_attr.keys.join(", ")} #{main_sql} #{limit}").each do |row|
        hash = HashArray.new
        row.each_with_index {|col,i| hash[self.class.columns_attr.keys[i]] = col  }
        @results << hash
      end
    
      # Perform formatting on each column
      @results.each_with_index do |row,i|
        row.each do |key, value|
          next unless respond_to? "format_#{key.to_s.underscore_dots}"
          @results[i][key] = send("format_#{key.to_s.underscore_dots}", row)
        end
      end
    end
    
    def render_view
      assigns = { 
        :columns => columns_attr,
        :results => results, 
        :name => report_name, 
        :pages => pages
      }
      view = ActionView::Base.new(template_root, assigns, controller)
      view.class.class_eval "include Reporting::ReportHelper"
      view.render_file template_name
    end      
  end
  
  module ReportHelper
    def report_pagination_links
      if @pages
        html, name = '', @name.underscore
        html << report_link_to("<", name => @pages.current.previous) if @pages.current.previous
        html << " "
        html << pagination_links(@pages, :name => name, :params => report_url_to)
        html << " "
        html << report_link_to(">", name => @pages.current.next) if @pages.current.next
        html
      end
    end
    
    def report_descending_link(key, name = "v", html_options = {})
      report_link_to name, { report_order => key, report_dir => 0 }, html_options
    end
    
    def report_ascending_link(key, name = "^", html_options = {})
      report_link_to name, { report_order => key, report_dir => 1 }, html_options
    end
    
    def report_order
      "#{@name.underscore}_order"
    end
    
    def report_dir
      "#{@name.underscore}_dir"
    end
  
    def report_link_to(name, options = {}, html_options = {})
      link_to name, report_url_to.update(options), html_options
    end
    
    def report_url_to
      {
        report_order => controller.params[report_order],
        report_dir => controller.params[report_dir],
        @name.underscore => controller.params[@name.underscore],
        :anchor => @name.underscore
      }
    end
  end
end
 
class String
  def underscore_dots
    gsub('.','_')
  end
end
 
class HashArray
  def initialize(*args)
    @hash = Hash[*args.flatten]
    if args.empty?
      @data = []
    else
      @data = *args
    end
  end
  
  def [](key)
    if key.is_a?(Fixnum) and hash.has_key?(key)
      @data[key].last
    else
      @hash[key]
    end
  end
  
  def []=(key, value)
    if @hash.has_key? key
      @data.each_with_index do |e,i|
        if e.first == key
          @data[i][1] = value
        end
      end
    else
      @data << [ key, value ]
    end
    @hash[key] = value
  end
  
  def each(&block)
    @data.each {|e| yield(e.first, e.last) }
  end
  
  def each_with_index(&block)
    @data.each_with_index {|e,i| yield(e.last, i) }
  end
  
  def keys
    @data.collect {|e| e.first }
  end
  
  def values
    @data.collect {|e| e.last }
  end
end