module TableDiffer::ClassMethods

Public Instance Methods

create_snapshot(suggestion=Time.now) click to toggle source

creates a new snapshot

# File lib/table_differ.rb, line 35
def create_snapshot suggestion=Time.now
  name = snapshot_name(suggestion)
  connection.execute("CREATE TABLE #{name} AS SELECT * FROM #{table_name}")
  name
end
delete_snapshot(name) click to toggle source

deletes the named snapshot

# File lib/table_differ.rb, line 50
def delete_snapshot name
  connection.execute("DROP TABLE #{snapshot_name(name)}")
end
delete_snapshots(snaps=self.snapshots, &block) click to toggle source

deletes every snapshot named in the array Model.delete_snapshots(:all) deletes all snapshots

# File lib/table_differ.rb, line 56
def delete_snapshots snaps=self.snapshots, &block
  snaps = snaps.select(&block) if block
  snaps.each { |name| delete_snapshot(name) }
end
diff_snapshot(options={}) click to toggle source
# File lib/table_differ.rb, line 87
def diff_snapshot options={}
  oldtable = snapshot_name(options[:old]) || snapshots.last
  newtable = snapshot_name(options[:new]) || table_name

  ignore = []
  if options[:ignore]
    ignore = Array(options[:ignore]).map(&:to_s)
  end

  columns = column_names - ignore
  cols = columns.map { |c| "#{c} as #{c}" }.join(", ")

  added =   find_by_sql("SELECT #{cols} FROM #{newtable} EXCEPT SELECT #{cols} FROM #{oldtable}")
  removed = find_by_sql("SELECT #{cols} from #{oldtable} EXCEPT SELECT #{cols} FROM #{newtable}")

  # hm, none of this seems to matter...  TODO: mark appropriate objects read-only: obj.readonly!
  # AR always thinks the record is persisted in the db, even when it obviously isn't
  # added.each   { |o| o.instance_variable_set("@new_record", true) } unless table_name == oldtable
  # removed.each { |o| o.instance_variable_set("@new_record", true) } unless table_name == newtable
  # actually, it's probably more reliable just to use the presence of an id to determine if the record can be saved
  # [*added, *removed].select { |o| !o.id }.each { |o| o.instance_variable_set("@new_record", true) }

  if options[:unique_by]
    added = table_differ_remap_objects(options[:unique_by], added, newtable)
    removed = table_differ_remap_objects(options[:unique_by], removed, oldtable)
  end

  changed = added & removed
  changed.each do |obj|
    orig = removed.find { |r| r == obj }
    raise "this is impossible" if orig.nil?
    obj.original_attributes = (orig.original_attributes || orig.attributes).except(*ignore)
  end

  added -= changed
  removed -= changed
  [*added, *removed].each { |o| o.original_attributes = nil }

  [added, removed, changed]
end
restore_snapshot(name) click to toggle source
# File lib/table_differ.rb, line 41
def restore_snapshot name
  name = snapshot_name(name)
  raise "#{name} doesn't exist" unless connection.tables.include?(name)

  delete_all
  connection.execute("INSERT INTO #{table_name} SELECT * FROM #{name}")
end
snapshot_name(name) click to toggle source

pass a date or name fragment, receive the full snapshot name. it's ok to pass a snapshot name; it will be returned unchaged.

# File lib/table_differ.rb, line 15
def snapshot_name name
  return nil if name.nil?

  if name.kind_of?(Date) || name.kind_of?(Time)
    name = name.strftime("%Y%m%d_%H%M%S")
  end

  unless name.index(table_name) == 0
    name = "#{table_name}_#{name}"
  end

  name
end
snapshots() click to toggle source

returns an array of the snapshot names that currently exist

# File lib/table_differ.rb, line 30
def snapshots
  connection.tables.grep(/^#{table_name}_/).sort
end
table_differ_remap_objects(params, records, table) click to toggle source
# File lib/table_differ.rb, line 61
def table_differ_remap_objects params, records, table
  model = self
  if table != table_name
    # create an exact copy of the model, but using a different table
    model = Class.new(self)
    model.table_name = table
  end

  params = Array(params)
  records.map do |record|
    result = record
    if record.id.nil?   # don't look up real ActiveRecord object if we already have one
      args = params.inject({}) { |hash,key| hash[key] = record[key]; hash }
      real_record = model.where(args).first
      if real_record
        if model != self
          real_record = self.new(real_record.attributes)  # convert fake model to real model
        end
        real_record.original_attributes = record.attributes
        result = real_record
      end
    end
    result
  end
end