archive_hook 0.1.1 → 0.1.3
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/Gemfile +0 -3
- data/README.md +6 -0
- data/archive_hook.gemspec +5 -1
- data/lib/archive_hook/version.rb +1 -1
- data/lib/archive_hook.rb +37 -13
- data/lib/generators/archive_hook/templates/migration.rb +7 -1
- metadata +42 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4952c4f26bb1327189da3753cbf44bf1fcd76f5477cc8e0160265d508b3f46db
|
|
4
|
+
data.tar.gz: 8e0930ef126fef1b8ff7e347a5ba17b95b0aad13eaabc4a1818f918c10edba3c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9ea29d7cb8a0269f8a3bd956820d21f69bd33cff1cfc9ca3c8a3c3d7b35fabb0cb2b34362402ee4233f50dd11ea33f72f96f1d57ef3505834da837d793a158a0
|
|
7
|
+
data.tar.gz: b1dcefaf9e2b9e0b6c7caa491756a7876557f2dc0e6ce8bd95070a7f3120a7fa2717370c0dc6a8eda06c510251aeac0080976fd3be4c27e905c22513d11b52ad
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -48,6 +48,12 @@ end
|
|
|
48
48
|
Run the generator for the root model and for every model listed under a `children:` entry in
|
|
49
49
|
your dependency mapping (see below) — each one needs its own `_archive` table.
|
|
50
50
|
|
|
51
|
+
> **Postgres only (for now):** the generator's `CREATE TABLE ... (LIKE ...)` is Postgres syntax.
|
|
52
|
+
> On other databases, write the `_archive` table migration by hand instead — the archiving and
|
|
53
|
+
> restoring API itself (`.archive`, `.archive_scope`, `.restore_scope`) is plain SQL
|
|
54
|
+
> (`INSERT INTO ... SELECT`, `DELETE ... WHERE id IN (...)`) run through the ActiveRecord
|
|
55
|
+
> connection, so it isn't tied to any particular adapter.
|
|
56
|
+
|
|
51
57
|
`ArchiveHook` reads the columns off the model at call time, so the archive table just needs to
|
|
52
58
|
have matching column names — it doesn't need matching constraints, indexes, or an FK to the
|
|
53
59
|
original table.
|
data/archive_hook.gemspec
CHANGED
|
@@ -32,7 +32,11 @@ Gem::Specification.new do |spec|
|
|
|
32
32
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
33
33
|
spec.require_paths = ["lib"]
|
|
34
34
|
|
|
35
|
+
# Only ActiveRecord (+ Arel, bundled with it) is used at runtime; no need for full Rails.
|
|
36
|
+
spec.add_dependency "activerecord", ">= 5.2", "< 9"
|
|
37
|
+
|
|
35
38
|
spec.add_development_dependency "bundler", "~> 2.3"
|
|
36
|
-
spec.add_development_dependency "
|
|
39
|
+
spec.add_development_dependency "pg"
|
|
40
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
37
41
|
spec.add_development_dependency "rspec", "~> 3.0"
|
|
38
42
|
end
|
data/lib/archive_hook/version.rb
CHANGED
data/lib/archive_hook.rb
CHANGED
|
@@ -1,7 +1,29 @@
|
|
|
1
1
|
require "archive_hook/version"
|
|
2
2
|
|
|
3
3
|
module ArchiveHook
|
|
4
|
+
# Identifiers are interpolated straight into SQL, so anything that needs
|
|
5
|
+
# quoting -- a column called "default", "order" or "group", a table name that
|
|
6
|
+
# collides with a keyword -- has to be quoted or Postgres rejects the
|
|
7
|
+
# statement with a syntax error.
|
|
8
|
+
module Quoting
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def quote_columns(column_names)
|
|
12
|
+
column_names.map { |name| connection.quote_column_name(name) }.join(",")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def quote_table(name)
|
|
16
|
+
connection.quote_table_name(name)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def connection
|
|
20
|
+
ActiveRecord::Base.connection
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
4
24
|
class ScopeArchiver
|
|
25
|
+
include Quoting
|
|
26
|
+
|
|
5
27
|
def initialize(dependencies: {})
|
|
6
28
|
@dependencies = dependencies
|
|
7
29
|
end
|
|
@@ -37,23 +59,24 @@ module ArchiveHook
|
|
|
37
59
|
end
|
|
38
60
|
|
|
39
61
|
def archive_records_sql(scope)
|
|
40
|
-
|
|
41
|
-
attributes_list = scope.column_names.join(",")
|
|
62
|
+
attributes_list = quote_columns(scope.column_names)
|
|
42
63
|
<<-SQL
|
|
43
|
-
INSERT INTO #{table_name}_archive (#{attributes_list})
|
|
64
|
+
INSERT INTO #{quote_table("#{scope.table_name}_archive")} (#{attributes_list})
|
|
44
65
|
#{scope.select(attributes_list).to_sql}
|
|
45
66
|
SQL
|
|
46
67
|
end
|
|
47
68
|
end
|
|
48
69
|
|
|
49
70
|
class ScopeRestorer
|
|
71
|
+
include Quoting
|
|
72
|
+
|
|
50
73
|
def initialize(dependencies: {})
|
|
51
74
|
@dependencies = dependencies
|
|
52
75
|
end
|
|
53
76
|
|
|
54
77
|
def call(scope)
|
|
55
78
|
parent = scope.model
|
|
56
|
-
table_name = "#{scope.table_name}_archive as #{scope.table_name}"
|
|
79
|
+
table_name = "#{quote_table("#{scope.table_name}_archive")} as #{quote_table(scope.table_name)}"
|
|
57
80
|
parent_id_groups = scope.from(table_name).in_batches.map { |relation| relation.pluck(:id) }
|
|
58
81
|
parent_id_groups.each do |parent_ids|
|
|
59
82
|
restore_by_ids(parent, parent_ids)
|
|
@@ -78,22 +101,22 @@ module ArchiveHook
|
|
|
78
101
|
end
|
|
79
102
|
|
|
80
103
|
def restore_by_ids(model, ids)
|
|
81
|
-
table_name = model.table_name
|
|
82
104
|
ActiveRecord::Base.transaction do
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
DELETE FROM #{
|
|
105
|
+
connection.execute(Arel.sql(restore_records_sql(model, ids)))
|
|
106
|
+
connection.execute(Arel.sql <<-SQL
|
|
107
|
+
DELETE FROM #{quote_table("#{model.table_name}_archive")}
|
|
108
|
+
WHERE #{connection.quote_column_name(model.primary_key)} IN (#{ids.join(', ')})
|
|
86
109
|
SQL
|
|
87
110
|
)
|
|
88
111
|
end
|
|
89
112
|
end
|
|
90
113
|
|
|
91
114
|
def restore_records_sql(model, ids)
|
|
92
|
-
|
|
93
|
-
attributes_list = model.column_names.join(",")
|
|
115
|
+
attributes_list = quote_columns(model.column_names)
|
|
94
116
|
<<-SQL
|
|
95
|
-
INSERT INTO #{table_name} (#{attributes_list})
|
|
96
|
-
SELECT #{attributes_list} FROM #{
|
|
117
|
+
INSERT INTO #{quote_table(model.table_name)} (#{attributes_list})
|
|
118
|
+
SELECT #{attributes_list} FROM #{quote_table("#{model.table_name}_archive")}
|
|
119
|
+
WHERE #{connection.quote_column_name(model.primary_key)} IN (#{ids.join(', ')})
|
|
97
120
|
SQL
|
|
98
121
|
end
|
|
99
122
|
end
|
|
@@ -101,7 +124,8 @@ module ArchiveHook
|
|
|
101
124
|
class << self
|
|
102
125
|
def archive(root, archive_date, dependencies)
|
|
103
126
|
column = dependencies[root] && dependencies[root][:column] || :created_at
|
|
104
|
-
|
|
127
|
+
quoted_column = ActiveRecord::Base.connection.quote_column_name(column)
|
|
128
|
+
base_scope = root.where("#{quoted_column} < ?", archive_date)
|
|
105
129
|
ScopeArchiver.new(dependencies: dependencies).call(base_scope)
|
|
106
130
|
end
|
|
107
131
|
|
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
class Create<%= archive_table_name.camelize %> < ActiveRecord::Migration<%= migration_version %>
|
|
4
4
|
def up
|
|
5
|
-
|
|
5
|
+
# Quoted at run time so a mixed-case table name survives Postgres folding
|
|
6
|
+
# unquoted identifiers to lower case, and so a name that collides with a
|
|
7
|
+
# reserved word still parses.
|
|
8
|
+
execute(
|
|
9
|
+
"CREATE TABLE #{connection.quote_table_name('<%= archive_table_name %>')} " \
|
|
10
|
+
"(LIKE #{connection.quote_table_name('<%= table_name %>')})"
|
|
11
|
+
)
|
|
6
12
|
end
|
|
7
13
|
|
|
8
14
|
def down
|
metadata
CHANGED
|
@@ -1,15 +1,35 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: archive_hook
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- alvir
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-09-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: activerecord
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '5.2'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '9'
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '5.2'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '9'
|
|
13
33
|
- !ruby/object:Gem::Dependency
|
|
14
34
|
name: bundler
|
|
15
35
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -24,20 +44,34 @@ dependencies:
|
|
|
24
44
|
- - "~>"
|
|
25
45
|
- !ruby/object:Gem::Version
|
|
26
46
|
version: '2.3'
|
|
47
|
+
- !ruby/object:Gem::Dependency
|
|
48
|
+
name: pg
|
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
type: :development
|
|
55
|
+
prerelease: false
|
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
27
61
|
- !ruby/object:Gem::Dependency
|
|
28
62
|
name: rake
|
|
29
63
|
requirement: !ruby/object:Gem::Requirement
|
|
30
64
|
requirements:
|
|
31
65
|
- - "~>"
|
|
32
66
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '
|
|
67
|
+
version: '13.0'
|
|
34
68
|
type: :development
|
|
35
69
|
prerelease: false
|
|
36
70
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
71
|
requirements:
|
|
38
72
|
- - "~>"
|
|
39
73
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '
|
|
74
|
+
version: '13.0'
|
|
41
75
|
- !ruby/object:Gem::Dependency
|
|
42
76
|
name: rspec
|
|
43
77
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -83,7 +117,7 @@ licenses:
|
|
|
83
117
|
metadata:
|
|
84
118
|
homepage_uri: https://github.com/alvir/archive_hook
|
|
85
119
|
source_code_uri: https://github.com/alvir/archive_hook
|
|
86
|
-
post_install_message:
|
|
120
|
+
post_install_message:
|
|
87
121
|
rdoc_options: []
|
|
88
122
|
require_paths:
|
|
89
123
|
- lib
|
|
@@ -98,8 +132,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
98
132
|
- !ruby/object:Gem::Version
|
|
99
133
|
version: '0'
|
|
100
134
|
requirements: []
|
|
101
|
-
rubygems_version: 3.
|
|
102
|
-
signing_key:
|
|
135
|
+
rubygems_version: 3.4.10
|
|
136
|
+
signing_key:
|
|
103
137
|
specification_version: 4
|
|
104
138
|
summary: Archive ActiveRecord models (and their dependents) to parallel _archive tables
|
|
105
139
|
test_files: []
|