Skip to content

Commit 1215a60

Browse files
authored
Merge pull request #21 from bdurand/delete-missing-option
Add delete_missing option to sync commands
2 parents f21ccf5 + 7093b1b commit 1215a60

5 files changed

Lines changed: 89 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## 1.6.0
8+
9+
### Added
10+
11+
- Added `delete_missing` option to `sync_table_data!` and `sync_all!`. When set to `true`, any records in the database that are not defined in the data files will be deleted. This option defaults to `false` to preserve backward compatibility.
12+
713
## 1.5.2
814

915
### Added

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,21 @@ Status.sync_table_data!
240240

241241
This will add any missing records to the table and update existing records so that the attributes in the table match the values in the data files. Records that do not appear in the data files will not be touched. Any attributes not specified in the data files will not be changed.
242242

243+
If you want to remove records from the database that are no longer in the data files, you can pass `delete_missing: true`:
244+
245+
```ruby
246+
Status.sync_table_data!(delete_missing: true)
247+
```
248+
249+
This option can also be passed to `SupportTableData.sync_all!`:
250+
251+
```ruby
252+
SupportTableData.sync_all!(delete_missing: true)
253+
```
254+
255+
> [!CAUTION]
256+
> Use `delete_missing` with care. It will delete any records in the table that are not defined in the data files, which may include user-created data or fail due to foreign key constraints.
257+
243258
The number of records contained in data files should be fairly small (ideally fewer than 100). It is possible to load just a subset of rows in a large table because only the rows listed in the data files will be synced. You can use this feature if your table allows user-entered data, but has a few rows that must exist for the code to work.
244259

245260
Loading data is done inside a database transaction. No changes will be persisted to the database unless all rows for a model can be synced.
@@ -309,6 +324,20 @@ end
309324

310325
You must also call `SupportTableData.sync_all!` before running your test suite. This method should be called in the test suite setup code after any data in the test database has been purged and before any tests are run.
311326

327+
> [!TIP]
328+
> If you are using a truncation database cleaning strategy exclude the support tables from the tables that get truncated. Syncing data is much faster when the data is already in the database. You should use the `delete_missing` option to remove any records that are not in the data files instead of deleting all records before each test.
329+
330+
```ruby
331+
# Excluding support tables from truncation in DatabaseCleaner configuration.
332+
RSpec.configure do |config|
333+
config.before(:suite) do
334+
support_tables = SupportTableData.support_table_classes.map(&:table_name)
335+
DatabaseCleaner.clean_with(:truncation, except: support_tables)
336+
SupportTableData.sync_all!(delete_missing: true)
337+
end
338+
end
339+
```
340+
312341
## Installation
313342

314343
Add this line to your application's Gemfile:

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.5.2
1+
1.6.0

lib/support_table_data.rb

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,13 @@ def support_table_key_attribute
4949
end
5050

5151
# Synchronize the rows in the table with the values defined in the data files added with
52-
# `add_support_table_data`. Note that rows will not be deleted if they are no longer in
53-
# the data files.
52+
# `add_support_table_data`. By default, rows that are no longer present in the data files
53+
# will not be deleted unless `delete_missing` is enabled.
5454
#
55+
# @param delete_missing [Boolean] If true, then any records in the database that are not in the data
56+
# files will be deleted. Use with caution.
5557
# @return [Array<Hash>] List of saved changes for each record that was created or modified.
56-
def sync_table_data!
58+
def sync_table_data!(delete_missing: false)
5759
return unless table_exists?
5860

5961
canonical_data = support_table_data.each_with_object({}) do |attributes, hash|
@@ -64,6 +66,8 @@ def sync_table_data!
6466

6567
begin
6668
ActiveSupport::Notifications.instrument("support_table_data.sync", class: self) do
69+
synced_ids = []
70+
6771
transaction do
6872
records.each do |record|
6973
key = record[support_table_key_attribute].to_s
@@ -75,6 +79,8 @@ def sync_table_data!
7579
changes << record.changes
7680
record.save!
7781
end
82+
83+
synced_ids << record.id if attributes
7884
end
7985

8086
canonical_data.each_value do |attributes|
@@ -86,6 +92,11 @@ def sync_table_data!
8692
end
8793
changes << record.changes
8894
record.save!
95+
synced_ids << record.id
96+
end
97+
98+
if delete_missing
99+
where.not(primary_key => synced_ids).destroy_all
89100
end
90101
end
91102
end
@@ -382,11 +393,13 @@ def data_directory=(value)
382393
# when the test suite is initializing.
383394
#
384395
# @param extra_classes [Class] List of classes to force into the detected list of classes to sync.
396+
# @param delete_missing [Boolean] If true, then any records in the database that are not in the data
397+
# files will be deleted from each table. Use with caution.
385398
# @return [Hash<Class, Array<Hash>] Hash of classes synced with a list of saved changes.
386-
def sync_all!(*extra_classes)
399+
def sync_all!(*extra_classes, delete_missing: false)
387400
changes = {}
388401
support_table_classes(*extra_classes).each do |klass|
389-
changes[klass] = klass.sync_table_data!
402+
changes[klass] = klass.sync_table_data!(delete_missing: delete_missing)
390403
end
391404
changes
392405
end

spec/support_table_data_spec.rb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,31 @@
6363
])
6464
expect { Color.sync_table_data! }.to raise_error(SupportTableData::ValidationError, /Validation failed for Color with id: 20 - Name can't be blank/)
6565
end
66+
67+
it "does not delete extra rows by default" do
68+
Group.sync_table_data!
69+
extra = Group.new(name: "extra")
70+
extra.group_id = 99
71+
extra.save!
72+
Group.sync_table_data!
73+
expect(Group.find_by(group_id: 99)).to eq extra
74+
end
75+
76+
it "deletes extra rows not in the data files when delete_missing is true" do
77+
Group.sync_table_data!
78+
extra = Group.new(name: "extra")
79+
extra.group_id = 99
80+
extra.save!
81+
expect(Group.find_by(group_id: 99)).to eq extra
82+
Group.sync_table_data!(delete_missing: true)
83+
expect(Group.find_by(group_id: 99)).to be_nil
84+
end
85+
86+
it "does not delete rows that are in the data files when delete_missing is true" do
87+
Group.sync_table_data!(delete_missing: true)
88+
expect(Group.count).to eq 3
89+
expect(Group.pluck(:name)).to match_array ["primary", "secondary", "gray"]
90+
end
6691
end
6792

6893
describe "sync_all!" do
@@ -104,6 +129,16 @@
104129
expect(Color.find_by(id: 10)).to eq color
105130
end
106131

132+
it "deletes extra rows not in the data files when delete_missing is true" do
133+
SupportTableData.sync_all!
134+
extra = Group.new(name: "extra")
135+
extra.group_id = 99
136+
extra.save!
137+
expect(Group.find_by(group_id: 99)).to eq extra
138+
SupportTableData.sync_all!(delete_missing: true)
139+
expect(Group.find_by(group_id: 99)).to be_nil
140+
end
141+
107142
it "combines data when a record is defined across multiple data files" do
108143
SupportTableData.sync_all!
109144
expect(purple.name).to eq "Purple"

0 commit comments

Comments
 (0)