historiographer 4.4.3 → 4.4.5
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/README.md +54 -3
- data/Rakefile +14 -0
- data/VERSION +1 -1
- data/historiographer.gemspec +26 -13
- data/lib/historiographer/history.rb +4 -1
- data/lib/historiographer.rb +12 -7
- data/spec/db/migrate/20250827000000_create_templates.rb +9 -0
- data/spec/db/migrate/20250827000001_create_template_histories.rb +9 -0
- data/spec/db/migrate/20250827000002_create_websites.rb +9 -0
- data/spec/db/migrate/20250827000003_create_website_histories.rb +9 -0
- data/spec/db/migrate/20250827000004_create_template_files.rb +15 -0
- data/spec/db/migrate/20250827000005_create_template_file_histories.rb +9 -0
- data/spec/db/migrate/20250827000006_create_website_files.rb +15 -0
- data/spec/db/migrate/20250827000007_create_website_file_histories.rb +9 -0
- data/spec/db/migrate/20250827000008_create_code_files_view.rb +62 -0
- data/spec/db/schema.rb +126 -1
- data/spec/examples.txt +72 -0
- data/spec/historiographer_spec.rb +30 -0
- data/spec/internal/log/development.log +0 -0
- data/spec/internal/log/test.log +1479 -0
- data/spec/models/code_file.rb +16 -0
- data/spec/models/template.rb +6 -0
- data/spec/models/template_file.rb +5 -0
- data/spec/models/template_file_history.rb +3 -0
- data/spec/models/template_history.rb +3 -0
- data/spec/models/website.rb +7 -0
- data/spec/models/website_file.rb +5 -0
- data/spec/models/website_file_history.rb +3 -0
- data/spec/models/website_history.rb +3 -0
- data/spec/view_backed_model_spec.rb +166 -0
- metadata +24 -11
- data/.document +0 -5
- data/.rspec +0 -1
- data/.ruby-version +0 -1
- data/.standalone_migrations +0 -6
- data/Gemfile.lock +0 -355
- data/historiographer-4.1.12.gem +0 -0
- data/historiographer-4.1.13.gem +0 -0
- data/historiographer-4.1.14.gem +0 -0
- data/historiographer-4.3.0.gem +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 99a33a798a97f440f2d4c0d2d1973303a013e99bb5f8d3f566c51a0d9375961f
|
|
4
|
+
data.tar.gz: 56b51522b7f4b254b9ba1e4e1813aa449dec80579e9e79c8e4f446e629ce74c7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d27ffa3d2aaca0a91d9283c5f18701e7c576475d2619c569dd7edc21cdc8983c3376e823067ffb233a1f14ec4123823564b486c906da6b7860c285eb555ecc67
|
|
7
|
+
data.tar.gz: 7b1608e2c572ab02da5e2371679fe5facc463ac4d788e1e9e499a2078be074e3345758e9a9aed97931ade4c15f59fd220d2f8957e7d7101f44290217647a9ac2
|
data/README.md
CHANGED
|
@@ -12,7 +12,7 @@ The Audited gem has some serious flaws.
|
|
|
12
12
|
|
|
13
13
|
2. It doesn't provide the indexes you need from your primary tables
|
|
14
14
|
|
|
15
|
-
3. It doesn't
|
|
15
|
+
3. It doesn't provide out-of-the-box snapshots
|
|
16
16
|
|
|
17
17
|
## How does Historiographer solve these problems?
|
|
18
18
|
|
|
@@ -86,14 +86,17 @@ You can take a snapshot of a record and all its associated records:
|
|
|
86
86
|
|
|
87
87
|
```ruby
|
|
88
88
|
post = Post.find(1)
|
|
89
|
-
post.snapshot
|
|
89
|
+
post.snapshot
|
|
90
90
|
```
|
|
91
91
|
|
|
92
92
|
This will:
|
|
93
93
|
|
|
94
94
|
1. Create a history record for the post
|
|
95
|
-
2.
|
|
95
|
+
2. Recursively create history records for all associated records that also include Historiographer (comments, author, etc.)
|
|
96
96
|
3. Link these history records together with a shared `snapshot_id`
|
|
97
|
+
4. Skip any view-backed models (models without a primary key)
|
|
98
|
+
|
|
99
|
+
**Note:** The snapshot cascades to ALL associations where the associated model includes Historiographer. There is currently no built-in way to limit which associations get snapshotted. If you need finer control, you can either not include Historiographer on models you don't want snapshotted, or override the `snapshot` method.
|
|
97
100
|
|
|
98
101
|
You can retrieve the latest snapshot using:
|
|
99
102
|
|
|
@@ -130,6 +133,38 @@ This can be useful when:
|
|
|
130
133
|
- You're versioning training data for machine learning models
|
|
131
134
|
- You need to maintain immutable audit trails at specific checkpoints
|
|
132
135
|
|
|
136
|
+
## Safe and Silent Modes
|
|
137
|
+
|
|
138
|
+
Historiographer provides two additional modes for migrating existing models:
|
|
139
|
+
|
|
140
|
+
### Historiographer::Safe
|
|
141
|
+
|
|
142
|
+
Use `Historiographer::Safe` when migrating an existing model to Historiographer. Instead of raising an error when `history_user_id` is missing, it logs to Rollbar, allowing you to find all locations that need to be updated:
|
|
143
|
+
|
|
144
|
+
```ruby
|
|
145
|
+
class Post < ActiveRecord::Base
|
|
146
|
+
include Historiographer::Safe
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# This will create a history record and log to Rollbar (instead of raising an error)
|
|
150
|
+
Post.create(title: "My Post")
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Historiographer::Silent
|
|
154
|
+
|
|
155
|
+
Use `Historiographer::Silent` when you want to allow missing `history_user_id` without any errors or logging:
|
|
156
|
+
|
|
157
|
+
```ruby
|
|
158
|
+
class Post < ActiveRecord::Base
|
|
159
|
+
include Historiographer::Silent
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# This will create a history record silently without history_user_id
|
|
163
|
+
Post.create(title: "My Post")
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Note:** Both Safe and Silent modes are intended for migration purposes, not as long-term solutions.
|
|
167
|
+
|
|
133
168
|
## Namespaced Models
|
|
134
169
|
|
|
135
170
|
When using namespaced models, Rails handles foreign key naming differently than with non-namespaced models. For example, if you have a model namespaced like this:
|
|
@@ -296,6 +331,7 @@ You should also make a `PostHistory` class if you're going to query `PostHistory
|
|
|
296
331
|
```ruby
|
|
297
332
|
class PostHistory < ActiveRecord::Base
|
|
298
333
|
self.table_name = "post_histories"
|
|
334
|
+
include Historiographer::History
|
|
299
335
|
end
|
|
300
336
|
```
|
|
301
337
|
|
|
@@ -323,6 +359,21 @@ Post.last.destroy!(history_user_id: current_user.id)
|
|
|
323
359
|
Post.destroy_all(history_user_id: current_user.id)
|
|
324
360
|
```
|
|
325
361
|
|
|
362
|
+
### Skipping History
|
|
363
|
+
|
|
364
|
+
If you need to save a record without creating a history entry:
|
|
365
|
+
|
|
366
|
+
```ruby
|
|
367
|
+
post = Post.new(title: "My Post")
|
|
368
|
+
post.save_without_history # No history_user_id required
|
|
369
|
+
post.save_without_history! # Bang version
|
|
370
|
+
|
|
371
|
+
# For bulk operations:
|
|
372
|
+
Post.all.update_all_without_history(title: "New Title")
|
|
373
|
+
Post.all.delete_all_without_history
|
|
374
|
+
Post.all.destroy_all_without_history
|
|
375
|
+
```
|
|
376
|
+
|
|
326
377
|
The `histories` classes have a `current` method, which only finds current history records. These records will also be the same as the data in the primary table.
|
|
327
378
|
|
|
328
379
|
```ruby
|
data/Rakefile
CHANGED
|
@@ -22,6 +22,20 @@ Jeweler::Tasks.new do |gem|
|
|
|
22
22
|
gem.description = %Q{Creates separate tables for each history table}
|
|
23
23
|
gem.email = "brett.shollenberger@gmail.com"
|
|
24
24
|
gem.authors = ["brettshollenberger"]
|
|
25
|
+
|
|
26
|
+
# Use glob patterns to automatically include all relevant files
|
|
27
|
+
gem.files = `git ls-files`.split("\n").reject { |f|
|
|
28
|
+
f.match(/^(test|spec|features)\//) ||
|
|
29
|
+
f.match(/\.gem$/) ||
|
|
30
|
+
f.match(/^\./) ||
|
|
31
|
+
f == 'Gemfile.lock'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Include spec files for development/testing
|
|
35
|
+
gem.files += Dir.glob("spec/**/*")
|
|
36
|
+
|
|
37
|
+
# Ensure we have all the executables
|
|
38
|
+
gem.executables = Dir.glob("bin/*").map { |f| File.basename(f) }
|
|
25
39
|
end
|
|
26
40
|
Jeweler::RubygemsDotOrgTasks.new
|
|
27
41
|
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.4.
|
|
1
|
+
4.4.5
|
data/historiographer.gemspec
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
|
4
4
|
# -*- encoding: utf-8 -*-
|
|
5
|
-
# stub: historiographer 4.4.
|
|
5
|
+
# stub: historiographer 4.4.5 ruby lib
|
|
6
6
|
|
|
7
7
|
Gem::Specification.new do |s|
|
|
8
8
|
s.name = "historiographer".freeze
|
|
9
|
-
s.version = "4.4.
|
|
9
|
+
s.version = "4.4.5"
|
|
10
10
|
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
|
12
12
|
s.require_paths = ["lib".freeze]
|
|
13
13
|
s.authors = ["brettshollenberger".freeze]
|
|
14
|
-
s.date = "
|
|
14
|
+
s.date = "2026-01-14"
|
|
15
15
|
s.description = "Creates separate tables for each history table".freeze
|
|
16
16
|
s.email = "brett.shollenberger@gmail.com".freeze
|
|
17
17
|
s.executables = ["console".freeze, "setup".freeze, "test".freeze, "test-all".freeze, "test-rails".freeze]
|
|
@@ -20,13 +20,8 @@ Gem::Specification.new do |s|
|
|
|
20
20
|
"README.md"
|
|
21
21
|
]
|
|
22
22
|
s.files = [
|
|
23
|
-
".document",
|
|
24
|
-
".rspec",
|
|
25
|
-
".ruby-version",
|
|
26
|
-
".standalone_migrations",
|
|
27
23
|
"DEVELOPMENT.md",
|
|
28
24
|
"Gemfile",
|
|
29
|
-
"Gemfile.lock",
|
|
30
25
|
"Guardfile",
|
|
31
26
|
"LICENSE.txt",
|
|
32
27
|
"README.md",
|
|
@@ -37,10 +32,6 @@ Gem::Specification.new do |s|
|
|
|
37
32
|
"bin/test",
|
|
38
33
|
"bin/test-all",
|
|
39
34
|
"bin/test-rails",
|
|
40
|
-
"historiographer-4.1.12.gem",
|
|
41
|
-
"historiographer-4.1.13.gem",
|
|
42
|
-
"historiographer-4.1.14.gem",
|
|
43
|
-
"historiographer-4.3.0.gem",
|
|
44
35
|
"historiographer.gemspec",
|
|
45
36
|
"init.rb",
|
|
46
37
|
"instructions/implementation.md",
|
|
@@ -83,7 +74,17 @@ Gem::Specification.new do |s|
|
|
|
83
74
|
"spec/db/migrate/20250826000001_create_test_user_histories.rb",
|
|
84
75
|
"spec/db/migrate/20250826000002_create_test_websites.rb",
|
|
85
76
|
"spec/db/migrate/20250826000003_create_test_website_histories.rb",
|
|
77
|
+
"spec/db/migrate/20250827000000_create_templates.rb",
|
|
78
|
+
"spec/db/migrate/20250827000001_create_template_histories.rb",
|
|
79
|
+
"spec/db/migrate/20250827000002_create_websites.rb",
|
|
80
|
+
"spec/db/migrate/20250827000003_create_website_histories.rb",
|
|
81
|
+
"spec/db/migrate/20250827000004_create_template_files.rb",
|
|
82
|
+
"spec/db/migrate/20250827000005_create_template_file_histories.rb",
|
|
83
|
+
"spec/db/migrate/20250827000006_create_website_files.rb",
|
|
84
|
+
"spec/db/migrate/20250827000007_create_website_file_histories.rb",
|
|
85
|
+
"spec/db/migrate/20250827000008_create_code_files_view.rb",
|
|
86
86
|
"spec/db/schema.rb",
|
|
87
|
+
"spec/examples.txt",
|
|
87
88
|
"spec/factories/post.rb",
|
|
88
89
|
"spec/historiographer_spec.rb",
|
|
89
90
|
"spec/integration/historiographer_safe_integration_spec.rb",
|
|
@@ -95,10 +96,13 @@ Gem::Specification.new do |s|
|
|
|
95
96
|
"spec/internal/config/database.yml",
|
|
96
97
|
"spec/internal/config/routes.rb",
|
|
97
98
|
"spec/internal/db/schema.rb",
|
|
99
|
+
"spec/internal/log/development.log",
|
|
100
|
+
"spec/internal/log/test.log",
|
|
98
101
|
"spec/models/application_record.rb",
|
|
99
102
|
"spec/models/author.rb",
|
|
100
103
|
"spec/models/author_history.rb",
|
|
101
104
|
"spec/models/byline.rb",
|
|
105
|
+
"spec/models/code_file.rb",
|
|
102
106
|
"spec/models/comment.rb",
|
|
103
107
|
"spec/models/comment_history.rb",
|
|
104
108
|
"spec/models/easy_ml/column.rb",
|
|
@@ -113,6 +117,10 @@ Gem::Specification.new do |s|
|
|
|
113
117
|
"spec/models/safe_post_history.rb",
|
|
114
118
|
"spec/models/silent_post.rb",
|
|
115
119
|
"spec/models/silent_post_history.rb",
|
|
120
|
+
"spec/models/template.rb",
|
|
121
|
+
"spec/models/template_file.rb",
|
|
122
|
+
"spec/models/template_file_history.rb",
|
|
123
|
+
"spec/models/template_history.rb",
|
|
116
124
|
"spec/models/test_article.rb",
|
|
117
125
|
"spec/models/test_article_history.rb",
|
|
118
126
|
"spec/models/test_category.rb",
|
|
@@ -125,8 +133,13 @@ Gem::Specification.new do |s|
|
|
|
125
133
|
"spec/models/thing_with_compound_index_history.rb",
|
|
126
134
|
"spec/models/thing_without_history.rb",
|
|
127
135
|
"spec/models/user.rb",
|
|
136
|
+
"spec/models/website.rb",
|
|
137
|
+
"spec/models/website_file.rb",
|
|
138
|
+
"spec/models/website_file_history.rb",
|
|
139
|
+
"spec/models/website_history.rb",
|
|
128
140
|
"spec/rails_integration/historiographer_rails_integration_spec.rb",
|
|
129
|
-
"spec/spec_helper.rb"
|
|
141
|
+
"spec/spec_helper.rb",
|
|
142
|
+
"spec/view_backed_model_spec.rb"
|
|
130
143
|
]
|
|
131
144
|
s.homepage = "http://github.com/brettshollenberger/historiographer".freeze
|
|
132
145
|
s.licenses = ["MIT".freeze]
|
|
@@ -109,8 +109,11 @@ module Historiographer
|
|
|
109
109
|
#
|
|
110
110
|
# Set up user association unless Silent module is included
|
|
111
111
|
# Defer this check until foreign_class is available
|
|
112
|
+
# Use optional: true because history_user_id validation is handled separately
|
|
113
|
+
# by Historiographer's validate_history_user_id_present method, and we need
|
|
114
|
+
# to allow updates without a user when using without_history_user_id blocks
|
|
112
115
|
unless base.foreign_class && base.foreign_class.ancestors.include?(Historiographer::Silent)
|
|
113
|
-
belongs_to :user, foreign_key: :history_user_id
|
|
116
|
+
belongs_to :user, foreign_key: :history_user_id, optional: true
|
|
114
117
|
end
|
|
115
118
|
|
|
116
119
|
# Add method_added hook to the original class when it's available
|
data/lib/historiographer.rb
CHANGED
|
@@ -188,7 +188,7 @@ module Historiographer
|
|
|
188
188
|
rescue NameError
|
|
189
189
|
# Get the base table name without _histories suffix
|
|
190
190
|
base_table = base.table_name.singularize.sub(/_histories$/, '')
|
|
191
|
-
|
|
191
|
+
|
|
192
192
|
history_class_initializer = Class.new(ActiveRecord::Base) do
|
|
193
193
|
self.table_name = "#{base_table}_histories"
|
|
194
194
|
end
|
|
@@ -204,7 +204,7 @@ module Historiographer
|
|
|
204
204
|
|
|
205
205
|
# Set the constant in the correct module
|
|
206
206
|
history_class = target_module.const_set(final_class_name, history_class_initializer)
|
|
207
|
-
|
|
207
|
+
|
|
208
208
|
# Now that the class is named, include the History module and extend class methods
|
|
209
209
|
history_class.send(:include, Historiographer::History)
|
|
210
210
|
end
|
|
@@ -279,7 +279,7 @@ module Historiographer
|
|
|
279
279
|
save!(*args, &block)
|
|
280
280
|
@no_history = false
|
|
281
281
|
end
|
|
282
|
-
|
|
282
|
+
|
|
283
283
|
def snapshot(tree = {}, snapshot_id = nil)
|
|
284
284
|
return if is_history_class?
|
|
285
285
|
|
|
@@ -296,7 +296,7 @@ module Historiographer
|
|
|
296
296
|
null_snapshot = history_class.where(foreign_key => attrs[primary_key], snapshot_id: nil).first
|
|
297
297
|
snapshot = nil
|
|
298
298
|
if null_snapshot.present?
|
|
299
|
-
null_snapshot.update(snapshot_id: snapshot_id)
|
|
299
|
+
null_snapshot.update!(snapshot_id: snapshot_id)
|
|
300
300
|
snapshot = null_snapshot
|
|
301
301
|
else
|
|
302
302
|
snapshot = record_history(snapshot_id: snapshot_id)
|
|
@@ -304,6 +304,11 @@ module Historiographer
|
|
|
304
304
|
|
|
305
305
|
# Recursively snapshot associations, avoiding infinite loops
|
|
306
306
|
self.class.reflect_on_all_associations.each do |association|
|
|
307
|
+
# Skip associations to models without primary keys (e.g., database views)
|
|
308
|
+
association_class = association.klass rescue nil
|
|
309
|
+
next if association_class.nil?
|
|
310
|
+
next if association_class.primary_key.nil?
|
|
311
|
+
|
|
307
312
|
associated_records = send(association.name)&.reload
|
|
308
313
|
if associated_records.respond_to?(:order)
|
|
309
314
|
associated_records = associated_records.order(id: :asc)
|
|
@@ -378,7 +383,7 @@ module Historiographer
|
|
|
378
383
|
|
|
379
384
|
if history_class.history_foreign_key.present? && history_class.present?
|
|
380
385
|
result = history_class.insert_all([attrs])
|
|
381
|
-
|
|
386
|
+
|
|
382
387
|
# Check if the insertion was successful
|
|
383
388
|
if result.rows.empty?
|
|
384
389
|
# insert_all returned empty rows, likely due to a duplicate/conflict
|
|
@@ -388,7 +393,7 @@ module Historiographer
|
|
|
388
393
|
foreign_key => attrs[foreign_key],
|
|
389
394
|
history_started_at: attrs['history_started_at']
|
|
390
395
|
).first
|
|
391
|
-
|
|
396
|
+
|
|
392
397
|
if existing_history
|
|
393
398
|
# A duplicate history already exists (race condition or retry)
|
|
394
399
|
# This is acceptable - return the existing history
|
|
@@ -399,7 +404,7 @@ module Historiographer
|
|
|
399
404
|
history_class.create!(attrs) # This will raise the correct error since it will fail the unique constraint
|
|
400
405
|
end
|
|
401
406
|
end
|
|
402
|
-
|
|
407
|
+
|
|
403
408
|
inserted_id = result.rows.first.first if history_class.primary_key == 'id'
|
|
404
409
|
instance = history_class.find(inserted_id)
|
|
405
410
|
current_history.update_columns(history_ended_at: now) if current_history.present?
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class CreateTemplateFiles < ActiveRecord::Migration[7.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :template_files do |t|
|
|
4
|
+
t.references :template, foreign_key: true, null: false
|
|
5
|
+
t.string :path, null: false
|
|
6
|
+
t.text :content
|
|
7
|
+
t.tsvector :content_tsv
|
|
8
|
+
t.string :shasum
|
|
9
|
+
t.integer :file_specification_id
|
|
10
|
+
t.timestamps
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
add_index :template_files, [:template_id, :path], unique: true
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class CreateWebsiteFiles < ActiveRecord::Migration[7.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :website_files do |t|
|
|
4
|
+
t.references :website, foreign_key: true, null: false
|
|
5
|
+
t.string :path, null: false
|
|
6
|
+
t.text :content
|
|
7
|
+
t.tsvector :content_tsv
|
|
8
|
+
t.string :shasum
|
|
9
|
+
t.integer :file_specification_id
|
|
10
|
+
t.timestamps
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
add_index :website_files, [:website_id, :path], unique: true
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
class CreateCodeFilesView < ActiveRecord::Migration[7.0]
|
|
2
|
+
def up
|
|
3
|
+
execute <<-SQL
|
|
4
|
+
CREATE OR REPLACE VIEW code_files AS
|
|
5
|
+
WITH merged_files AS (
|
|
6
|
+
-- Get all website files
|
|
7
|
+
SELECT
|
|
8
|
+
wf.website_id,
|
|
9
|
+
wf.path,
|
|
10
|
+
wf.content,
|
|
11
|
+
wf.content_tsv,
|
|
12
|
+
wf.shasum,
|
|
13
|
+
wf.file_specification_id,
|
|
14
|
+
wf.created_at,
|
|
15
|
+
wf.updated_at,
|
|
16
|
+
'WebsiteFile' AS source_type,
|
|
17
|
+
wf.id AS source_id
|
|
18
|
+
FROM website_files wf
|
|
19
|
+
|
|
20
|
+
UNION ALL
|
|
21
|
+
|
|
22
|
+
-- Get template files that don't have a matching website file
|
|
23
|
+
SELECT
|
|
24
|
+
w.id AS website_id,
|
|
25
|
+
tf.path,
|
|
26
|
+
tf.content,
|
|
27
|
+
tf.content_tsv,
|
|
28
|
+
tf.shasum,
|
|
29
|
+
tf.file_specification_id,
|
|
30
|
+
tf.created_at,
|
|
31
|
+
tf.updated_at,
|
|
32
|
+
'TemplateFile' AS source_type,
|
|
33
|
+
tf.id AS source_id
|
|
34
|
+
FROM template_files tf
|
|
35
|
+
INNER JOIN websites w ON w.template_id = tf.template_id
|
|
36
|
+
WHERE NOT EXISTS (
|
|
37
|
+
SELECT 1
|
|
38
|
+
FROM website_files wf2
|
|
39
|
+
WHERE wf2.website_id = w.id
|
|
40
|
+
AND wf2.path = tf.path
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
SELECT
|
|
44
|
+
website_id,
|
|
45
|
+
path,
|
|
46
|
+
content,
|
|
47
|
+
content_tsv,
|
|
48
|
+
shasum,
|
|
49
|
+
file_specification_id,
|
|
50
|
+
source_type,
|
|
51
|
+
source_id,
|
|
52
|
+
created_at,
|
|
53
|
+
updated_at
|
|
54
|
+
FROM merged_files
|
|
55
|
+
ORDER BY website_id, path;
|
|
56
|
+
SQL
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def down
|
|
60
|
+
execute "DROP VIEW IF EXISTS code_files;"
|
|
61
|
+
end
|
|
62
|
+
end
|
data/spec/db/schema.rb
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
#
|
|
11
11
|
# It's strongly recommended that you check this file into your version control system.
|
|
12
12
|
|
|
13
|
-
ActiveRecord::Schema[7.1].define(version:
|
|
13
|
+
ActiveRecord::Schema[7.1].define(version: 2025_08_27_000008) do
|
|
14
14
|
# These are extensions that must be enabled in order to support this database
|
|
15
15
|
enable_extension "plpgsql"
|
|
16
16
|
|
|
@@ -299,6 +299,66 @@ ActiveRecord::Schema[7.1].define(version: 2025_08_26_000003) do
|
|
|
299
299
|
t.index ["live_at"], name: "index_silent_posts_on_live_at"
|
|
300
300
|
end
|
|
301
301
|
|
|
302
|
+
create_table "template_file_histories", force: :cascade do |t|
|
|
303
|
+
t.integer "template_file_id", null: false
|
|
304
|
+
t.integer "template_id", null: false
|
|
305
|
+
t.string "path", null: false
|
|
306
|
+
t.text "content"
|
|
307
|
+
t.tsvector "content_tsv"
|
|
308
|
+
t.string "shasum"
|
|
309
|
+
t.integer "file_specification_id"
|
|
310
|
+
t.datetime "created_at", null: false
|
|
311
|
+
t.datetime "updated_at", null: false
|
|
312
|
+
t.datetime "history_started_at", null: false
|
|
313
|
+
t.datetime "history_ended_at"
|
|
314
|
+
t.integer "history_user_id"
|
|
315
|
+
t.string "snapshot_id"
|
|
316
|
+
t.index ["history_ended_at"], name: "index_template_file_histories_on_history_ended_at"
|
|
317
|
+
t.index ["history_started_at"], name: "index_template_file_histories_on_history_started_at"
|
|
318
|
+
t.index ["history_user_id"], name: "index_template_file_histories_on_history_user_id"
|
|
319
|
+
t.index ["snapshot_id"], name: "index_template_file_histories_on_snapshot_id"
|
|
320
|
+
t.index ["template_file_id"], name: "index_template_file_histories_on_template_file_id"
|
|
321
|
+
t.index ["template_id", "path"], name: "index_template_file_histories_on_template_id_and_path"
|
|
322
|
+
t.index ["template_id"], name: "index_template_file_histories_on_template_id"
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
create_table "template_files", force: :cascade do |t|
|
|
326
|
+
t.bigint "template_id", null: false
|
|
327
|
+
t.string "path", null: false
|
|
328
|
+
t.text "content"
|
|
329
|
+
t.tsvector "content_tsv"
|
|
330
|
+
t.string "shasum"
|
|
331
|
+
t.integer "file_specification_id"
|
|
332
|
+
t.datetime "created_at", null: false
|
|
333
|
+
t.datetime "updated_at", null: false
|
|
334
|
+
t.index ["template_id", "path"], name: "index_template_files_on_template_id_and_path", unique: true
|
|
335
|
+
t.index ["template_id"], name: "index_template_files_on_template_id"
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
create_table "template_histories", force: :cascade do |t|
|
|
339
|
+
t.integer "template_id", null: false
|
|
340
|
+
t.string "name", null: false
|
|
341
|
+
t.text "description"
|
|
342
|
+
t.datetime "created_at", null: false
|
|
343
|
+
t.datetime "updated_at", null: false
|
|
344
|
+
t.datetime "history_started_at", null: false
|
|
345
|
+
t.datetime "history_ended_at"
|
|
346
|
+
t.integer "history_user_id"
|
|
347
|
+
t.string "snapshot_id"
|
|
348
|
+
t.index ["history_ended_at"], name: "index_template_histories_on_history_ended_at"
|
|
349
|
+
t.index ["history_started_at"], name: "index_template_histories_on_history_started_at"
|
|
350
|
+
t.index ["history_user_id"], name: "index_template_histories_on_history_user_id"
|
|
351
|
+
t.index ["snapshot_id"], name: "index_template_histories_on_snapshot_id"
|
|
352
|
+
t.index ["template_id"], name: "index_template_histories_on_template_id"
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
create_table "templates", force: :cascade do |t|
|
|
356
|
+
t.string "name", null: false
|
|
357
|
+
t.text "description"
|
|
358
|
+
t.datetime "created_at", null: false
|
|
359
|
+
t.datetime "updated_at", null: false
|
|
360
|
+
end
|
|
361
|
+
|
|
302
362
|
create_table "test_article_histories", force: :cascade do |t|
|
|
303
363
|
t.integer "test_article_id", null: false
|
|
304
364
|
t.string "title"
|
|
@@ -419,4 +479,69 @@ ActiveRecord::Schema[7.1].define(version: 2025_08_26_000003) do
|
|
|
419
479
|
t.string "name"
|
|
420
480
|
end
|
|
421
481
|
|
|
482
|
+
create_table "website_file_histories", force: :cascade do |t|
|
|
483
|
+
t.integer "website_file_id", null: false
|
|
484
|
+
t.integer "website_id", null: false
|
|
485
|
+
t.string "path", null: false
|
|
486
|
+
t.text "content"
|
|
487
|
+
t.tsvector "content_tsv"
|
|
488
|
+
t.string "shasum"
|
|
489
|
+
t.integer "file_specification_id"
|
|
490
|
+
t.datetime "created_at", null: false
|
|
491
|
+
t.datetime "updated_at", null: false
|
|
492
|
+
t.datetime "history_started_at", null: false
|
|
493
|
+
t.datetime "history_ended_at"
|
|
494
|
+
t.integer "history_user_id"
|
|
495
|
+
t.string "snapshot_id"
|
|
496
|
+
t.index ["history_ended_at"], name: "index_website_file_histories_on_history_ended_at"
|
|
497
|
+
t.index ["history_started_at"], name: "index_website_file_histories_on_history_started_at"
|
|
498
|
+
t.index ["history_user_id"], name: "index_website_file_histories_on_history_user_id"
|
|
499
|
+
t.index ["snapshot_id"], name: "index_website_file_histories_on_snapshot_id"
|
|
500
|
+
t.index ["website_file_id"], name: "index_website_file_histories_on_website_file_id"
|
|
501
|
+
t.index ["website_id", "path"], name: "index_website_file_histories_on_website_id_and_path"
|
|
502
|
+
t.index ["website_id"], name: "index_website_file_histories_on_website_id"
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
create_table "website_files", force: :cascade do |t|
|
|
506
|
+
t.bigint "website_id", null: false
|
|
507
|
+
t.string "path", null: false
|
|
508
|
+
t.text "content"
|
|
509
|
+
t.tsvector "content_tsv"
|
|
510
|
+
t.string "shasum"
|
|
511
|
+
t.integer "file_specification_id"
|
|
512
|
+
t.datetime "created_at", null: false
|
|
513
|
+
t.datetime "updated_at", null: false
|
|
514
|
+
t.index ["website_id", "path"], name: "index_website_files_on_website_id_and_path", unique: true
|
|
515
|
+
t.index ["website_id"], name: "index_website_files_on_website_id"
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
create_table "website_histories", force: :cascade do |t|
|
|
519
|
+
t.integer "website_id", null: false
|
|
520
|
+
t.string "domain", null: false
|
|
521
|
+
t.integer "template_id"
|
|
522
|
+
t.datetime "created_at", null: false
|
|
523
|
+
t.datetime "updated_at", null: false
|
|
524
|
+
t.datetime "history_started_at", null: false
|
|
525
|
+
t.datetime "history_ended_at"
|
|
526
|
+
t.integer "history_user_id"
|
|
527
|
+
t.string "snapshot_id"
|
|
528
|
+
t.index ["history_ended_at"], name: "index_website_histories_on_history_ended_at"
|
|
529
|
+
t.index ["history_started_at"], name: "index_website_histories_on_history_started_at"
|
|
530
|
+
t.index ["history_user_id"], name: "index_website_histories_on_history_user_id"
|
|
531
|
+
t.index ["snapshot_id"], name: "index_website_histories_on_snapshot_id"
|
|
532
|
+
t.index ["template_id"], name: "index_website_histories_on_template_id"
|
|
533
|
+
t.index ["website_id"], name: "index_website_histories_on_website_id"
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
create_table "websites", force: :cascade do |t|
|
|
537
|
+
t.string "domain", null: false
|
|
538
|
+
t.bigint "template_id"
|
|
539
|
+
t.datetime "created_at", null: false
|
|
540
|
+
t.datetime "updated_at", null: false
|
|
541
|
+
t.index ["template_id"], name: "index_websites_on_template_id"
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
add_foreign_key "template_files", "templates"
|
|
545
|
+
add_foreign_key "website_files", "websites"
|
|
546
|
+
add_foreign_key "websites", "templates"
|
|
422
547
|
end
|