use yardoc for documentation - reportable - Unnamed repository; edit this file 'description' to name the repository.
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
       ---
 (DIR) commit b94d337cd6b1aa0fc6aa285542e7c9f2ad3553e7
 (DIR) parent 7a999addb7eef8518d36310c56b4ca80450f3a4d
 (HTM) Author: Marco Otte-Witte <marco.otte-witte@simplabs.com>
       Date:   Wed, 24 Feb 2010 21:38:14 +0100
       
       use yardoc for documentation
       
       Diffstat:
         M .gitignore                          |       1 +
         A README.md                           |       5 +++++
         D README.rdoc                         |       4 ----
         M Rakefile                            |      14 +++++++-------
         M generators/reports_as_sparkline_mi… |       2 +-
         M lib/saulabs/reportable.rb           |      43 +++++++++++++++++++------------
         M lib/saulabs/reportable/cumulated_r… |      19 ++++++++-----------
         M lib/saulabs/reportable/grouping.rb  |      33 +++++++++++++++++++++++++++----
         M lib/saulabs/reportable/report.rb    |      90 +++++++++++++++++++++++--------
         M lib/saulabs/reportable/report_cach… |      56 ++++++++++++++++++++++---------
         M lib/saulabs/reportable/reporting_p… |      94 ++++++++++++++++++++++++++++---
         M lib/saulabs/reportable/sparkline_t… |      39 ++++++++++++++++++++-----------
       
       12 files changed, 296 insertions(+), 104 deletions(-)
       ---
 (DIR) diff --git a/.gitignore b/.gitignore
       @@ -2,3 +2,4 @@
        spec/log/spec.log
        spec/db/reportable.sqlite3.db
        doc
       +.yardoc
 (DIR) diff --git a/README.md b/README.md
       @@ -0,0 +1,4 @@
       +Reportable
       +----------
       +
       +Former ReportsAsSparkline; expect new features, cleaner code etc. in the next weeks
       +\ No newline at end of file
 (DIR) diff --git a/README.rdoc b/README.rdoc
       @@ -1,3 +0,0 @@
       -= Reportable
       -
       -Former ReportsAsSparkline; expect new features, cleaner code etc. in the next weeks
       -\ No newline at end of file
 (DIR) diff --git a/Rakefile b/Rakefile
       @@ -22,13 +22,13 @@ Spec::Rake::SpecTask.new(:spec) do |t|
          t.spec_files = FileList['spec/**/*_spec.rb']
        end
        
       -desc 'Generate documentation for the ReportsAsSparkline plugin.'
       -Rake::RDocTask.new(:rdoc) do |rdoc|
       -  rdoc.rdoc_dir = 'doc'
       -  rdoc.title    = 'ReportsAsSparkline'
       -  rdoc.options << '--line-numbers' << '--inline-source'
       -  rdoc.rdoc_files.include('README.rdoc')
       -  rdoc.rdoc_files.include('lib/**/*.rb')
       +begin
       +  require 'yard'
       +  YARD::Rake::YardocTask.new(:doc) do |t|
       +    t.files   = ['lib/**/*.rb', '-', 'README.md']
       +    t.options = ['--no-private', '--title', 'Reportable Documentation']
       +  end
       +rescue LoadError
        end
        
        begin
 (DIR) diff --git a/generators/reports_as_sparkline_migration/reports_as_sparkline_migration_generator.rb b/generators/reports_as_sparkline_migration/reports_as_sparkline_migration_generator.rb
       @@ -1,4 +1,4 @@
       -class ReportableMigrationGenerator < Rails::Generator::NamedBase #:nodoc:
       +class ReportableMigrationGenerator < Rails::Generator::NamedBase
        
          def manifest
            record do |m|
 (DIR) diff --git a/lib/saulabs/reportable.rb b/lib/saulabs/reportable.rb
       @@ -1,31 +1,40 @@
       -module Saulabs #:nodoc:
       +module Saulabs
        
          module Reportable
        
       -    def self.included(base) #:nodoc:
       +    # Includes the {Saulabs::Reportable.reportable} method into +base+.
       +    #
       +    def self.included(base)
              base.extend ClassMethods
            end
        
            module ClassMethods
        
       -      # Generates a report on a model. That report can then be executed via the new method <tt><name>_report</tt> (see documentation of Saulabs::Reportable::Report#run).
       +      # Generates a report on a model. That report can then be executed via the new method +<name>_report+ (see documentation of {Saulabs::Reportable::Report#run}).
              # 
       -      # ==== Parameters
       +      # @param [String] name
       +      #   the name of the report, also defines the name of the generated report method (+<name>_report+)
       +      # @param [Hash] options
       +      #   the options to generate the reports with
              #
       -      # * <tt>name</tt> - The name of the report, defines the name of the generated report method (<tt><name>_report</tt>)
       +      # @option options [Symbol] :date_column (created_at)
       +      #   the name of the date column over that the records are aggregated
       +      # @option options [String, Symbol] :value_column (:id)
       +      #   the name of the column that holds the values to aggregate when using a calculation aggregation like +:sum+
       +      # @option options [Symbol] :aggregation (:count)
       +      #   the aggregation to use (one of +:count+, +:sum+, +:minimum+, +:maximum+ or +:average+); when using anything other than +:count+, +:value_column+ must also be specified
       +      # @option options [Symbol] :grouping (:day)
       +      #   the period records are grouped in (+:hour+, +:day+, +:week+, +:month+); <b>Beware that <tt>reportable</tt> treats weeks as starting on monday!</b>
       +      # @option options [Fixnum] :limit (100)
       +      #   the number of reporting periods to get (see +:grouping+)
       +      # @option options [Hash] :conditions ({})
       +      #   conditions like in +ActiveRecord::Base#find+; only records that match these conditions are reported;
       +      # @option options [Boolean] :live_data (false)
       +      #   specifies whether data for the current reporting period is to be read; <b>if +:live_data+ is +true+, you will experience a performance hit since the request cannot be satisfied from the cache alone</b>
       +      # @option options [DateTime, Boolean] :end_date (false)
       +      #   when specified, the report will only include data for the +:limit+ reporting periods until this date.
              #
       -      # ==== Options
       -      #
       -      # * <tt>:date_column</tt> - The name of the date column over that the records are aggregated (defaults to <tt>created_at</tt>)
       -      # * <tt>:value_column</tt> - The name of the column that holds the values to sum up when using aggregation <tt>:sum</tt>
       -      # * <tt>:aggregation</tt> - The aggregation to use (one of <tt>:count</tt>, <tt>:sum</tt>, <tt>:minimum</tt>, <tt>:maximum</tt> or <tt>:average</tt>); when using anything other than <tt>:count</tt>, <tt>:value_column</tt> must also be specified (<b>If you really want to e.g. sum up the values in the <tt>id</tt> column, you have to explicitely say so.</b>); (defaults to <tt>:count</tt>)
       -      # * <tt>:grouping</tt> - The period records are grouped on (<tt>:hour</tt>, <tt>:day</tt>, <tt>:week</tt>, <tt>:month</tt>); <b>Beware that <tt>reportable</tt> treats weeks as starting on monday!</b>
       -      # * <tt>:limit</tt> - The number of reporting periods to get (see <tt>:grouping</tt>), (defaults to 100)
       -      # * <tt>:conditions</tt> - Conditions like in <tt>ActiveRecord::Base#find</tt>; only records that match the conditions are reported; <b>Beware that when conditions are specified, caching is disabled!</b>
       -      # * <tt>:live_data</tt> - Specifies whether data for the current reporting period is to be read; <b>if <tt>:live_data</tt> is <tt>true</tt>, you will experience a performance hit since the request cannot be satisfied from the cache only (defaults to <tt>false</tt>)</b>
       -      # * <tt>:end_date</tt> - When specified, the report will only include data for the <tt>:limit</tt> reporting periods until this date.
       -      #
       -      # ==== Examples
       +      # @example Declaring reports on a model
              #
              #  class User < ActiveRecord::Base
              #    reportable :registrations, :aggregation => :count
 (DIR) diff --git a/lib/saulabs/reportable/cumulated_report.rb b/lib/saulabs/reportable/cumulated_report.rb
       @@ -1,26 +1,23 @@
       -module Saulabs #:nodoc:
       +module Saulabs
        
       -  module Reportable #:nodoc:
       +  module Reportable
        
       -    # A special report class that cumulates all data (see Saulabs::Reportable::Report)
       +    # A special report class that cumulates all data (see {Saulabs::Reportable::Report})
            #
       -    # ==== Examples
       -    #
       -    # When Saulabs::Reportable::Report returns
       +    # @example Cumulated reports as opposed to regular reports
            #
            #  [[<DateTime today>, 1], [<DateTime yesterday>, 2], etc.]
       -    #
       -    # Saulabs::Reportable::CumulatedReport returns
       -    #
            #  [[<DateTime today>, 3], [<DateTime yesterday>, 2], etc.]
       +    #
            class CumulatedReport < Report
        
       -      # Runs the report (see Saulabs::Reportable::Report#run)
       +      # Runs the report (see {Saulabs::Reportable::Report#run})
       +      #
              def run(options = {})
                cumulate(super, options_for_run(options))
              end
        
       -      protected
       +      private
        
                def cumulate(data, options)
                  first_reporting_period = ReportingPeriod.first(options[:grouping], options[:limit], options[:end_date])
 (DIR) diff --git a/lib/saulabs/reportable/grouping.rb b/lib/saulabs/reportable/grouping.rb
       @@ -1,18 +1,38 @@
       -module Saulabs #:nodoc:
       +module Saulabs
        
       -  module Reportable #:nodoc:
       +  module Reportable
        
       -    class Grouping #:nodoc:
       +    # The grouping specifies which records are grouped into one {Saulabs::Reportable::ReportingPeriod}.
       +    #
       +    class Grouping
        
       +      # Initializes a new grouping.
       +      #
       +      # @param [Symbol] identifier
       +      #   the identifier of the grouping (one of +:hour+, +:day+, +:week+ or +:month+)
       +      #
              def initialize(identifier)
                raise ArgumentError.new("Invalid grouping #{identifier}") unless [:hour, :day, :week, :month].include?(identifier)
                @identifier = identifier
              end
        
       +      # Gets the identifier of the grouping.
       +      #
       +      # @returns [Symbol]
       +      #   the identifier of the grouping.
       +      #
              def identifier
                @identifier
              end
        
       +      # Gets an array of date parts from a DB string.
       +      #
       +      # @param [String] db_string
       +      #   the DB string to get the date parts from
       +      #
       +      # @returns [Array<Fixnum>]
       +      #   array of numbers that represent the values of the date
       +      #
              def date_parts_from_db_string(db_string)
                case ActiveRecord::Base.connection.adapter_name
                  when /mysql/i
       @@ -24,7 +44,12 @@ module Saulabs #:nodoc:
                end
              end
        
       -      def to_sql(date_column) #:nodoc:
       +      # Converts the grouping into a DB specific string that can be used to group records.
       +      #
       +      # @param [String] date_column
       +      #   the name of the DB column that holds the date
       +      #
       +      def to_sql(date_column)
                case ActiveRecord::Base.connection.adapter_name
                  when /mysql/i
                    mysql_format(date_column)
 (DIR) diff --git a/lib/saulabs/reportable/report.rb b/lib/saulabs/reportable/report.rb
       @@ -1,26 +1,61 @@
       -module Saulabs #:nodoc:
       +module Saulabs
        
       -  module Reportable #:nodoc:
       +  module Reportable
        
       -    # The Report class that does all the data retrieval and calculations
       +    # The Report class that does all the data retrieval and calculations.
       +    #
            class Report
        
       -      attr_reader :klass, :name, :date_column, :value_column, :aggregation, :options
       +      # the model the report works on (This is the class you invoke {Saulabs::Reportable::ClassMethods#reportable} on)
       +      #
       +      attr_reader :klass
       +
       +      # the name of the report (as in {Saulabs::Reportable::ClassMethods#reportable})
       +      #
       +      attr_reader :name
       +
       +      # the name of the date column over that the records are aggregated
       +      #
       +      attr_reader :date_column
       +
       +      # the name of the column that holds the values to aggregate when using a calculation aggregation like +:sum+
       +      #
       +      attr_reader :value_column
        
       -      # ==== Parameters
       -      # * <tt>klass</tt> - The model the report works on (This is the class you invoke Saulabs::Reportable::ClassMethods#reportable on)
       -      # * <tt>name</tt> - The name of the report (as in Saulabs::Reportable::ClassMethods#reportable)
       +      # the aggregation to use (one of +:count+, +:sum+, +:minimum+, +:maximum+ or +:average+); when using anything other than +:count+, +:value_column+ must also be specified
              #
       -      # ==== Options
       +      attr_reader :aggregation
       +
       +      # options for the report
       +      #
       +      attr_reader :options
       +
       +      # Initializes a new {Saulabs::Reportable::Report}
       +      #
       +      # @param [Class] klass
       +      #   the model the report works on (This is the class you invoke {Saulabs::Reportable::ClassMethods#reportable} on)
       +      # @param [String] name
       +      #   the name of the report (as in {Saulabs::Reportable::ClassMethods#reportable})
       +      # @param [Hash] options
       +      #   options for the report creation
       +      #
       +      # @option options [Symbol] :date_column (created_at)
       +      #   the name of the date column over that the records are aggregated
       +      # @option options [String, Symbol] :value_column (:id)
       +      #   the name of the column that holds the values to aggregate when using a calculation aggregation like +:sum+
       +      # @option options [Symbol] :aggregation (:count)
       +      #   the aggregation to use (one of +:count+, +:sum+, +:minimum+, +:maximum+ or +:average+); when using anything other than +:count+, +:value_column+ must also be specified
       +      # @option options [Symbol] :grouping (:day)
       +      #   the period records are grouped in (+:hour+, +:day+, +:week+, +:month+); <b>Beware that <tt>reportable</tt> treats weeks as starting on monday!</b>
       +      # @option options [Fixnum] :limit (100)
       +      #   the number of reporting periods to get (see +:grouping+)
       +      # @option options [Hash] :conditions ({})
       +      #   conditions like in +ActiveRecord::Base#find+; only records that match these conditions are reported;
       +      # @option options [Boolean] :live_data (false)
       +      #   specifies whether data for the current reporting period is to be read; <b>if +:live_data+ is +true+, you will experience a performance hit since the request cannot be satisfied from the cache alone</b>
       +      # @option options [DateTime, Boolean] :end_date (false)
       +      #   when specified, the report will only include data for the +:limit+ reporting periods until this date.
              #
       -      # * <tt>:date_column</tt> - The name of the date column over that the records are aggregated (defaults to <tt>created_at</tt>)
       -      # * <tt>:value_column</tt> - The name of the column that holds the values to sum up when using aggregation <tt>:sum</tt>
       -      # * <tt>:aggregation</tt> - The aggregation to use (one of <tt>:count</tt>, <tt>:sum</tt>, <tt>:minimum</tt>, <tt>:maximum</tt> or <tt>:average</tt>); when using anything other than <tt>:count</tt>, <tt>:value_column</tt> must also be specified (<b>If you really want to e.g. sum up the values in the <tt>id</tt> column, you have to explicitely say so.</b>); (defaults to <tt>:count</tt>)
       -      # * <tt>:grouping</tt> - The period records are grouped on (<tt>:hour</tt>, <tt>:day</tt>, <tt>:week</tt>, <tt>:month</tt>); <b>Beware that <tt>reportable</tt> treats weeks as starting on monday!</b>
       -      # * <tt>:limit</tt> - The number of reporting periods to get (see <tt>:grouping</tt>), (defaults to 100)
       -      # * <tt>:conditions</tt> - Conditions like in <tt>ActiveRecord::Base#find</tt>; only records that match the conditions are reported; <b>Beware that when conditions are specified, caching is disabled!</b>
       -      # * <tt>:live_data</tt> - Specifies whether data for the current reporting period is to be read; <b>if <tt>:live_data</tt> is <tt>true</tt>, you will experience a performance hit since the request cannot be satisfied from the cache only (defaults to <tt>false</tt>)</b>
       -      # * <tt>:end_date</tt> - When specified, the report will only include data for the <tt>:limit</tt> reporting periods until this date.
              def initialize(klass, name, options = {})
                ensure_valid_options(options)
                @klass        = klass
       @@ -41,12 +76,23 @@ module Saulabs #:nodoc:
        
              # Runs the report and returns an array of array of DateTimes and Floats
              #
       -      # ==== Options
       -      # * <tt>:grouping</tt> - The period records are grouped on (<tt>:hour</tt>, <tt>:day</tt>, <tt>:week</tt>, <tt>:month</tt>); <b>Beware that <tt>reportable</tt> treats weeks as starting on monday!</b>
       -      # * <tt>:limit</tt> - The number of reporting periods to get (see <tt>:grouping</tt>), (defaults to 100)
       -      # * <tt>:conditions</tt> - Conditions like in <tt>ActiveRecord::Base#find</tt>; only records that match the conditions are reported
       -      # * <tt>:live_data</tt> - Specifies whether data for the current reporting period is to be read; <b>if <tt>:live_data</tt> is <tt>true</tt>, you will experience a performance hit since the request cannot be satisfied from the cache only (defaults to <tt>false</tt>)</b>
       -      # * <tt>:end_date</tt> - When specified, the report will only include data for the <tt>:limit</tt> reporting periods until this date.
       +      # @param [Hash] options
       +      #   options to run the report with
       +      #
       +      # @option options [Symbol] :grouping (:day)
       +      #   the period records are grouped in (+:hour+, +:day+, +:week+, +:month+); <b>Beware that <tt>reportable</tt> treats weeks as starting on monday!</b>
       +      # @option options [Fixnum] :limit (100)
       +      #   the number of reporting periods to get (see +:grouping+)
       +      # @option options [Hash] :conditions ({})
       +      #   conditions like in +ActiveRecord::Base#find+; only records that match these conditions are reported;
       +      # @option options [Boolean] :live_data (false)
       +      #   specifies whether data for the current reporting period is to be read; <b>if +:live_data+ is +true+, you will experience a performance hit since the request cannot be satisfied from the cache alone</b>
       +      # @option options [DateTime, Boolean] :end_date (false)
       +      #   when specified, the report will only include data for the +:limit+ reporting periods until this date.
       +      #
       +      # @returns [Array<Array<DateTime, Float>>]
       +      #   the result of the report as pairs of {DateTime}s and {Float}s
       +      #
              def run(options = {})
                options = options_for_run(options)
                ReportCache.process(self, options) do |begin_at, end_at|
 (DIR) diff --git a/lib/saulabs/reportable/report_cache.rb b/lib/saulabs/reportable/report_cache.rb
       @@ -1,9 +1,10 @@
       -module Saulabs #:nodoc:
       +module Saulabs
        
       -  module Reportable #:nodoc:
       +  module Reportable
        
       -    # The ReportCache class is a regular +ActiveRecord+ model and represents cached results for single reporting periods (table name is +reportable_cache+)
       -    # ReportCache instances are identified by the combination of +model_name+, +report_name+, +grouping+, +aggregation+ and +reporting_period+
       +    # The +ReportCache+ class is a regular {ActiveRecord} model and represents cached results for single {Saulabs::Reportable::ReportingPeriod}s.
       +    # +ReportCache+ instances are identified by the combination of +model_name+, +report_name+, +grouping+, +aggregation+ and +reporting_period+.
       +    #
            class ReportCache < ActiveRecord::Base
        
              set_table_name :reportable_cache
       @@ -12,17 +13,19 @@ module Saulabs #:nodoc:
        
              # Clears the cache for the specified +klass+ and +report+
              #
       -      # === Parameters
       -      # * <tt>klass</tt> - The model the report to clear the cache for works on
       -      # * <tt>report</tt> - The name of the report to clear the cache for
       +      # @param [Class] klass
       +      #   the model the report to clear the cache for works on
       +      # @param [Symbol] report
       +      #   the name of the report to clear the cache for
       +      #
       +      # @example Clearing the cache for a report
       +      #
       +      #   class User < ActiveRecord::Base
       +      #     reportable :registrations
       +      #   end
       +      #
       +      #   Saulabs::Reportable::ReportCache.clear_for(User, :registrations)
              #
       -      # === Example
       -      # To clear the cache for a report defined as
       -      #  class User < ActiveRecord::Base
       -      #    reportable :registrations
       -      #  end
       -      # just do
       -      #  Saulabs::Reportable::ReportCache.clear_for(User, :registrations)
              def self.clear_for(klass, report)
                self.delete_all(:conditions => {
                  :model_name  => klass.name,
       @@ -30,7 +33,28 @@ module Saulabs #:nodoc:
                })
              end
        
       -      def self.process(report, options, &block) #:nodoc:
       +      # Processes the report using the respective cache.
       +      #
       +      # @param [Saulabe::Reportable::Report] report
       +      #   the report to process
       +      # @param [Hash] options
       +      #   options for the report
       +      #
       +      # @option options [Symbol] :grouping (:day)
       +      #   the period records are grouped in (+:hour+, +:day+, +:week+, +:month+); <b>Beware that <tt>reportable</tt> treats weeks as starting on monday!</b>
       +      # @option options [Fixnum] :limit (100)
       +      #   the number of reporting periods to get (see +:grouping+)
       +      # @option options [Hash] :conditions ({})
       +      #   conditions like in +ActiveRecord::Base#find+; only records that match these conditions are reported;
       +      # @option options [Boolean] :live_data (false)
       +      #   specifies whether data for the current reporting period is to be read; <b>if +:live_data+ is +true+, you will experience a performance hit since the request cannot be satisfied from the cache alone</b>
       +      # @option options [DateTime, Boolean] :end_date (false)
       +      #   when specified, the report will only include data for the +:limit+ reporting periods until this date.
       +      #
       +      # @returns [Array<Array<DateTime, Float>>]
       +      #   the result of the report as pairs of {DateTime}s and {Float}s
       +      #
       +      def self.process(report, options, &block)
                raise ArgumentError.new('A block must be given') unless block_given?
                self.transaction do
                  cached_data = read_cached_data(report, options)
       @@ -44,7 +68,7 @@ module Saulabs #:nodoc:
                def self.prepare_result(new_data, cached_data, report, options)
                  new_data = new_data.map { |data| [ReportingPeriod.from_db_string(options[:grouping], data[0]), data[1]] }
                  cached_data.map! { |cached| [ReportingPeriod.new(options[:grouping], cached.reporting_period), cached.value] }
       -          current_reporting_period = ReportingPeriod.current(options[:grouping])
       +          current_reporting_period = ReportingPeriod.new(options[:grouping])
                  reporting_period = get_first_reporting_period(options)
                  result = []
                  while reporting_period < (options[:end_date] ? ReportingPeriod.new(options[:grouping], options[:end_date]).next : current_reporting_period)
 (DIR) diff --git a/lib/saulabs/reportable/reporting_period.rb b/lib/saulabs/reportable/reporting_period.rb
       @@ -1,28 +1,74 @@
       -module Saulabs #:nodoc:
       +module Saulabs
        
       -  module Reportable #:nodoc:
       +  module Reportable
        
       -    class ReportingPeriod #:nodoc:
       +    # A reporting period is a specific hour or a specific day etc. depending on the used {Saulabs::Reportable::Grouping}.
       +    #
       +    class ReportingPeriod
        
       -      attr_reader :date_time, :grouping
       +      # The actual {DateTime} the reporting period represents
       +      #
       +      attr_reader :date_time
        
       +      # The {Saulabs::Reportable::Grouping} of the reporting period
       +      #
       +      attr_reader :grouping
       +
       +      # Initializes a new reporting period.
       +      #
       +      # @param [Saulabs::Reportable::Grouping] grouping
       +      #   the grouping the generate the reporting period for
       +      # @param [DateTime] date_time
       +      #   the {DateTime} to generate the reporting period for
       +      #
              def initialize(grouping, date_time = nil)
                @grouping  = grouping
                @date_time = parse_date_time(date_time || DateTime.now)
              end
        
       +      # Gets a reporting period relative to the current one.
       +      #
       +      # @param [Fixnum] offset
       +      #   the offset to get the reporting period for
       +      #
       +      # @returns [Saulabs::Reportable::ReportingPeriod]
       +      #   the reporting period relative by offset to the current one
       +      #
       +      # @example Getting the reporting period one week later
       +      #
       +      #   reporting_period = Saulabs::Reportable::ReportingPeriod.new(:week, DateTime.now)
       +      #   next_week = reporting_period.offset(1)
       +      #
              def offset(offset)
                self.class.new(@grouping, @date_time + offset.send(@grouping.identifier))
              end
        
       +      # Gets the first reporting period for a grouping and a limit (optionally relative to and end date).
       +      #
       +      # @param [Saulabs::ReportingPeriod::Grouping] grouping
       +      #   the grouping to get the first reporting period for
       +      # @param [Fixnum] limit
       +      #   the limit to get the first reporting period for
       +      # @param [DateTime] end_date
       +      #   the end date to get the first reporting period for (the first reporting period is then +end_date+ - +limit+ * +grouping+)
       +      #
       +      # @returns [Saulabs::Reportable::ReportingPeriod]
       +      #   the first reporting period for the grouping, limit and optionally end date
       +      #
              def self.first(grouping, limit, end_date = nil)
                self.new(grouping, end_date).offset(-limit)
              end
        
       -      def self.current(grouping)
       -        self.new(grouping, Time.now)
       -      end
       -
       +      # Gets a reporting period from a DB date string.
       +      #
       +      # @param [Saulabs::Reportable::Grouping] grouping
       +      #   the grouping to get the reporting period for
       +      # @param [String] db_string
       +      #   the DB string to parse and get the reporting period for
       +      #
       +      # @returns [Saulabs::Reportable::ReportingPeriod]
       +      #   the reporting period for the {Saulabs::Reportable::Grouping} as parsed from the db string
       +      #
              def self.from_db_string(grouping, db_string)
                parts = grouping.date_parts_from_db_string(db_string)
                result = case grouping.identifier
       @@ -38,14 +84,32 @@ module Saulabs #:nodoc:
                result
              end
        
       +      # Gets the next reporting period.
       +      #
       +      # @returns [Saulabs::Reportable::ReportingPeriod]
       +      #   the reporting period after the current one
       +      #
              def next
                self.offset(1)
              end
        
       +      # Gets the previous reporting period.
       +      #
       +      # @returns [Saulabs::Reportable::ReportingPeriod]
       +      #   the reporting period before the current one
       +      #
              def previous
                self.offset(-1)
              end
        
       +      # Gets whether the reporting period +other+ is equal to the current one.
       +      #
       +      # @param [Saulabs::Reportable::ReportingPeriod] other
       +      #   the reporting period to check for whether it is equal to the current one
       +      #
       +      # @returns [Boolean]
       +      #   true if +other+ is equal to the current reporting period, false otherwise
       +      #
              def ==(other)
                if other.is_a?(Saulabs::Reportable::ReportingPeriod)
                  @date_time.to_s == other.date_time.to_s && @grouping.identifier.to_s == other.grouping.identifier.to_s
       @@ -56,6 +120,14 @@ module Saulabs #:nodoc:
                end
              end
        
       +      # Gets whether the reporting period +other+ is smaller to the current one.
       +      #
       +      # @param [Saulabs::Reportable::ReportingPeriod] other
       +      #   the reporting period to check for whether it is smaller to the current one
       +      #
       +      # @returns [Boolean]
       +      #   true if +other+ is smaller to the current reporting period, false otherwise
       +      #
              def <(other)
                if other.is_a?(Saulabs::Reportable::ReportingPeriod)
                  return @date_time < other.date_time
       @@ -66,6 +138,12 @@ module Saulabs #:nodoc:
                end
              end
        
       +      # Gets the latest point in time that is included the reporting period. The latest point in time included in a reporting period
       +      # for grouping hour would be that hour and 59 minutes and 59 seconds.
       +      #
       +      # @returns [DateTime]
       +      #   the latest point in time that is included in the reporting period
       +      #
              def last_date_time
                case @grouping.identifier
                  when :hour
 (DIR) diff --git a/lib/saulabs/reportable/sparkline_tag_helper.rb b/lib/saulabs/reportable/sparkline_tag_helper.rb
       @@ -1,27 +1,38 @@
       -module Saulabs #:nodoc:
       +module Saulabs
        
       -  module Reportable #:nodoc:
       +  module Reportable
        
            module SparklineTagHelper
        
              # Renders a sparkline with the given data.
              #
       -      # ==== Parameters
       +      # @param [Array<Array<DateTime, Float>>] data
       +      #   an array of report data as returned by {Saulabs::Reportable::Report#run}
       +      # @param [Hash] options
       +      #   options for the sparkline
              #
       -      # * <tt>data</tt> - The data to render the sparkline for, is retrieved from a report like <tt>User.registration_report</tt>
       +      # @option options [Fixnum] :width (300)
       +      #   the width of the generated image
       +      # @option options [Fixnum] :height (34)
       +      #   the height of the generated image
       +      # @option options [String] :line_color ('0077cc')
       +      #   the line color of the generated image
       +      # @option options [String] :fill_color ('e6f2fa')
       +      #   the fill color of the generated image
       +      # @option options [Array<Symbol>] :labels ([])
       +      #   the axes to render lables for (Array of +:x+, +:y+, +:r+, +:t+; this is x axis, y axis, right, top)
       +      # @option options [String] :alt ('')
       +      #   the alt attribute for the generated image
       +      # @option options [String] :title ('')
       +      #   the title attribute for the generated image
              #
       -      # ==== Options
       +      # @returns [String]
       +      #   an image tag showing a sparkline for the passed +data+
              #
       -      # * <tt>width</tt> - The width of the generated image
       -      # * <tt>height</tt> - The height of the generated image
       -      # * <tt>line_color</tt> - The line color of the sparkline (hex code)
       -      # * <tt>fill_color</tt> - The color to fill the area below the sparkline with (hex code)
       -      # * <tt>labels</tt> - The axes to render lables for (Array of <tt>:x</tt>, <tt>:y+</tt>, <tt>:r</tt>, <tt>:t</tt>; this is x axis, y axis, right, top)
       -      # * <tt>alt</tt> - The HTML img alt tag
       -      # * <tt>title</tt> - The HTML img title tag
       +      # @example Rendering a sparkline tag for report data
       +      #
       +      #   <%= sparkline_tag(User.registrations_report, :width => 200, :height => 100, :color => '000') %>
              #
       -      # ==== Example
       -      # <tt><%= sparkline_tag(User.registrations_report, :width => 200, :height => 100, :color => '000') %></tt>
              def sparkline_tag(data, options = {})
                options.reverse_merge!({ :width => 300, :height => 34, :line_color => '0077cc', :fill_color => 'e6f2fa', :labels => [], :alt => '', :title => '' })
                data = data.collect { |d| d[1] }