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



75
76
77
78
79
80
81
82
83
84
85
# File 'lib/chrono_model/time_machine.rb', line 75

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.



123
124
125
# File 'lib/chrono_model/time_machine.rb', line 123

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.



130
131
132
# File 'lib/chrono_model/time_machine.rb', line 130

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.



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/chrono_model/time_machine.rb', line 226

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

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

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

#current_versionObject

Returns the current history version



205
206
207
208
209
210
211
# File 'lib/chrono_model/time_machine.rb', line 205

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

#destroyObject

Inhibit destroy of historical records

Raises:

  • (ActiveRecord::ReadOnlyRecord)


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

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)


158
159
160
# File 'lib/chrono_model/time_machine.rb', line 158

def historical?
  as_of_time.present?
end

#historyObject

Return the complete read-only history of this instance.



145
146
147
# File 'lib/chrono_model/time_machine.rb', line 145

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.



216
217
218
219
# File 'lib/chrono_model/time_machine.rb', line 216

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.



173
174
175
176
177
178
179
180
181
182
183
# File 'lib/chrono_model/time_machine.rb', line 173

def pred(options = {})
  if self.class.timeline_associations.empty?
    history.reverse_order.second
  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.



188
189
190
191
192
193
194
195
# File 'lib/chrono_model/time_machine.rb', line 188

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.



199
200
201
# File 'lib/chrono_model/time_machine.rb', line 199

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.



152
153
154
# File 'lib/chrono_model/time_machine.rb', line 152

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