Permalink
Browse files

Initial version of the Time Machine

Unluckily, schema switching is not practical in an ActiveRecord::Relation
context, as its methods use both the model's find_by_sql and the adapter's
select_value, select_one, etc.

Wrapping is possible, e.g. by overriding singleton methods on each relation
instance, but it's simply not practical as many (private ones) of them have
to be overridden for each bit of functionality to work.

So, a simpler Arel::Relation#from approach has been used, that is way more
composable and doesn't rely on the inner workings of ActiveRecord::Relation.
  • Loading branch information...
1 parent 8742595 commit 4e6e147a984a03d67f98ebdd6e4f6af5696d8edf @vjt vjt committed May 4, 2012
Showing with 61 additions and 0 deletions.
  1. +1 −0 lib/chrono_model.rb
  2. +60 −0 lib/chrono_model/time_machine.rb
View
@@ -1,5 +1,6 @@
require 'chrono_model/version'
require 'chrono_model/adapter'
+require 'chrono_model/time_machine'
module ChronoModel
class Error < ActiveRecord::ActiveRecordError #:nodoc:
@@ -0,0 +1,60 @@
+require 'active_record'
+
+module ChronoModel
+
+ module TimeMachine
+ extend ActiveSupport::Concern
+
+ # Returns a read-only representation of this record as it was +time+ ago.
+ #
+ def as_of(time)
+ self.class.as_of(time).find(self)
+ end
+
+ # Return the complete read-only history of this instance.
+ #
+ def history
+ self.class.history_of(self)
+ end
+
+ module ClassMethods
+ # Fetches as of +time+ records.
+ #
+ def as_of(time)
+ on_history do
+ where(':ts BETWEEN valid_from AND valid_to', :ts => time)
+ end
+ end
+
+ # Fetches the given +object+ history, sorted by history record time.
+ #
+ def history_of(object)
+ on_history do
+ where(:id => object).order(history_field(:recorded_at))
+ end
+ end
+
+ private
+ # Assumes the block will return an ActiveRecord::Relation and
+ # makes it fetch data from the +history_table+ in read-only mode.
+ #
+ def on_history(&block)
+ block.call.readonly.from(history_table)
+ end
+
+ # Returns this table name in the +Adapter::HISTORY_SCHEMA+
+ #
+ def history_table
+ [Adapter::HISTORY_SCHEMA, table_name].join('.')
+ end
+
+ # Returns the given field in the +history_table+.
+ #
+ def history_field(name)
+ [history_table, name].join('.')
+ end
+ end
+
+ end
+
+end

0 comments on commit 4e6e147

Please sign in to comment.