module Sequel::Plugins::Bitemporal::InstanceMethods

Attributes

pending_version[R]

Public Instance Methods

after_create() click to toggle source
Calls superclass method
# File lib/sequel/plugins/bitemporal.rb, line 362
def after_create
  super
  if @create_version
    @create_version = nil
    return false unless save_pending_version
  end
end
after_save() click to toggle source
Calls superclass method
# File lib/sequel/plugins/bitemporal.rb, line 378
def after_save
  super
  _refresh_set_values @values
end
attributes() click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 316
def attributes
  if pending_version
    pending_version.values
  elsif current_version
    current_version.values
  else
    initial_version.values
  end
end
attributes=(attributes) click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 326
def attributes=(attributes)
  @pending_version ||= begin
    current_attributes =
      if composite_primary_key?
        version_values
      else
        { master_id: id }
      end

    current_version.keys.each do |key|
      next if excluded_columns.include? key
      current_attributes[key] = current_version.public_send key
    end if current_version?
    model.version_class.new current_attributes
  end
  pending_version.set_all attributes
end
audited?() click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 296
def audited?
  !!self.class.audit_class
end
before_create() click to toggle source
Calls superclass method
# File lib/sequel/plugins/bitemporal.rb, line 357
def before_create
  @create_version = pending_version_holds_changes?
  super
end
before_update() click to toggle source
Calls superclass method
# File lib/sequel/plugins/bitemporal.rb, line 370
def before_update
  if pending_version_holds_changes?
    expire_previous_versions
    return false unless save_pending_version
  end
  super
end
before_validation() click to toggle source
Calls superclass method
# File lib/sequel/plugins/bitemporal.rb, line 300
def before_validation
  prepare_pending_version
  super
end
composite_primary_key?() click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 383
def composite_primary_key?
  [*primary_key].size > 1
end
deleted?() click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 425
def deleted?
  !new? && !current_version
end
destroy() click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 387
def destroy
  point_in_time = ::Sequel::Plugins::Bitemporal.point_in_time
  versions_dataset.where(
    expired_at: nil
  ).where(
    Sequel.lit("valid_to>valid_from")
  ).update expired_at: point_in_time
end
destroy_version(version, expand_previous_version) click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 396
def destroy_version(version, expand_previous_version)
  now = ::Sequel::Plugins::Bitemporal.now
  point_in_time = ::Sequel::Plugins::Bitemporal.point_in_time
  return false if version.valid_to.to_datetime<=now
  associations.delete :current_version
  model.db.transaction do
    success = true
    version_was_valid = now>=version.valid_from.to_datetime
    if expand_previous_version
      previous = versions_dataset.where({
        expired_at: nil,
        valid_to: version.valid_from,
      }).where(Sequel.lit("valid_to>valid_from")).first
      if previous
        if version_was_valid
          success &&= save_fossil previous, created_at: point_in_time, valid_from: now, valid_to: version.valid_to
        else
          success &&= save_fossil previous, created_at: point_in_time, valid_to: version.valid_to
          success &&= previous.update expired_at: point_in_time
        end
      end
    end
    success &&= save_fossil version, created_at: point_in_time, valid_to: now if version_was_valid
    success &&= version.update expired_at: point_in_time
    raise Sequel::Rollback unless success
    success
  end
end
last_version() click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 429
def last_version
  @last_version ||= begin
    return if new?
    t = ::Sequel::Plugins::Bitemporal.point_in_time
    n = ::Sequel::Plugins::Bitemporal.now
    if use_ranges = self.class.use_ranges
      range_conditions = self.class.existence_range_contains t
    end
    versions_dataset.where do
      if use_ranges
        range_conditions
      else
        (created_at <= t) &
        Sequel.|({expired_at=>nil}, expired_at > t)
      end & (valid_from <= n)
    end.order(Sequel.desc(:valid_to), Sequel.desc(:created_at)).first
  end
end
next_version() click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 448
def next_version
  @next_version ||= begin
    return if new?
    t = ::Sequel::Plugins::Bitemporal.point_in_time
    n = ::Sequel::Plugins::Bitemporal.now
    if use_ranges = self.class.use_ranges
      range_conditions = self.class.existence_range_contains t
    end
    versions_dataset.where do
      if use_ranges
        range_conditions
      else
        (created_at <= t) &
        Sequel.|({expired_at=>nil}, expired_at > t)
      end & (valid_from > n)
    end.order(Sequel.asc(:valid_to), Sequel.desc(:created_at)).first
  end
end
pending_or_current_version() click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 312
def pending_or_current_version
  pending_version || current_version || initial_version
end
propagated_during_last_save() click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 490
def propagated_during_last_save
  @propagated_during_last_save ||= []
end
reload() click to toggle source
Calls superclass method
# File lib/sequel/plugins/bitemporal.rb, line 482
def reload
  @last_version = nil
  @current_version_values = nil
  @pending_version = nil
  @initial_version = nil
  super
end
restore(attrs={}) click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 467
def restore(attrs={})
  return false unless deleted?
  last_version_attributes = if last_version
    last_version.columns.each_with_object({}) do |column, hash|
      unless excluded_columns.include? column
        hash[column] = last_version.public_send column
      end
    end
  else
    {}
  end
  update_attributes last_version_attributes.merge attrs
  @last_version = nil
end
update_attributes(attributes={}) click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 348
def update_attributes(attributes={})
  self.attributes = attributes
  if save raise_on_failure: false
    self
  else
    false
  end
end
validate() click to toggle source
Calls superclass method
# File lib/sequel/plugins/bitemporal.rb, line 305
def validate
  super
  pending_version.errors.each do |key, key_errors|
    key_errors.each{|error| errors.add key, error}
  end if pending_version_holds_changes? && !pending_version.valid?
end
version_foreign_keys() click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 494
def version_foreign_keys
  composite_primary_key? ? primary_key : :master_id
end
version_values() click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 344
def version_values
  version_foreign_keys.map { |k| [k, public_send(k)] }.to_h
end

Private Instance Methods

current_version?() click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 618
def current_version?
  !new? && current_version
end
excluded_columns() click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 654
def excluded_columns
  self.class.excluded_columns
end
excluded_columns_for_changes() click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 658
def excluded_columns_for_changes
  []
end
expire_previous_versions() click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 508
def expire_previous_versions
  master_changes = values.select{|k| changed_columns.include? k}
  lock!
  set master_changes
  expired = versions_dataset.where expired_at: nil
  expired = expired.exclude Sequel.lit("valid_from=valid_to")
  expired = expired.exclude Sequel.lit("valid_to<=?", pending_version.valid_from)
  pending_version.valid_to ||= expired.where(Sequel.lit("valid_from>?", pending_version.valid_from)).min(:valid_from)
  pending_version.valid_to ||= Time.utc 9999
  expired = expired.exclude Sequel.lit("valid_from>=?", pending_version.valid_to)
  expired = expired.all
  expired.each do |expired_version|
    if expired_version.valid_from<pending_version.valid_from && expired_version.valid_to>pending_version.valid_from
      return false unless save_fossil expired_version, created_at: pending_version.created_at, valid_to: pending_version.valid_from
    elsif expired_version.valid_from<pending_version.valid_to && expired_version.valid_to>pending_version.valid_to
      return false unless save_fossil expired_version, created_at: pending_version.created_at, valid_from: pending_version.valid_to
    end
  end
  versions_dataset.where(id: expired.collect(&:id)).update expired_at: pending_version.created_at
end
initial_version() click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 662
def initial_version
  @initial_version ||= model.version_class.new
end
pending_version_holds_changes?() click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 622
def pending_version_holds_changes?
  return false unless @pending_version
  return true unless current_version?
  @current_version_values = values_for_changes current_version
  values_for_changes(pending_version).any? do |column, new_value|
    case column
    when :id, :created_at, :expired_at, *version_foreign_keys
      false
    when :valid_from
      pending_version.values.has_key?(:valid_from) && (
        new_value<current_version.valid_from ||
        (
          current_version.valid_to &&
          new_value>current_version.valid_to
        )
      )
    when :valid_to
      pending_version.values.has_key?(:valid_to) &&
        new_value!=current_version.valid_to
    else
      if model.version_uses_string_nilifier
        if current_version.respond_to? :nil_string?
          new_value = nil if current_version.nil_string? column, new_value
        elsif !model.version_class.skip_input_transformer?(:string_nilifier, column)
          new_value = model.version_class.input_transformers[:string_nilifier].call(new_value)
        end
      end
      current_version.public_send(column)!=new_value
    end
  end
end
prepare_pending_version() click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 500
def prepare_pending_version
  return unless pending_version_holds_changes?
  now = ::Sequel::Plugins::Bitemporal.now
  point_in_time = ::Sequel::Plugins::Bitemporal.point_in_time
  pending_version.created_at = point_in_time
  pending_version.valid_from ||= now
end
propagate_changes_to_future_versions() click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 529
def propagate_changes_to_future_versions
  return true unless self.class.propagate_per_column
  @propagated_during_last_save = []
  lock!
  futures = versions_dataset.where expired_at: nil
  futures = futures.exclude Sequel.lit("valid_from=valid_to")
  futures = futures.exclude Sequel.lit("valid_to<=?", pending_version.valid_from)
  futures = futures.where Sequel.lit("valid_from>?", pending_version.valid_from)
  futures = futures.order(:valid_from).all

  to_check_columns = self.class.version_class.columns - excluded_columns
  updated_by = (send(self.class.audit_updated_by_method) if audited?)
  previous_values = @current_version_values || {}
  current_values = values_for_changes pending_version
  futures.each do |future_version|
    attrs = {}
    to_check_columns.each do |col|
      if previous_values[col]==future_version[col] &&
          previous_values[col]!=current_values[col]
        attrs[col] = current_values[col]
      end
    end
    if attrs.any?
      propagated = save_propagated future_version, attrs
      previous_values = values_for_changes future_version
      current_values = values_for_changes propagated
      if !propagated.new? && audited? && updated_by
        self.class.audit_class.audit(
          self,
          previous_values,
          current_values,
          propagated.valid_from,
          updated_by
        )
      end
      future_version.this.update :expired_at => Sequel::Plugins::Bitemporal.point_in_time
    else
      break
    end
  end
end
save_fossil(expired, attributes={}) click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 593
def save_fossil(expired, attributes={})
  fossil = model.version_class.new
  expired_attributes = expired.keys.each_with_object({}) do |key, hash|
    hash[key] = expired.public_send key
  end
  expired_attributes.delete :id
  fossil.set_all expired_attributes.merge(attributes)
  fossil.save validate: false
  fossil.send :_refresh_set_values, fossil.values
end
save_pending_version() click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 571
def save_pending_version
  current_values_for_audit = @current_version_values || {}
  pending_version.valid_to ||= Time.utc 9999
  success = add_version pending_version
  if success
    if audited?
      updated_by = send(self.class.audit_updated_by_method)
      self.class.audit_class.audit(
        self,
        current_values_for_audit,
        values_for_changes(pending_version),
        pending_version.valid_from,
        updated_by
      ) if updated_by
    end
    propagate_changes_to_future_versions
    @current_version_values = nil
    @pending_version = nil
  end
  success
end
save_propagated(version, attributes={}) click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 604
def save_propagated(version, attributes={})
  propagated = model.version_class.new
  version_attributes = version.keys.each_with_object({}) do |key, hash|
    hash[key] = version.public_send key
  end
  version_attributes.delete :id
  version_attributes[:created_at] = Sequel::Plugins::Bitemporal.point_in_time
  propagated.set_all version_attributes.merge(attributes)
  propagated.save validate: false
  propagated.send :_refresh_set_values, propagated.values
  propagated_during_last_save << propagated
  propagated
end
values_for_changes(version) click to toggle source
# File lib/sequel/plugins/bitemporal.rb, line 666
def values_for_changes(version)
  values = {}
  (version.columns - excluded_columns_for_changes).each do |column|
    values[column] = version.public_send column
  end
  values
end