table_differ 0.6.10 → 0.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 664380ceed99a8c963e10e4cbca8ef22aaa4ca0e
4
- data.tar.gz: 4001965b54f65033d46cd68aed094eb8b5f85137
3
+ metadata.gz: 602a0b1ed4708321a8bee1cfc177a4c194df5208
4
+ data.tar.gz: 8ad8aa0badd13e031ce7a350bcc05fae945636b1
5
5
  SHA512:
6
- metadata.gz: d6f709d20c230f814d77499eb10684553a36b4d0605254caefca757bd9337313686a833f30663a2c52ebf987974fbb1b651ecdccc815c3e03a12c07e63b33577
7
- data.tar.gz: 9743c8e8118bda9387dabab7dd533618f1aaee33a5d604d0d7ce97c52c8c19aa74c4a7dba43914bf1473f4daa615b04344ef2afb6faf27c692b2116e7a69c43f
6
+ metadata.gz: 87f01c2b832339b5fe798dbc15d5ac906632c77abdcd34cb6a4181dc69f1dc9c684175ee881a289445e68277c306f0723f99f3785e897a7296dcffd38d7aa3c8
7
+ data.tar.gz: d86fd94c247b7e7977ca003bb830cfb87dd8e167f9ab67cb3188f09260daa2c4cf2c51c04ade1cafbc90a7f94287ef87cf35364a48bc4fe62124057b62979e11
data/Gemfile CHANGED
@@ -1,2 +1,8 @@
1
1
  source 'https://rubygems.org'
2
2
  gemspec
3
+
4
+ group :development do
5
+ gem 'guard'
6
+ gem 'guard-rspec'
7
+ gem 'guard-bundler'
8
+ end
data/Guardfile CHANGED
@@ -3,7 +3,7 @@ guard :bundler do
3
3
  watch(/^.+\.gemspec/)
4
4
  end
5
5
 
6
- guard :rspec do
6
+ guard :rspec, cmd: 'rspec' do
7
7
  watch(%r{^spec/.+_spec\.rb$})
8
8
  watch(%r{.*\.rb})
9
9
  watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Table Differ
2
2
 
3
- Take snapshots of database tables and compute the differences between two snapshots.
3
+ Snapshot database tables, restore them, and compute the differences between two snapshots.
4
4
 
5
5
  [![Build Status](https://api.travis-ci.org/bronson/table_differ.png?branch=master)](http://travis-ci.org/bronson/table_differ)
6
6
  [![Gem Version](https://badge.fury.io/rb/table_differ.svg)](http://badge.fury.io/rb/table_differ)
@@ -15,15 +15,21 @@ gem 'table_differ'
15
15
 
16
16
  ## Synopsis
17
17
 
18
+ To follow this, replace `Attachment` with any model from your own application.
19
+ Once you restore the snapshot, your database should appear unchanged.
20
+
18
21
  ```ruby
19
- Attachment.create_snapshot
20
- => "attachments_20140626_233336"
21
- Attachment.first.update_attributes!(name: 'newname')
22
- added,removed,changed = Attachment.diff_snapshot # diffs against most recent snapshot
22
+ snapshot = Attachment.create_snapshot
23
+ Attachment.first.update_attributes!(name: 'newname') # make a change
24
+ # or run rake db:migrate, Attachment.delete_all, or anything.
25
+
26
+ # compute the changes
27
+ added,removed,changed = Attachment.diff_snapshot(snapshot)
23
28
  => [[], [], [<Attachment 1>]]
24
- changed.first.original_attributes # returns original value for each field
25
- => {"name" => 'oldname'}
26
- Attachment.delete_snapshot "attachments_20140626_233336"
29
+
30
+ Attachment.restore_snapshot(snapshot)
31
+ Attachment.delete_snapshot(snapshot)
32
+ # and we're right back where we started
27
33
  ```
28
34
 
29
35
  ## Usage
@@ -31,52 +37,75 @@ Attachment.delete_snapshot "attachments_20140626_233336"
31
37
  Include TableDiffer in models that will be snapshotted:
32
38
 
33
39
  ```ruby
34
- class Property < ActiveRecord::Base
40
+ class Attachment < ActiveRecord::Base
35
41
  include TableDiffer
36
42
  ...
37
43
  end
38
44
  ```
39
45
 
40
- ### Snapshot a Table
41
-
42
- Any time you want to snapshot a table (say, before a new data import),
43
- call `create_snapshot`.
46
+ ### Create Snapshot
44
47
 
45
48
  ```ruby
46
49
  Property.create_snapshot
47
50
  Property.create_snapshot 'import_0012'
48
51
  ```
49
52
 
50
- If you don't specify a name then a numeric name based on the current
51
- date will be used (something like `property_20140606_124722`)
52
- Whatever naming scheme you use, the names need to sort alphabetically so
53
- Table Differ can know which one is most recent.
53
+ If you don't specify a name then one will be specified for you.
54
+ Whatever naming scheme you use, the names should sort alphabetically.
55
+ Otherwise some Table Differ functions won't be able to default to the most recent snapshot.
54
56
 
55
- Use the snapshots method to return all the snapshots that exist now:
57
+ ### List Snapshots
56
58
 
57
59
  ```ruby
58
60
  Property.snapshots
59
61
  => ['property_import_0011', 'property_import_0012']
60
62
  ```
61
63
 
64
+ ### Restore Snapshot
65
+
66
+ ```ruby
67
+ Property.restore_snapshot 'import_0012'
68
+ ```
69
+
70
+ ### Delete Snapshots
71
+
72
+ ```ruby
73
+ Property.delete_snapshot 'import_0012'
74
+ ```
75
+
76
+ Or multiple snapshots:
77
+
78
+ ```ruby
79
+ Property.delete_snapshots ['import_01', 'import_02']
80
+ Property.delete_snapshots # deletes all Property snapshots
81
+
82
+ # more complex: delete all snapshots more than one week old
83
+ week_old_name = Property.snapshot_name(1.week.ago)
84
+ Property.delete_snapshots { |name| name < week_old_name }
85
+ ```
86
+
62
87
  ### Compute Differences
63
88
 
64
89
  Now, to retrieve a list of the differences, call diff_snapshot:
65
90
 
66
91
  ```ruby
67
- added,removed,changed = Property.diff_snapshot
92
+ added,removed,changed = Attachment.diff_snapshot # compute the change
93
+ => [[], [], [<Attachment 1>]]
94
+ changed.first.original_attributes # returns original value for each field
95
+ => {"name" => 'oldname'}
68
96
  ```
69
97
 
70
98
  This computes the difference between the current table and the most recent
71
99
  snapshot (determined alphabetically). Each value is an array of ActiveRecord
72
100
  objects. `added` contains the records that have been added since the snapshot
73
101
  was taken, `removed` contains the records that were removed, and `changed` contains
74
- records where, of course, one or more of their columns have changed. Tablediffer
75
- doesn't follow foreign keys so, if you want that, you'll need to do it manually.
102
+ records where, of course, one or more of their columns have changed. Table Differ
103
+ doesn't follow foreign keys for that would be madness. If you want to discover
104
+ changes in related tables, you'll need to snapshot and diff them one by one.
76
105
 
77
106
  Records in `added` and `changed` are regular ActiveRecord objects -- you can modify
78
107
  their attributes and save them. Records in `removed`, however, aren't backed by
79
- a database object and should be treated read-only.
108
+ a database object (obviously) and should be treated read-only.
80
109
 
81
110
  Changed records include a hash of the original attributes before the change was
82
111
  made. For example, if you changed the name column from 'Nexus' to 'Nexii':
@@ -101,25 +130,24 @@ Property.diff_snapshot ignore: %w[ id created_at updated_at ]
101
130
  ```
102
131
 
103
132
  Note that if you ignore the primary key, Table Differ can no longer compute which
104
- columns have changed. Changed records will appear as a remove followed by an add,
105
- so you can ignore the empty third array.
133
+ columns have changed. This is no problem, but changed records will appear as a
134
+ remove followed by an add. The changed array will always be empty.
106
135
 
107
136
  ```ruby
108
137
  added,removed = Attachment.diff_snapshot(ignore: 'id')
109
138
  ```
110
139
 
111
- If there are other fields that you can use to uniquely identify the records,
112
- you can specify them in the unique_by option. This will ensure that changes
113
- are returned (not just adds/removes), and the ActiveRecord objects returned are
140
+ If there are other fields that uniquely identify the records,
141
+ you can specify them in the unique_by option. This will cause changes to
142
+ be computed, and the ActiveRecord objects returned are
114
143
  complete with IDs. This requires one database lookup per returned object,
115
- however so, if your results sets are huge, this might not be a good idea.
144
+ however so, if your results are large, this might not be a good idea.
116
145
 
117
146
  ```ruby
118
147
  # Normally ingoring the ID prevents diff from being able to compute the changed records.
119
- # If we can tell it that one or more fields can be used to uniquely identify the object,
120
- # then it can compute the changed records and return full ActiveRecord objects.
148
+ # If we can use one or more fields to uniquely identify the object,
149
+ # then changesets can be computed and full ActiveRecord objects will be returned.
121
150
  added,removed,changed = Contact.diff_snapshot(ignore: 'id', unique_by: [:property_id, :contact_id])
122
-
123
151
  ```
124
152
 
125
153
  Also, if you ignore the ID, you won't be able to update or save any models directly.
@@ -131,23 +159,8 @@ normally and still knows its ID.
131
159
  You can name the tables you want to diff explicitly:
132
160
 
133
161
  ```ruby
134
- a,r,c = Property.diff_snapshot(old: 'import_0012') # changes between the named snapshot and now
135
- a,r,c = Property.diff_snapshot('cc', 'cd') # difference between the two snapshots named cc and cd
136
- ```
137
-
138
- ### Delete Snapshots
139
-
140
- delete_snapshot gets rid of unwanted snapshots.
141
- Pass an array of names or a proc to specify which snapshots should be deleted,
142
- or `:all`.
143
-
144
- ```ruby
145
- Property.delete_snapshot 'import_0012'
146
- Property.delete_snapshots :all
147
-
148
- week_old_name = Property.snapshot_name(1.week.ago)
149
- old_snapshots = Property.snapshots.select { |name| name < week_old_name }
150
- Property.delete_snapshots(old_snapshots)
162
+ add,del,ch = Property.diff_snapshot(old: 'import_0012') # differences between the named snapshot and the table
163
+ add,del,ch = Property.diff_snapshot('cc', 'cd') # differences between the snapshots named cc and cd
151
164
  ```
152
165
 
153
166
  ## Internals
@@ -156,8 +169,16 @@ Table Differ creates a full copy of the table whenever Snapshot is called.
156
169
  If your table is large enough that it would cause problems if it suddenly
157
170
  doubled in size, then this is not the gem for you.
158
171
 
159
- Table Differ diffs the tables server-side using only two SELECT queries.
160
- This should be plenty fast for any normal usage.
172
+ Table Differ creates and restores snapshots with a single CREATE/SELECT statement,
173
+ and it diffs the tables 100% server-side using two SELECTs. It should be fast
174
+ enough.
175
+
176
+ It doesn't touch indicies.
177
+
178
+
179
+ ## Alternatives
180
+
181
+ * [Stellar](https://github.com/fastmonkeys/stellar) appears to do the same thing, written in Python.
161
182
 
162
183
 
163
184
  ## Contributing
data/TODO CHANGED
@@ -1,2 +1,3 @@
1
+ TODO: the diff_snapshot(old: s) syntax is weird.
1
2
  TODO: try to mark relevant records read-only
2
3
  TODO: could CREATE DATABASE ... TEMPLATE work to create snapshots?
@@ -38,6 +38,14 @@ module TableDiffer
38
38
  name
39
39
  end
40
40
 
41
+ def restore_snapshot name
42
+ name = snapshot_name(name)
43
+ raise "#{name} doesn't exist" unless connection.tables.include?(name)
44
+
45
+ delete_all
46
+ connection.execute("INSERT INTO #{table_name} SELECT * FROM #{name}")
47
+ end
48
+
41
49
  # deletes the named snapshot
42
50
  def delete_snapshot name
43
51
  connection.execute("DROP TABLE #{snapshot_name(name)}")
@@ -45,14 +53,15 @@ module TableDiffer
45
53
 
46
54
  # deletes every snapshot named in the array
47
55
  # Model.delete_snapshots(:all) deletes all snapshots
48
- def delete_snapshots snaps
49
- snaps = self.snapshots if snaps == :all
56
+ def delete_snapshots snaps=self.snapshots, &block
57
+ snaps = snaps.select(&block) if block
50
58
  snaps.each { |name| delete_snapshot(name) }
51
59
  end
52
60
 
53
61
  def table_differ_remap_objects params, records, table
54
62
  model = self
55
63
  if table != table_name
64
+ # create an exact copy of the model, but using a different table
56
65
  model = Class.new(self)
57
66
  model.table_name = table
58
67
  end
@@ -75,7 +84,6 @@ module TableDiffer
75
84
  end
76
85
  end
77
86
 
78
- # ignore: %w[ created_at updated_at id ]
79
87
  def diff_snapshot options={}
80
88
  oldtable = snapshot_name(options[:old]) || snapshots.last
81
89
  newtable = snapshot_name(options[:new]) || table_name
@@ -1,3 +1,3 @@
1
1
  module TableDiffer
2
- VERSION = "0.6.10"
2
+ VERSION = "0.7"
3
3
  end
@@ -27,6 +27,27 @@ describe TableDiffer do
27
27
  expect(Model.snapshots.sort).to eq ['models_aiee', 'models_bee', 'models_cee']
28
28
  end
29
29
 
30
+ it "restores a snapshot" do
31
+ # TODO: test that it doesn't delete any indices
32
+ Model.create!(name: 'one')
33
+ first_id = Model.first.id # ensure the ID doesn't change
34
+ snapshot = Model.create_snapshot('snapname')
35
+ Model.create!(name: 'two')
36
+
37
+ Model.restore_snapshot(snapshot)
38
+ expect(Model.pluck(:id, :name).sort).to eq [[first_id, 'one']]
39
+
40
+ Model.delete_snapshot(snapshot)
41
+ end
42
+
43
+ it "doesn't destroy the database if the snapshot can't be found" do
44
+ snapshot = Model.create_snapshot('snapname')
45
+ expect {
46
+ Model.restore_snapshot(snapshot+' ')
47
+ }.to raise_error(/doesn't exist/)
48
+ Model.delete_snapshot(snapshot)
49
+ end
50
+
30
51
  it "deletes a named snapshot" do
31
52
  Model.create_snapshot('snapname')
32
53
  expect(Model.snapshots.size).to eq 1
@@ -34,24 +55,28 @@ describe TableDiffer do
34
55
  expect(Model.snapshots.size).to eq 0
35
56
  end
36
57
 
37
- it "deletes a bunch of snapshots" do
38
- Model.create_snapshot('21')
39
- Model.create_snapshot('22')
40
- Model.create_snapshot('33')
41
-
42
- to_delete = Model.snapshots.select do |snapname|
43
- # return true for all tables with names divisble by 11
44
- name = /(\d+)$/.match(snapname)[1]
45
- (name.to_i % 11) == 0
46
- end
47
- Model.delete_snapshots(to_delete)
48
- expect(Model.snapshots.sort).to eq ['models_21']
58
+ it "deletes all snapshots" do
59
+ Model.create_snapshot('snapname')
60
+ expect(Model.snapshots.size).to eq 1
61
+ Model.delete_snapshots
62
+ expect(Model.snapshots.size).to eq 0
49
63
  end
50
64
 
51
- it "deletes all snapshots" do
65
+ it "doesn't delete snapshots if none specified" do
52
66
  Model.create_snapshot('snapname')
53
67
  expect(Model.snapshots.size).to eq 1
54
- Model.delete_snapshots(:all)
68
+ Model.delete_snapshots []
69
+ expect(Model.snapshots.size).to eq 1
70
+ Model.delete_snapshots(Model.snapshots)
55
71
  expect(Model.snapshots.size).to eq 0
56
72
  end
73
+
74
+ it "deletes a block of snapshots" do
75
+ Model.create_snapshot('21')
76
+ Model.create_snapshot('22')
77
+ Model.create_snapshot('33')
78
+
79
+ Model.delete_snapshots { |name| name == 'models_22' || name == 'models_33' }
80
+ expect(Model.snapshots.sort).to eq ['models_21']
81
+ end
57
82
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: table_differ
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.10
4
+ version: '0.7'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Bronson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-03 00:00:00.000000000 Z
11
+ date: 2014-10-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord