table_differ 0.5.0 → 0.6.0

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: 753fc1ecd3ac45fb1a11785069a8236127cde352
4
- data.tar.gz: 10d8fd04834d8cf542e86717332f8ad831f48cf8
3
+ metadata.gz: 0cca324c658aebd498e3a150ef613027b416f434
4
+ data.tar.gz: 78b5e10edf2b2da4ffdc209ca8ce4d8d62418db9
5
5
  SHA512:
6
- metadata.gz: 383647ef5b7608af4b895548868e33459bc752ec9ba5c82700d1678229c166d2b219b858e35fdb6aead81a72c3c50aec80acf0cc7f4134c76222a6f39707c806
7
- data.tar.gz: 809cf0d2b6519daf343f5f1d8e01f7b9f7739bdd71b7c811ff19b7702bc2997ad74f23fae0f09e85653b29921181c7f00e19155d376bbe3d51a1247eefe52505
6
+ metadata.gz: 6238ba387974306c3e18deeac1cfebb5da47c5565e51f6905423584878068e8ebf0b61a446ccabd93cf9a91b7e53bb18bdf7084b64f5f1d586b09b5cc5dc409c
7
+ data.tar.gz: c1351cd32f9e19f439bd40f47273d5405c1b24dc0184941c54d004b93294ab48905fa273c690276e38d691b48634a9a741a9b708432b1a11c2cad5e97179a943
data/README.md CHANGED
@@ -3,6 +3,7 @@
3
3
  Take snapshots of database tables 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
+ [![Gem Version](https://badge.fury.io/rb/table_differ.svg)](http://badge.fury.io/rb/table_differ)
6
7
 
7
8
  ## Installation
8
9
 
@@ -17,12 +18,11 @@ gem 'table_differ'
17
18
  ```ruby
18
19
  Attachment.create_snapshot
19
20
  => "attachments_20140626_233336"
20
- Attachment.first.touch # set updated_at to right now
21
- => true
22
- added,removed,changed = Attachment.diff_snapshot
21
+ Attachment.first.update_attributes!(name: 'newname')
22
+ added,removed,changed = Attachment.diff_snapshot # diffs against most recent snapshot
23
23
  => [[], [], [<Attachment 1>]]
24
- changed.first.original_attributes # returns the fields that have changed
25
- => {"updated_at"=>Fri, 27 Jun 2014 05:45:56 UTC +00:00}
24
+ changed.first.original_attributes # returns original value for each field
25
+ => {"name" => 'oldname'}
26
26
  Attachment.delete_snapshot "attachments_20140626_233336"
27
27
  ```
28
28
 
@@ -85,7 +85,7 @@ made. For example, if you changed the name column from 'Nexus' to 'Nexii':
85
85
  record.attributes
86
86
  => { 'id' => 1, 'name' => 'Nexus' }
87
87
  record.original_attributes
88
- => { 'name' => 'Nexii' } # id didn't change so it's not included
88
+ => { 'id' => 1, 'name' => 'Nexii' }
89
89
  ```
90
90
 
91
91
  Single-Table Inheritance (STI) appears to work correctly (TODO: add this to tests!)
@@ -108,6 +108,20 @@ so you can ignore the empty third array.
108
108
  added,removed = Attachment.diff_snapshot(ignore: 'id')
109
109
  ```
110
110
 
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
114
+ 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.
116
+
117
+ ```ruby
118
+ # 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.
121
+ added,removed,changed = Contact.diff_snapshot(ignore: 'id', unique_by: [:property_id, :contact_id])
122
+
123
+ ```
124
+
111
125
  Also, if you ignore the ID, you won't be able to update or save any models directly.
112
126
  You must copy the attributes to another model, one that was loaded from the database
113
127
  normally and still knows its ID.
@@ -124,10 +138,12 @@ a,r,c = Property.diff_snapshot('cc', 'cd') # difference between the tw
124
138
  ### Delete Snapshots
125
139
 
126
140
  delete_snapshot gets rid of unwanted snapshots.
127
- Either pass a name or a proc to specify which snapshots should be deleted.
141
+ Pass an array of names or a proc to specify which snapshots should be deleted,
142
+ or `:all`.
128
143
 
129
144
  ```ruby
130
- Property.delete_snapshot 'import_0012'
145
+ Property.delete_snapshot 'import_0012'
146
+ Property.delete_snapshots :all
131
147
 
132
148
  week_old_name = Property.snapshot_name(1.week.ago)
133
149
  old_snapshots = Property.snapshots.select { |name| name < week_old_name }
@@ -1,3 +1,3 @@
1
1
  module TableDiffer
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.0"
3
3
  end
data/lib/table_differ.rb CHANGED
@@ -44,8 +44,24 @@ module TableDiffer
44
44
  end
45
45
 
46
46
  # deletes every snapshot named in the array
47
- def delete_snapshots snapshots
48
- snapshots.each { |name| delete_snapshot(name) }
47
+ # Model.delete_snapshots(:all) deletes all snapshots
48
+ def delete_snapshots snaps
49
+ snaps = self.snapshots if snaps == :all
50
+ snaps.each { |name| delete_snapshot(name) }
51
+ end
52
+
53
+ def table_differ_remap_objects params, records
54
+ params = Array(params)
55
+ records.map do |record|
56
+ args = params.inject({}) { |hash,key| hash[key] = record[key]; hash }
57
+ real_record = where(args).first
58
+ if real_record
59
+ real_record.original_attributes = record.attributes
60
+ real_record
61
+ else
62
+ record
63
+ end
64
+ end
49
65
  end
50
66
 
51
67
  # ignore: %w[ created_at updated_at id ]
@@ -71,19 +87,23 @@ module TableDiffer
71
87
  # actually, it's probably more reliable just to use the presence of an id to determine if the record can be saved
72
88
  # [*added, *removed].select { |o| !o.id }.each { |o| o.instance_variable_set("@new_record", true) }
73
89
 
90
+ if options[:unique_by]
91
+ added = table_differ_remap_objects(options[:unique_by], added)
92
+ removed = table_differ_remap_objects(options[:unique_by], removed)
93
+ end
94
+
74
95
  changed = added & removed
75
96
  changed.each do |obj|
76
97
  orig = removed.find { |r| r == obj }
77
98
  raise "this is impossible" if orig.nil?
78
-
79
- nattrs = obj.attributes
80
- # remove all unchanged elements -- original_attributes only contains changed values
81
- oattrs = orig.attributes.reject { |k,v| nattrs.include?(k) && nattrs[k] == v }
82
-
83
- obj.original_attributes = HashWithIndifferentAccess.new(oattrs)
99
+ obj.original_attributes = orig.original_attributes || orig.attributes
84
100
  end
85
101
 
86
- [added - changed, removed - changed, changed]
102
+ added -= changed
103
+ removed -= changed
104
+ [*added, *removed].each { |o| o.original_attributes = nil }
105
+
106
+ [added, removed, changed]
87
107
  end
88
108
  end
89
109
  end
data/spec/diff_spec.rb CHANGED
@@ -63,8 +63,7 @@ describe "diffing a model" do
63
63
  expect(changed.first.name).to eq 'uno'
64
64
 
65
65
  # ensure we can access the previous value, with indifferent access
66
- expect(changed.first.original_attributes[:name]).to eq 'one'
67
- expect(changed.first.original_attributes).to eq({'name' => 'one'})
66
+ expect(changed.first.original_attributes).to eq({'id' => one.id, 'name' => 'one'})
68
67
 
69
68
  # changed records are normal AR objects, try using it
70
69
  changed.first.update_attributes!(name: 'nuevo')
@@ -123,7 +122,7 @@ describe "diffing a model" do
123
122
  end
124
123
 
125
124
  # without an ID, we can't tell if anything changed
126
- it "detects a changed field" do
125
+ it "detects a changed field by add/remove" do
127
126
  one = Model.where(name: 'one').first
128
127
  one.update_attributes!(name: 'uno')
129
128
  added,removed,changed = Model.diff_snapshot(ignore: :id)
@@ -0,0 +1,41 @@
1
+ describe "diffing a model" do
2
+ include_context "surrogate_model"
3
+
4
+ it "detects a changed field using a single surrogate" do
5
+ first = SurrogateModel.create!(name: 'one', original_name: 'one')
6
+ second = SurrogateModel.create!(name: 'two', original_name: 'two')
7
+
8
+ SurrogateModel.create_snapshot('original')
9
+
10
+ first.update_attributes!(name: 'uno')
11
+ third = SurrogateModel.create!(name: 'three', original_name: 'three')
12
+ second.destroy!
13
+
14
+ added,removed,changed = SurrogateModel.diff_snapshot(ignore: :id, unique_by: :original_name)
15
+
16
+ # we can find added and changed records by surrogate IDs but, of course, can't find removed ones
17
+ expect(added).to eq [third]
18
+ expect(added.first.original_attributes).to eq nil
19
+ expect(removed.map(&:attributes)).to eq [{"id" => nil, "name" => "two", "original_name" => "two", "alternate_value" => nil}]
20
+ expect(removed.first.original_attributes).to eq nil
21
+ expect(changed).to eq [first]
22
+ expect(changed.first.name).to eq 'uno'
23
+ expect(changed.first.original_attributes).to eq({"id" => nil, "name" => 'one', "original_name" => 'one', "alternate_value" => nil})
24
+ end
25
+
26
+ it "detects a changed field using a composite surrogate" do
27
+ first = SurrogateModel.create!(name: 'one', original_name: 'one', alternate_value: 1)
28
+ second = SurrogateModel.create!(name: 'one', original_name: 'one', alternate_value: 2)
29
+ third = SurrogateModel.create!(name: 'one', original_name: 'one', alternate_value: 3)
30
+
31
+ SurrogateModel.create_snapshot('original')
32
+
33
+ second.update_attributes!(name: 'uno')
34
+
35
+ added,removed,changed = SurrogateModel.diff_snapshot(ignore: :id, unique_by: [:original_name, 'alternate_value'])
36
+
37
+ expect(added).to eq []
38
+ expect(removed).to eq []
39
+ expect(changed).to eq [second] # the alternate value should ensure we pick up the correct record
40
+ end
41
+ end
@@ -47,4 +47,11 @@ describe TableDiffer do
47
47
  Model.delete_snapshots(to_delete)
48
48
  expect(Model.snapshots.sort).to eq ['models_21']
49
49
  end
50
+
51
+ it "deletes all snapshots" do
52
+ Model.create_snapshot('snapname')
53
+ expect(Model.snapshots.size).to eq 1
54
+ Model.delete_snapshots(:all)
55
+ expect(Model.snapshots.size).to eq 0
56
+ end
50
57
  end
data/spec/spec_helper.rb CHANGED
@@ -23,6 +23,12 @@ RSpec.configure do |config|
23
23
  create_table :models do |table|
24
24
  table.column :name, :string
25
25
  end
26
+
27
+ create_table :surrogate_models do |table|
28
+ table.column :name, :string
29
+ table.column :original_name, :string
30
+ table.column :alternate_value, :string
31
+ end
26
32
  end
27
33
 
28
34
  # need to explicitly specify active_record since we don't have a database.yml?
@@ -44,6 +50,12 @@ RSpec.shared_context "model" do
44
50
  end
45
51
  end
46
52
 
53
+ RSpec.shared_context "surrogate_model" do
54
+ class SurrogateModel < ActiveRecord::Base
55
+ include TableDiffer
56
+ end
57
+ end
58
+
47
59
  =begin
48
60
  maybe later...
49
61
 
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.5.0
4
+ version: 0.6.0
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-06-27 00:00:00.000000000 Z
11
+ date: 2014-07-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -100,6 +100,7 @@ files:
100
100
  - lib/table_differ.rb
101
101
  - lib/table_differ/version.rb
102
102
  - spec/diff_spec.rb
103
+ - spec/remap_spec.rb
103
104
  - spec/snapshot_spec.rb
104
105
  - spec/spec_helper.rb
105
106
  - tablediffer.gemspec
@@ -130,5 +131,6 @@ summary: Take snapshots of database tables and compute the differences between t
130
131
  snapshots.
131
132
  test_files:
132
133
  - spec/diff_spec.rb
134
+ - spec/remap_spec.rb
133
135
  - spec/snapshot_spec.rb
134
136
  - spec/spec_helper.rb