Module: ChronoModel::TimeMachine::HistoryMethods

Includes:
TimeQuery
Defined in:
lib/chrono_model/time_machine.rb

Overview

Methods that make up the history interface of the companion History model, automatically built for each Model that includes TimeMachine

Defined Under Namespace

Modules: HistorySelect

Instance Method Summary collapse

Instance Method Details

#as_of(time, scope = nil) ⇒ Object

Fetches as of time records.



458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
# File 'lib/chrono_model/time_machine.rb', line 458

def as_of(time, scope = nil)
  as_of = non_history_superclass.unscoped.from(virtual_table_at(time))

  # Add default scopes back if we're passed nil or a
  # specific scope, because we're .unscopeing above.
  #
  scopes = !scope.nil? ? [scope] : (
    superclass.default_scopes.map do |s|
      s.respond_to?(:call) ? s.call : s
    end)

  scopes.each do |s|
    s.order_values.each {|clause| as_of = as_of.order(clause)}
    s.where_values.each {|clause| as_of = as_of.where(clause)}
  end

  as_of.instance_variable_set(:@temporal, time)

  return as_of
end

#at(time) ⇒ Object

Fetches history record at the given time



488
489
490
491
492
493
494
# File 'lib/chrono_model/time_machine.rb', line 488

def at(time)
  time = Conversions.time_to_utc_string(time.utc) if time.kind_of?(Time) && !time.utc?

  unscoped.
    select("#{quoted_table_name}.*, #{connection.quote(time)}::timestamp AS as_of_time").
    time_query(:at, time)
end

#force_history_fieldsObject

HACK FIXME. When querying history, ChronoModel does not add his timestamps and sorting if there is an aggregate function in the select list - as it is likely what you’ll want. However, if you have a query that performs an aggregate in a subquery, the code below will do the wrong thing - and you’ll have to forcibly add back the history fields yourself.

The obvious solution is to use a VIEW on the history containing the added history fields, and remove all this crap from here… but it is not easily feasible. So we’re going with a workaround for now.

- vjt  Wed Apr  2 19:56:35 CEST 2014


525
526
527
# File 'lib/chrono_model/time_machine.rb', line 525

def force_history_fields
  select(HistorySelect::SELECT_VALUES).order(HistorySelect::ORDER_VALUES[quoted_table_name])
end

#history?Boolean

To identify this class as the History subclass

Returns:

  • (Boolean)


437
438
439
# File 'lib/chrono_model/time_machine.rb', line 437

def history?
  true
end

#non_history_superclass(klass = self) ⇒ Object

Getting the correct quoted_table_name can be tricky when STI is involved. If Orange < Fruit, then Orange::History < Fruit::History (see define_inherited_history_model_for). This means that the superclass method returns Fruit::History, which will give us the wrong table name. What we actually want is the superclass of Fruit::History, which is Fruit. So, we use non_history_superclass instead. -npj



448
449
450
451
452
453
454
# File 'lib/chrono_model/time_machine.rb', line 448

def non_history_superclass(klass = self)
  if klass.superclass.respond_to?(:history?) && klass.superclass.history?
    non_history_superclass(klass.superclass)
  else
    klass.superclass
  end
end

#of(object) ⇒ Object

Fetches the given object history, sorted by history record time by default. Always includes an “as_of_time” column that is either the upper bound of the validity range or now() if history validity is maximum.



507
508
509
# File 'lib/chrono_model/time_machine.rb', line 507

def of(object)
  where(:id => object).extend(HistorySelect)
end

#pastObject



432
433
434
# File 'lib/chrono_model/time_machine.rb', line 432

def past
  time_query(:before, :now).where('NOT upper_inf(validity)')
end

#quoted_history_fieldsObject



631
632
633
634
635
636
637
638
639
640
# File 'lib/chrono_model/time_machine.rb', line 631

def quoted_history_fields
  @quoted_history_fields ||= begin
    validity =
      [connection.quote_table_name(table_name),
       connection.quote_column_name('validity')
      ].join('.')

    [:lower, :upper].map! {|func| "#{func}(#{validity})"}
  end
end

#sortedObject

Returns the history sorted by recorded_at



498
499
500
# File 'lib/chrono_model/time_machine.rb', line 498

def sorted
  all.order(%[ #{quoted_table_name}."recorded_at", #{quoted_table_name}."hid" ])
end

#time_query(match, time, options = {}) ⇒ Object

In the History context, pre-fill the :on options with the validity interval.



427
428
429
430
# File 'lib/chrono_model/time_machine.rb', line 427

def time_query(match, time, options = {})
  options[:on] ||= :validity
  super
end

#virtual_table_at(time, name = nil) ⇒ Object



479
480
481
482
483
484
# File 'lib/chrono_model/time_machine.rb', line 479

def virtual_table_at(time, name = nil)
  name = name ? connection.quote_table_name(name) :
    non_history_superclass.quoted_table_name

  "(#{at(time).to_sql}) #{name}"
end