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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8e28890f3f0aa049ddfd3d24e63157b586c45d648c7f1fd186b27b5f285e7198
4
- data.tar.gz: 52273e4374126d9ee717badf9f3e674774644447559101ac04ff20dcc50b8257
3
+ metadata.gz: 4952c4f26bb1327189da3753cbf44bf1fcd76f5477cc8e0160265d508b3f46db
4
+ data.tar.gz: 8e0930ef126fef1b8ff7e347a5ba17b95b0aad13eaabc4a1818f918c10edba3c
5
5
  SHA512:
6
- metadata.gz: e194db706bc79ddbadbc41328da3956756aeecb3f99feeb313e4bf19c6fe764f27b2f23ab39e7a83a5c905bcbf113103569a261c21a23b039a559669f0462903
7
- data.tar.gz: 96cdf5ff4e22b1186cac5810129a287fa576d828c8df2981e64ee050a39ce8d93b5651132c0e162700eda2939aa826924c0440ebaafde9c238eb63be6e151af3
6
+ metadata.gz: 9ea29d7cb8a0269f8a3bd956820d21f69bd33cff1cfc9ca3c8a3c3d7b35fabb0cb2b34362402ee4233f50dd11ea33f72f96f1d57ef3505834da837d793a158a0
7
+ data.tar.gz: b1dcefaf9e2b9e0b6c7caa491756a7876557f2dc0e6ce8bd95070a7f3120a7fa2717370c0dc6a8eda06c510251aeac0080976fd3be4c27e905c22513d11b52ad
data/Gemfile CHANGED
@@ -2,8 +2,5 @@ source "https://rubygems.org"
2
2
 
3
3
  git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
4
 
5
- gem "rails", "~> 6.1"
6
- gem "pg"
7
-
8
5
  # Specify your gem's dependencies in archive_hook.gemspec
9
6
  gemspec
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 "rake", "~> 10.0"
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
@@ -1,3 +1,3 @@
1
1
  module ArchiveHook
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.3"
3
3
  end
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
- table_name = scope.table_name
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
- ActiveRecord::Base.connection.execute(Arel.sql(restore_records_sql(model, ids)))
84
- ActiveRecord::Base.connection.execute(Arel.sql <<-SQL
85
- DELETE FROM #{table_name}_archive WHERE id IN (#{ids.join(', ')})
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
- table_name = model.table_name
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 #{table_name}_archive WHERE id IN (#{ids.join(', ')})
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
- base_scope = root.where("#{column} < ?", archive_date)
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
- execute("CREATE TABLE <%= archive_table_name %> (LIKE <%= table_name %>)")
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.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-08-28 00:00:00.000000000 Z
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: '10.0'
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: '10.0'
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.0.3.1
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: []