Module: TableDiffer::ClassMethods

Defined in:
lib/table_differ.rb

Instance Method Summary collapse

Instance Method Details

#create_snapshot(suggestion = Time.now) ⇒ Object

creates a new snapshot



35
36
37
38
39
# 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) ⇒ Object

deletes the named snapshot



50
51
52
# 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) ⇒ Object

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



56
57
58
59
# 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 = {}) ⇒ Object



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# 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) ⇒ Object



41
42
43
44
45
46
47
# 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) ⇒ Object

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



15
16
17
18
19
20
21
22
23
24
25
26
27
# 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

#snapshotsObject

returns an array of the snapshot names that currently exist



30
31
32
# File 'lib/table_differ.rb', line 30

def snapshots
  connection.tables.grep(/^#{table_name}_/).sort
end

#table_differ_remap_objects(params, records, table) ⇒ Object



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# 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