rails-archiver 0.1.8 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/CHANGELOG.md +16 -0
- data/lib/rails-archiver/archiver.rb +15 -3
- data/lib/rails-archiver/unarchiver.rb +56 -11
- data/lib/rails-archiver/utils/deadlock_retry.rb +29 -0
- data/rails-archiver.gemspec +2 -2
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ddfcaefd86b70ef668e8ba941e20f68a96a62a704ad9e931fbe72343a6ef7ddb
|
4
|
+
data.tar.gz: d3283d3fbbb8abeafab718a445b7f129cef999f531b7766998af860c22de2c90
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a69726e5dff1eb032dcb915c67804cce258f970652045561a52a727a073493844a5879d79e2bb982d6a9427c834f0d33e1873759c29c463765d65894cb19674e
|
7
|
+
data.tar.gz: 34eb78437f58aced9d64fe518e0183f8ecc5dc24e8d24e88194d7d379a5c9ecd138c00bbc46f54f23fef280ea8994acd12b8c9a86decbc0d3fa04d9014896917
|
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## UNRELEASED
|
9
|
+
|
10
|
+
# [0.3.0] - 2025-05-22
|
11
|
+
- Added retries for delete table operations to handle SQL Timeouts and deadlocks.
|
12
|
+
|
13
|
+
# [0.2.0] - 2025-01-20
|
14
|
+
- Added support for enum columns - should be able to handle both keys and values.
|
15
|
+
- Refactored the `unarchiver` method to more easily subclass `Unarchiver`.
|
16
|
+
- For `belongs_to` associations, replace foreign key attributes with actual reference to the relevant object while archiving to avoid issues with validation.
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'tmpdir'
|
2
|
+
require 'rails-archiver/utils/deadlock_retry'
|
2
3
|
# Takes a database model and:
|
3
4
|
# 1) Visits all dependent associations
|
4
5
|
# 2) Saves everything in one giant JSON hash
|
@@ -122,15 +123,26 @@ module RailsArchiver
|
|
122
123
|
delete_query = <<-SQL
|
123
124
|
DELETE FROM `#{table}` WHERE `id` IN (#{group.compact.join(',')})
|
124
125
|
SQL
|
125
|
-
|
126
|
+
RailsArchiver::Utils::DeadlockRetry.wrap do
|
127
|
+
ActiveRecord::Base.connection.delete(delete_query)
|
128
|
+
end
|
126
129
|
end
|
127
130
|
|
128
131
|
@logger.info("Finished deleting from #{table}")
|
129
132
|
end
|
130
133
|
|
134
|
+
# @return [Class]
|
135
|
+
def unarchiver_class
|
136
|
+
RailsArchiver::Unarchiver
|
137
|
+
end
|
138
|
+
|
131
139
|
# @return [RailsArchiver::Unarchiver]
|
132
|
-
|
133
|
-
|
140
|
+
# @param crash_on_errors [Boolean]
|
141
|
+
def unarchiver(crash_on_errors: false)
|
142
|
+
unarchiver = unarchiver_class.new(@model,
|
143
|
+
logger: @logger,
|
144
|
+
crash_on_errors: crash_on_errors
|
145
|
+
)
|
134
146
|
unarchiver.transport = self.transport
|
135
147
|
unarchiver
|
136
148
|
end
|
@@ -11,18 +11,24 @@ module RailsArchiver
|
|
11
11
|
attr_accessor :errors, :transport
|
12
12
|
|
13
13
|
# @param model [ActiveRecord::Base]
|
14
|
-
# @param
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
def initialize(model,
|
14
|
+
# @param logger [Logger]
|
15
|
+
# @param new_copy [Boolean] if true, create all new objects instead of
|
16
|
+
# replacing existing ones.
|
17
|
+
# @param crash_on_errors [Boolean] if true, do not do any imports if any
|
18
|
+
# models' validations failed.
|
19
|
+
# @param transport [Symbol|RailsArchiver::Transport::Base]
|
20
|
+
def initialize(model,
|
21
|
+
transport: :in_memory,
|
22
|
+
logger: Logger.new(STDOUT),
|
23
|
+
new_copy: false, crash_on_errors: false)
|
21
24
|
@model = model
|
22
|
-
@logger =
|
23
|
-
@options =
|
25
|
+
@logger = logger
|
26
|
+
@options = {
|
27
|
+
new_copy: new_copy,
|
28
|
+
crash_on_errors: crash_on_errors
|
29
|
+
}
|
24
30
|
# Transport for downloading
|
25
|
-
self.transport = _get_transport(
|
31
|
+
self.transport = _get_transport(transport)
|
26
32
|
self.errors = []
|
27
33
|
end
|
28
34
|
|
@@ -58,7 +64,7 @@ module RailsArchiver
|
|
58
64
|
end
|
59
65
|
end
|
60
66
|
if @options[:crash_on_errors] && self.errors.any?
|
61
|
-
raise ImportError.new("Errors occurred during load
|
67
|
+
raise ImportError.new("Errors occurred during load: #{self.errors.join("\n")}")
|
62
68
|
end
|
63
69
|
end
|
64
70
|
|
@@ -114,13 +120,52 @@ module RailsArchiver
|
|
114
120
|
end
|
115
121
|
|
116
122
|
model
|
123
|
+
rescue => e
|
124
|
+
self.errors << "Error importing class #{klass.name}: #{e.message}"
|
117
125
|
end
|
118
126
|
|
119
127
|
private
|
120
128
|
|
129
|
+
# @param model [ActiveRecord::Base]
|
130
|
+
# @param field [String]
|
131
|
+
# @param attrs [Hash]
|
132
|
+
def assign_enum_value(model, field, attrs)
|
133
|
+
entries = model.class.public_send(field.pluralize)
|
134
|
+
is_integer = entries.values.first.is_a?(Integer)
|
135
|
+
value = attrs[field]
|
136
|
+
valid = if entries.keys.include?(value)
|
137
|
+
true
|
138
|
+
elsif is_integer && entries.values.include?(value.to_i)
|
139
|
+
true
|
140
|
+
else
|
141
|
+
entries.values.include?(value)
|
142
|
+
end
|
143
|
+
|
144
|
+
return if valid
|
145
|
+
|
146
|
+
@logger.warn("Invalid value for #{field}: #{attrs[field]}")
|
147
|
+
new_value = model.class.columns.find { |a| a.name == field }.default
|
148
|
+
new_value = new_value.to_i if is_integer && new_value
|
149
|
+
attrs[field] = new_value
|
150
|
+
end
|
151
|
+
|
121
152
|
# @param model [ActiveRecord::Base]
|
122
153
|
# @param attrs [Hash]
|
123
154
|
def _assign_attributes(model, attrs)
|
155
|
+
# Handle integer enum values that were turned into strings
|
156
|
+
if model.class.respond_to?(:attribute_types)
|
157
|
+
enums = model.class.attribute_types.select { |_, v| v.is_a?(ActiveRecord::Enum::EnumType) }
|
158
|
+
enums.keys.each do |field|
|
159
|
+
assign_enum_value(model, field, attrs)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
model.class.reflect_on_all_associations(:belongs_to).each do |assoc|
|
163
|
+
if attrs[assoc.foreign_key].present?
|
164
|
+
record = assoc.klass.find_by(assoc.association_primary_key => attrs[assoc.foreign_key])
|
165
|
+
attrs[assoc.name] = record if record
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
124
169
|
if model.method(:attributes=).arity == 1
|
125
170
|
model.attributes = attrs
|
126
171
|
else
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsArchiver
|
4
|
+
module Utils
|
5
|
+
# Utility module to retry a block of code in case of a deadlock.
|
6
|
+
module DeadlockRetry
|
7
|
+
class << self
|
8
|
+
|
9
|
+
RETRY_COUNT = 2
|
10
|
+
|
11
|
+
def wrap(&block)
|
12
|
+
count = RETRY_COUNT
|
13
|
+
begin
|
14
|
+
ActiveRecord::Base.transaction(&block)
|
15
|
+
rescue ActiveRecord::Deadlocked, ActiveRecord::LockWaitTimeout => e
|
16
|
+
raise if count <= 0
|
17
|
+
|
18
|
+
Rails.logger.error("Error: #{e.message}. Retrying. #{count} attempts remaining")
|
19
|
+
count -= 1
|
20
|
+
|
21
|
+
sleep(Random.rand(5.0) + 0.5)
|
22
|
+
|
23
|
+
retry
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/rails-archiver.gemspec
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'rails-archiver'
|
3
3
|
s.require_paths = %w(. lib lib/rails-archiver)
|
4
|
-
s.version = '0.
|
5
|
-
s.date = '
|
4
|
+
s.version = '0.3.0'
|
5
|
+
s.date = '2025-05-22'
|
6
6
|
s.summary = 'Fully archive a Rails model'
|
7
7
|
s.description = <<-EOF
|
8
8
|
EOF
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails-archiver
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Orner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-05-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -44,6 +44,8 @@ executables: []
|
|
44
44
|
extensions: []
|
45
45
|
extra_rdoc_files: []
|
46
46
|
files:
|
47
|
+
- ".gitignore"
|
48
|
+
- CHANGELOG.md
|
47
49
|
- README.md
|
48
50
|
- lib/rails-archiver.rb
|
49
51
|
- lib/rails-archiver/archiver.rb
|
@@ -51,6 +53,7 @@ files:
|
|
51
53
|
- lib/rails-archiver/transport/in_memory.rb
|
52
54
|
- lib/rails-archiver/transport/s3.rb
|
53
55
|
- lib/rails-archiver/unarchiver.rb
|
56
|
+
- lib/rails-archiver/utils/deadlock_retry.rb
|
54
57
|
- rails-archiver.gemspec
|
55
58
|
homepage: https://github.com/dorner/rails-archiver
|
56
59
|
licenses:
|