lib/saulabs/reportable/report_cache.rb in reportable-1.2.0 vs lib/saulabs/reportable/report_cache.rb in reportable-1.3.0

- old
+ new

@@ -18,11 +18,11 @@ validates_presence_of :grouping validates_presence_of :aggregation validates_presence_of :value validates_presence_of :reporting_period - attr_accessible :model_name, :report_name, :grouping, :aggregation, :value, :reporting_period, :conditions + # attr_accessible :model_name, :report_name, :grouping, :aggregation, :value, :reporting_period, :conditions self.skip_time_zone_conversion_for_attributes = [:reporting_period] # Clears the cache for the specified +klass+ and +report+ # @@ -38,14 +38,11 @@ # end # # Saulabs::Reportable::ReportCache.clear_for(User, :registrations) # def self.clear_for(klass, report) - self.delete_all(:conditions => { - :model_name => klass.name, - :report_name => report.to_s - }) + self.where(model_name: klass.name, report_name: report.to_s).delete_all end # Processes the report using the respective cache. # # @param [Saulabe::Reportable::Report] report @@ -67,22 +64,31 @@ # @return [ResultSet<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? + + # If end_date is in the middle of the current reporting period it means it requests live_data. + # Update the options hash to reflect reality. + current_reporting_period = ReportingPeriod.new(options[:grouping]) + if options[:end_date] && options[:end_date] > current_reporting_period.date_time + options[:live_data] = true + options.delete(:end_date) + end + self.transaction do cached_data = read_cached_data(report, options) new_data = read_new_data(cached_data, options, &block) prepare_result(new_data, cached_data, report, options) end end private 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] } + new_data = new_data.to_a.map { |data| [ReportingPeriod.from_db_string(options[:grouping], data[0]), data[1]] } + cached_data.to_a.map! { |cached| [ReportingPeriod.new(options[:grouping], cached.reporting_period), cached.value] } 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) if cached = cached_data.find { |cached| reporting_period == cached[0] } @@ -126,61 +132,66 @@ conditions.empty? ? '' : conditions.to_s end end def self.read_cached_data(report, options) - options[:conditions] ||= [] - conditions = [ - %w(model_name report_name grouping aggregation conditions).map do |column_name| - "#{self.connection.quote_column_name(column_name)} = ?" - end.join(' AND '), - report.klass.to_s, - report.name.to_s, - options[:grouping].identifier.to_s, - report.aggregation.to_s, - serialize_conditions(options[:conditions]) - ] - first_reporting_period = get_first_reporting_period(options) - last_reporting_period = get_last_reporting_period(options) - if last_reporting_period - conditions.first << ' AND reporting_period BETWEEN ? AND ?' - conditions << first_reporting_period.date_time - conditions << last_reporting_period.date_time + conditions = build_conditions_for_reading_cached_data(report, options) + conditions.limit(options[:limit]).order('reporting_period ASC') + end + + def self.build_conditions_for_reading_cached_data(report, options) + start_date = get_first_reporting_period(options).date_time + + conditions = where('reporting_period >= ?', start_date).where( + model_name: report.klass.to_s, + report_name: report.name.to_s, + grouping: options[:grouping].identifier.to_s, + aggregation: report.aggregation.to_s, + conditions: serialize_conditions(options[:conditions] || []) + ) + + if options[:end_date] + end_date = ReportingPeriod.new(options[:grouping], options[:end_date]).date_time + conditions.where('reporting_period <= ?', end_date) else - conditions.first << ' AND reporting_period >= ?' - conditions << first_reporting_period.date_time + conditions end - self.all( - :conditions => conditions, - :limit => options[:limit], - :order => 'reporting_period ASC' - ) end def self.read_new_data(cached_data, options, &block) - if !options[:live_data] && cached_data.length == options[:limit] - [] + return [] if !options[:live_data] && cached_data.size == options[:limit] + + first_reporting_period_to_read = get_first_reporting_period_to_read(cached_data, options) + last_reporting_period_to_read = options[:end_date] ? ReportingPeriod.new(options[:grouping], options[:end_date]).last_date_time : nil + + yield(first_reporting_period_to_read.date_time, last_reporting_period_to_read) + end + + def self.get_first_reporting_period_to_read(cached_data, options) + return get_first_reporting_period(options) if cached_data.empty? + + last_cached_reporting_period = ReportingPeriod.new(options[:grouping], cached_data.last.reporting_period) + missing_reporting_periods = options[:limit] - cached_data.length + last_reporting_period = if !options[:live_data] && options[:end_date] + ReportingPeriod.new(options[:grouping], options[:end_date]) else - first_reporting_period_to_read = if cached_data.length < options[:limit] - get_first_reporting_period(options) - else - ReportingPeriod.new(options[:grouping], cached_data.last.reporting_period).next - end - last_reporting_period_to_read = options[:end_date] ? ReportingPeriod.new(options[:grouping], options[:end_date]).last_date_time : nil - yield(first_reporting_period_to_read.date_time, last_reporting_period_to_read) + ReportingPeriod.new(options[:grouping]).previous end + + if missing_reporting_periods == 0 || last_cached_reporting_period.offset(missing_reporting_periods) == last_reporting_period + # cache only has missing data contiguously at the end + last_cached_reporting_period.next + else + get_first_reporting_period(options) + end end def self.get_first_reporting_period(options) if options[:end_date] ReportingPeriod.first(options[:grouping], options[:limit] - 1, options[:end_date]) else ReportingPeriod.first(options[:grouping], options[:limit]) end - end - - def self.get_last_reporting_period(options) - return ReportingPeriod.new(options[:grouping], options[:end_date]) if options[:end_date] end end end