Module: ChronoModel::TimeMachine

Extended by:
ActiveSupport::Concern
Includes:
Patches::AsOfTimeHolder
Defined in:
lib/chrono_model/time_machine.rb,
lib/chrono_model/time_machine/timeline.rb,
lib/chrono_model/time_machine/time_query.rb,
lib/chrono_model/time_machine/history_model.rb

Defined Under Namespace

Modules: ClassMethods, HistoryModel, TimeQuery, Timeline

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Patches::AsOfTimeHolder

#as_of_time, #as_of_time!

Class Method Details

.define_history_model_for(model) ⇒ Object



53
54
55
56
57
58
59
60
61
62
63
# File 'lib/chrono_model/time_machine.rb', line 53

def self.define_history_model_for(model)
  history = Class.new(model) do
    include ChronoModel::TimeMachine::HistoryModel
  end

  model.singleton_class.instance_eval do
    define_method(:history) { history }
  end

  model.const_set :History, history
end

Instance Method Details

#as_of(time) ⇒ Object

Returns a read-only representation of this record as it was time ago. Returns nil if no record is found.



99
100
101
# File 'lib/chrono_model/time_machine.rb', line 99

def as_of(time)
  _as_of(time).first
end

#as_of!(time) ⇒ Object

Returns a read-only representation of this record as it was time ago. Raises ActiveRecord::RecordNotFound if no record is found.



106
107
108
# File 'lib/chrono_model/time_machine.rb', line 106

def as_of!(time)
  _as_of(time).first!
end

#changes_against(ref) ⇒ Object

Returns the differences between this record and an arbitrary reference record. The changes representation is an hash keyed by attribute whose values are arrays containing previous and current attributes values - the same format used by ActiveModel::Dirty.



197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/chrono_model/time_machine.rb', line 197

def changes_against(ref)
  self.class.attribute_names_for_history_changes.inject({}) do |changes, attr|
    old, new = ref.public_send(attr), self.public_send(attr)

    changes.tap do |c|
      changed = old.respond_to?(:history_eql?) ?
        !old.history_eql?(new) : old != new

      c[attr] = [old, new] if changed
    end
  end
end

#current_versionObject

Returns the current history version



180
181
182
# File 'lib/chrono_model/time_machine.rb', line 180

def current_version
  self.historical? ? self.class.find(self.id) : self
end

#destroyObject

Inhibit destroy of historical records

Raises:

  • (ActiveRecord::ReadOnlyRecord)


140
141
142
143
# File 'lib/chrono_model/time_machine.rb', line 140

def destroy
  raise ActiveRecord::ReadOnlyRecord, 'Cannot delete historical records' if historical?
  super
end

#historical?Boolean

Returns a boolean indicating whether this record is an history entry.

Returns:

  • (Boolean)


134
135
136
# File 'lib/chrono_model/time_machine.rb', line 134

def historical?
  self.as_of_time.present?
end

#historyObject

Return the complete read-only history of this instance.



121
122
123
# File 'lib/chrono_model/time_machine.rb', line 121

def history
  self.class.history.chronological.of(self)
end

#last_changesObject

Returns the differences between this entry and the previous history one. See: changes_against.



187
188
189
190
# File 'lib/chrono_model/time_machine.rb', line 187

def last_changes
  pred = self.pred
  changes_against(pred) if pred
end

#pred(options = {}) ⇒ Object

Returns the previous record in the history, or nil if this is the only recorded entry.



148
149
150
151
152
153
154
155
156
157
158
# File 'lib/chrono_model/time_machine.rb', line 148

def pred(options = {})
  if self.class.timeline_associations.empty?
    history.order(Arel.sql('upper(validity) DESC')).offset(1).first
  else
    return nil unless (ts = pred_timestamp(options))

    order_clause = Arel.sql %[ LOWER(#{options[:table] || self.class.quoted_table_name}."validity") DESC ]

    self.class.as_of(ts).order(order_clause).find(options[:id] || id)
  end
end

#pred_timestamp(options = {}) ⇒ Object

Returns the previous timestamp in this record’s timeline. Includes temporal associations.



163
164
165
166
167
168
169
170
# File 'lib/chrono_model/time_machine.rb', line 163

def pred_timestamp(options = {})
  if historical?
    options[:before] ||= as_of_time
    timeline(options.merge(limit: 1, reverse: true)).first
  else
    timeline(options.merge(limit: 2, reverse: true)).second
  end
end

#succObject

This is a current record, so its next instance is always nil.



174
175
176
# File 'lib/chrono_model/time_machine.rb', line 174

def succ
  nil
end

#timeline(options = {}) ⇒ Object

Returns an Array of timestamps for which this instance has an history record. Takes temporal associations into account.



128
129
130
# File 'lib/chrono_model/time_machine.rb', line 128

def timeline(options = {})
  self.class.history.timeline(self, options)
end