Module: ChronoModel::TimeMachine::HistoryModel

Extended by:
ActiveSupport::Concern
Defined in:
lib/chrono_model/time_machine/history_model.rb

Defined Under Namespace

Modules: ClassMethods

Instance Method Summary collapse

Instance Method Details

#_find_record(options) ⇒ Object



14
15
16
17
18
19
20
# File 'lib/chrono_model/time_machine/history_model.rb', line 14

def _find_record(options)
  if options && options[:lock]
    self.class.preload(strict_loaded_associations).lock(options[:lock]).find_by!(hid: hid)
  else
    self.class.preload(strict_loaded_associations).find_by!(hid: hid)
  end
end

#as_of_timeTime?

Computes an as_of_time strictly inside this record's validity period for historical queries.

Ensures association queries return versions that existed during this record's validity, not ones that became valid exactly at the boundary time. When objects are updated in the same transaction, they can share the same valid_to, which would otherwise cause boundary mis-selection.

PostgreSQL ranges are half-open [start, end) by default.

Returns:

  • (Time, nil)

    valid_to - ChronoModel::VALIDITY_TSRANGE_PRECISION when valid_to is a Time; otherwise returns valid_to unchanged (which may be nil for open-ended validity).

See Also:



243
244
245
246
247
248
249
# File 'lib/chrono_model/time_machine/history_model.rb', line 243

def as_of_time
  if valid_to.is_a?(Time)
    valid_to - ChronoModel::VALIDITY_TSRANGE_PRECISION
  else
    valid_to
  end
end

#current_versionObject

Returns this history entry's current record



206
207
208
# File 'lib/chrono_model/time_machine/history_model.rb', line 206

def current_version
  self.class.superclass.find(rid)
end

#firstObject

Returns the first history entry



194
195
196
# File 'lib/chrono_model/time_machine/history_model.rb', line 194

def first
  self.class.where(id: rid).chronological.first
end

#historical?Boolean

Returns:

  • (Boolean)


161
162
163
# File 'lib/chrono_model/time_machine/history_model.rb', line 161

def historical?
  true
end

#idObject

The history id is hid, but this cannot set as primary key or temporal associations will break. Solutions are welcome.



139
140
141
# File 'lib/chrono_model/time_machine/history_model.rb', line 139

def id
  hid
end

#lastObject

Returns the last history entry



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

def last
  self.class.where(id: rid).chronological.last
end

#predObject

Returns the previous history entry, or nil if this is the first one.



168
169
170
171
172
173
174
175
176
# File 'lib/chrono_model/time_machine/history_model.rb', line 168

def pred
  return if valid_from.nil?

  if self.class.timeline_associations.empty?
    self.class.where('id = ? AND upper(validity) = ?', rid, valid_from).first
  else
    super(id: rid, before: valid_from, table: self.class.superclass.quoted_table_name)
  end
end

#read_attribute(attr_name, &block) ⇒ Object

.read_attribute uses the memoized primary_key if it detects that the attribute name is id.

Since the primary key may have been changed to hid because of .find overload, the new behavior may break relations where id is still the correct attribute to read

Ref: ifad/chronomodel#181



259
260
261
262
263
# File 'lib/chrono_model/time_machine/history_model.rb', line 259

def read_attribute(attr_name, &block)
  return super unless attr_name.to_s == 'id' && @primary_key.to_s == 'hid'

  _read_attribute('id', &block)
end

#ridObject

Referenced record ID.



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

def rid
  attributes[self.class.primary_key]
end

#saveObject



149
150
151
# File 'lib/chrono_model/time_machine/history_model.rb', line 149

def save(*)
  with_hid_pkey { super }
end

#save!Object



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

def save!(*)
  with_hid_pkey { super }
end

#succObject Also known as: next

Returns the next history entry, or nil if this is the last one.



181
182
183
184
185
186
187
188
189
# File 'lib/chrono_model/time_machine/history_model.rb', line 181

def succ
  return if valid_to.nil?

  if self.class.timeline_associations.empty?
    self.class.where('id = ? AND lower(validity) = ?', rid, valid_to).first
  else
    super(id: rid, after: valid_to, table: self.class.superclass.quoted_table_name)
  end
end

#update_columnsObject



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

def update_columns(*)
  with_hid_pkey { super }
end

#valid_fromObject

Return nil instead of -Infinity/Infinity to preserve current Chronomodel behaviour and avoid failures with Rails 7.0 and unbounded time ranges

Check if begin and end are Time because validity is a tsrange column, so it is either Time, nil, and in some cases Infinity.

Ref: rails/rails#45099 TODO: consider removing when Rails 7.0 support will be dropped



219
220
221
# File 'lib/chrono_model/time_machine/history_model.rb', line 219

def valid_from
  validity.begin if validity.begin.is_a?(Time)
end

#valid_toObject



223
224
225
# File 'lib/chrono_model/time_machine/history_model.rb', line 223

def valid_to
  validity.end if validity.end.is_a?(Time)
end