historiographer 4.4.2 → 4.4.4

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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/DEVELOPMENT.md +124 -0
  3. data/Gemfile +2 -0
  4. data/README.md +16 -1
  5. data/Rakefile +68 -0
  6. data/VERSION +1 -1
  7. data/bin/console +10 -0
  8. data/bin/setup +15 -0
  9. data/bin/test +5 -0
  10. data/bin/test-all +10 -0
  11. data/bin/test-rails +5 -0
  12. data/historiographer.gemspec +52 -14
  13. data/lib/historiographer/history.rb +72 -37
  14. data/lib/historiographer.rb +5 -0
  15. data/spec/combustion_helper.rb +34 -0
  16. data/spec/db/migrate/20250826000000_create_test_users.rb +8 -0
  17. data/spec/db/migrate/20250826000001_create_test_user_histories.rb +18 -0
  18. data/spec/db/migrate/20250826000002_create_test_websites.rb +9 -0
  19. data/spec/db/migrate/20250826000003_create_test_website_histories.rb +19 -0
  20. data/spec/db/migrate/20250827000000_create_templates.rb +9 -0
  21. data/spec/db/migrate/20250827000001_create_template_histories.rb +9 -0
  22. data/spec/db/migrate/20250827000002_create_websites.rb +9 -0
  23. data/spec/db/migrate/20250827000003_create_website_histories.rb +9 -0
  24. data/spec/db/migrate/20250827000004_create_template_files.rb +15 -0
  25. data/spec/db/migrate/20250827000005_create_template_file_histories.rb +9 -0
  26. data/spec/db/migrate/20250827000006_create_website_files.rb +15 -0
  27. data/spec/db/migrate/20250827000007_create_website_file_histories.rb +9 -0
  28. data/spec/db/migrate/20250827000008_create_code_files_view.rb +62 -0
  29. data/spec/db/schema.rb +170 -1
  30. data/spec/examples.txt +71 -0
  31. data/spec/historiographer_spec.rb +164 -0
  32. data/spec/integration/historiographer_safe_integration_spec.rb +154 -0
  33. data/spec/internal/app/models/application_record.rb +5 -0
  34. data/spec/internal/app/models/deploy.rb +5 -0
  35. data/spec/internal/app/models/user.rb +4 -0
  36. data/spec/internal/app/models/website.rb +5 -0
  37. data/spec/internal/app/models/website_history.rb +7 -0
  38. data/spec/internal/config/database.yml +9 -0
  39. data/spec/internal/config/routes.rb +2 -0
  40. data/spec/internal/db/schema.rb +48 -0
  41. data/spec/internal/log/development.log +0 -0
  42. data/spec/internal/log/test.log +1479 -0
  43. data/spec/models/code_file.rb +16 -0
  44. data/spec/models/template.rb +6 -0
  45. data/spec/models/template_file.rb +5 -0
  46. data/spec/models/template_file_history.rb +3 -0
  47. data/spec/models/template_history.rb +3 -0
  48. data/spec/models/test_user.rb +4 -0
  49. data/spec/models/test_user_history.rb +3 -0
  50. data/spec/models/test_website.rb +4 -0
  51. data/spec/models/test_website_history.rb +3 -0
  52. data/spec/models/website.rb +7 -0
  53. data/spec/models/website_file.rb +5 -0
  54. data/spec/models/website_file_history.rb +3 -0
  55. data/spec/models/website_history.rb +3 -0
  56. data/spec/rails_integration/historiographer_rails_integration_spec.rb +106 -0
  57. data/spec/view_backed_model_spec.rb +166 -0
  58. metadata +55 -13
  59. data/.document +0 -5
  60. data/.rspec +0 -1
  61. data/.ruby-version +0 -1
  62. data/.standalone_migrations +0 -6
  63. data/Gemfile.lock +0 -341
  64. data/historiographer-4.1.12.gem +0 -0
  65. data/historiographer-4.1.13.gem +0 -0
  66. data/historiographer-4.1.14.gem +0 -0
  67. data/historiographer-4.3.0.gem +0 -0
  68. data/spec/foreign_key_spec.rb +0 -189
@@ -0,0 +1,18 @@
1
+ class CreateTestUserHistories < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :test_user_histories do |t|
4
+ t.integer :test_user_id, null: false
5
+ t.string :name
6
+ t.timestamps
7
+ t.datetime :history_started_at, null: false
8
+ t.datetime :history_ended_at
9
+ t.integer :history_user_id
10
+ t.string :snapshot_id
11
+
12
+ t.index :test_user_id
13
+ t.index :history_started_at
14
+ t.index :history_ended_at
15
+ t.index :snapshot_id
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,9 @@
1
+ class CreateTestWebsites < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :test_websites do |t|
4
+ t.string :name
5
+ t.integer :user_id
6
+ t.timestamps
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ class CreateTestWebsiteHistories < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :test_website_histories do |t|
4
+ t.integer :test_website_id, null: false
5
+ t.string :name
6
+ t.integer :user_id
7
+ t.timestamps
8
+ t.datetime :history_started_at, null: false
9
+ t.datetime :history_ended_at
10
+ t.integer :history_user_id
11
+ t.string :snapshot_id
12
+
13
+ t.index :test_website_id
14
+ t.index :history_started_at
15
+ t.index :history_ended_at
16
+ t.index :snapshot_id
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ class CreateTemplates < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :templates do |t|
4
+ t.string :name, null: false
5
+ t.text :description
6
+ t.timestamps
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ require 'historiographer/history_migration'
2
+
3
+ class CreateTemplateHistories < ActiveRecord::Migration[7.0]
4
+ def change
5
+ create_table :template_histories do |t|
6
+ t.histories
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ class CreateWebsites < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :websites do |t|
4
+ t.string :domain, null: false
5
+ t.references :template, foreign_key: true
6
+ t.timestamps
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ require 'historiographer/history_migration'
2
+
3
+ class CreateWebsiteHistories < ActiveRecord::Migration[7.0]
4
+ def change
5
+ create_table :website_histories do |t|
6
+ t.histories
7
+ end
8
+ end
9
+ end
@@ -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,9 @@
1
+ require 'historiographer/history_migration'
2
+
3
+ class CreateTemplateFileHistories < ActiveRecord::Migration[7.0]
4
+ def change
5
+ create_table :template_file_histories do |t|
6
+ t.histories
7
+ end
8
+ end
9
+ 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,9 @@
1
+ require 'historiographer/history_migration'
2
+
3
+ class CreateWebsiteFileHistories < ActiveRecord::Migration[7.0]
4
+ def change
5
+ create_table :website_file_histories do |t|
6
+ t.histories
7
+ end
8
+ end
9
+ 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: 2025_08_25_000000) do
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_25_000000) 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"
@@ -345,6 +405,50 @@ ActiveRecord::Schema[7.1].define(version: 2025_08_25_000000) do
345
405
  t.index ["test_category_id"], name: "index_test_category_histories_on_test_category_id"
346
406
  end
347
407
 
408
+ create_table "test_user_histories", force: :cascade do |t|
409
+ t.integer "test_user_id", null: false
410
+ t.string "name"
411
+ t.datetime "created_at", null: false
412
+ t.datetime "updated_at", null: false
413
+ t.datetime "history_started_at", null: false
414
+ t.datetime "history_ended_at"
415
+ t.integer "history_user_id"
416
+ t.string "snapshot_id"
417
+ t.index ["history_ended_at"], name: "index_test_user_histories_on_history_ended_at"
418
+ t.index ["history_started_at"], name: "index_test_user_histories_on_history_started_at"
419
+ t.index ["snapshot_id"], name: "index_test_user_histories_on_snapshot_id"
420
+ t.index ["test_user_id"], name: "index_test_user_histories_on_test_user_id"
421
+ end
422
+
423
+ create_table "test_users", force: :cascade do |t|
424
+ t.string "name"
425
+ t.datetime "created_at", null: false
426
+ t.datetime "updated_at", null: false
427
+ end
428
+
429
+ create_table "test_website_histories", force: :cascade do |t|
430
+ t.integer "test_website_id", null: false
431
+ t.string "name"
432
+ t.integer "user_id"
433
+ t.datetime "created_at", null: false
434
+ t.datetime "updated_at", null: false
435
+ t.datetime "history_started_at", null: false
436
+ t.datetime "history_ended_at"
437
+ t.integer "history_user_id"
438
+ t.string "snapshot_id"
439
+ t.index ["history_ended_at"], name: "index_test_website_histories_on_history_ended_at"
440
+ t.index ["history_started_at"], name: "index_test_website_histories_on_history_started_at"
441
+ t.index ["snapshot_id"], name: "index_test_website_histories_on_snapshot_id"
442
+ t.index ["test_website_id"], name: "index_test_website_histories_on_test_website_id"
443
+ end
444
+
445
+ create_table "test_websites", force: :cascade do |t|
446
+ t.string "name"
447
+ t.integer "user_id"
448
+ t.datetime "created_at", null: false
449
+ t.datetime "updated_at", null: false
450
+ end
451
+
348
452
  create_table "thing_with_compound_index_histories", force: :cascade do |t|
349
453
  t.integer "thing_with_compound_index_id", null: false
350
454
  t.string "key"
@@ -375,4 +479,69 @@ ActiveRecord::Schema[7.1].define(version: 2025_08_25_000000) do
375
479
  t.string "name"
376
480
  end
377
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"
378
547
  end
data/spec/examples.txt ADDED
@@ -0,0 +1,71 @@
1
+ example_id | status | run_time |
2
+ ------------------------------------------------------------------ | ------ | --------------- |
3
+ ./spec/historiographer_spec.rb[1:1:1] | passed | 0.06528 seconds |
4
+ ./spec/historiographer_spec.rb[1:1:2] | passed | 0.06876 seconds |
5
+ ./spec/historiographer_spec.rb[1:1:3] | passed | 0.06676 seconds |
6
+ ./spec/historiographer_spec.rb[1:2:1] | passed | 0.07069 seconds |
7
+ ./spec/historiographer_spec.rb[1:2:2] | passed | 0.05746 seconds |
8
+ ./spec/historiographer_spec.rb[1:2:3:1:1] | passed | 0.09942 seconds |
9
+ ./spec/historiographer_spec.rb[1:2:3:1:2] | passed | 0.09532 seconds |
10
+ ./spec/historiographer_spec.rb[1:2:3:2:1] | passed | 0.0666 seconds |
11
+ ./spec/historiographer_spec.rb[1:2:3:2:2] | passed | 0.09223 seconds |
12
+ ./spec/historiographer_spec.rb[1:2:3:2:3] | passed | 0.16142 seconds |
13
+ ./spec/historiographer_spec.rb[1:2:3:3:1] | passed | 0.09003 seconds |
14
+ ./spec/historiographer_spec.rb[1:2:3:3:2] | passed | 0.0727 seconds |
15
+ ./spec/historiographer_spec.rb[1:2:4:1] | passed | 0.07277 seconds |
16
+ ./spec/historiographer_spec.rb[1:2:4:2] | passed | 0.07295 seconds |
17
+ ./spec/historiographer_spec.rb[1:2:4:3] | passed | 0.06818 seconds |
18
+ ./spec/historiographer_spec.rb[1:2:5:1] | passed | 0.07794 seconds |
19
+ ./spec/historiographer_spec.rb[1:2:5:2] | passed | 0.06574 seconds |
20
+ ./spec/historiographer_spec.rb[1:2:5:3] | passed | 0.07246 seconds |
21
+ ./spec/historiographer_spec.rb[1:2:6] | passed | 0.05671 seconds |
22
+ ./spec/historiographer_spec.rb[1:2:7] | passed | 0.06192 seconds |
23
+ ./spec/historiographer_spec.rb[1:2:8] | passed | 0.06312 seconds |
24
+ ./spec/historiographer_spec.rb[1:3:1] | passed | 0.08252 seconds |
25
+ ./spec/historiographer_spec.rb[1:4:1] | passed | 0.08901 seconds |
26
+ ./spec/historiographer_spec.rb[1:5:1] | passed | 0.06204 seconds |
27
+ ./spec/historiographer_spec.rb[1:5:2] | passed | 0.06017 seconds |
28
+ ./spec/historiographer_spec.rb[1:6:1] | passed | 0.06542 seconds |
29
+ ./spec/historiographer_spec.rb[1:6:2] | passed | 0.07392 seconds |
30
+ ./spec/historiographer_spec.rb[1:7:1] | passed | 0.08007 seconds |
31
+ ./spec/historiographer_spec.rb[1:7:2] | passed | 0.06545 seconds |
32
+ ./spec/historiographer_spec.rb[1:7:3] | passed | 0.06212 seconds |
33
+ ./spec/historiographer_spec.rb[1:7:4] | passed | 0.06293 seconds |
34
+ ./spec/historiographer_spec.rb[1:7:5] | passed | 0.06288 seconds |
35
+ ./spec/historiographer_spec.rb[1:8:1] | passed | 0.062 seconds |
36
+ ./spec/historiographer_spec.rb[1:9:1] | passed | 0.09362 seconds |
37
+ ./spec/historiographer_spec.rb[1:10:1] | passed | 0.05997 seconds |
38
+ ./spec/historiographer_spec.rb[1:11:1] | passed | 0.07282 seconds |
39
+ ./spec/historiographer_spec.rb[1:11:2] | passed | 0.06606 seconds |
40
+ ./spec/historiographer_spec.rb[1:11:3] | passed | 0.06809 seconds |
41
+ ./spec/historiographer_spec.rb[1:11:4] | passed | 0.07018 seconds |
42
+ ./spec/historiographer_spec.rb[1:12:1] | passed | 0.08426 seconds |
43
+ ./spec/historiographer_spec.rb[1:12:2] | passed | 0.06852 seconds |
44
+ ./spec/historiographer_spec.rb[1:12:3] | passed | 0.10016 seconds |
45
+ ./spec/historiographer_spec.rb[1:12:4] | passed | 0.09182 seconds |
46
+ ./spec/historiographer_spec.rb[1:12:5] | passed | 0.08378 seconds |
47
+ ./spec/historiographer_spec.rb[1:12:6] | passed | 0.11921 seconds |
48
+ ./spec/historiographer_spec.rb[1:13:1] | passed | 0.11958 seconds |
49
+ ./spec/historiographer_spec.rb[1:14:1] | passed | 0.05717 seconds |
50
+ ./spec/historiographer_spec.rb[1:14:2] | passed | 0.07805 seconds |
51
+ ./spec/historiographer_spec.rb[1:14:3] | passed | 0.06426 seconds |
52
+ ./spec/historiographer_spec.rb[1:14:4] | passed | 0.06178 seconds |
53
+ ./spec/historiographer_spec.rb[1:15:1] | passed | 0.06755 seconds |
54
+ ./spec/historiographer_spec.rb[1:15:2] | passed | 0.08706 seconds |
55
+ ./spec/historiographer_spec.rb[1:16:1] | passed | 0.06589 seconds |
56
+ ./spec/historiographer_spec.rb[1:16:2] | passed | 0.04756 seconds |
57
+ ./spec/historiographer_spec.rb[1:16:3] | passed | 0.04635 seconds |
58
+ ./spec/historiographer_spec.rb[1:16:4] | passed | 0.07578 seconds |
59
+ ./spec/historiographer_spec.rb[1:17:1:1] | passed | 0.06365 seconds |
60
+ ./spec/historiographer_spec.rb[1:17:1:2] | passed | 0.10348 seconds |
61
+ ./spec/historiographer_spec.rb[1:17:2:1] | passed | 0.06393 seconds |
62
+ ./spec/integration/historiographer_safe_integration_spec.rb[1:1:1] | passed | 0.06664 seconds |
63
+ ./spec/integration/historiographer_safe_integration_spec.rb[1:1:2] | passed | 0.07357 seconds |
64
+ ./spec/integration/historiographer_safe_integration_spec.rb[1:1:3] | passed | 0.05953 seconds |
65
+ ./spec/view_backed_model_spec.rb[1:1:1] | passed | 0.06878 seconds |
66
+ ./spec/view_backed_model_spec.rb[1:1:2] | passed | 0.08949 seconds |
67
+ ./spec/view_backed_model_spec.rb[1:1:3:1] | passed | 0.11878 seconds |
68
+ ./spec/view_backed_model_spec.rb[1:1:3:2] | passed | 0.1122 seconds |
69
+ ./spec/view_backed_model_spec.rb[1:1:3:3] | passed | 0.10204 seconds |
70
+ ./spec/view_backed_model_spec.rb[1:1:3:4] | passed | 0.11072 seconds |
71
+ ./spec/view_backed_model_spec.rb[1:2:1] | passed | 0.07444 seconds |
@@ -1071,4 +1071,168 @@ describe Historiographer do
1071
1071
  expect { article.snapshot }.to_not raise_error
1072
1072
  end
1073
1073
  end
1074
+
1075
+ describe 'Foreign key handling' do
1076
+ before(:all) do
1077
+ # Ensure test tables exist
1078
+ unless ActiveRecord::Base.connection.table_exists?(:test_users)
1079
+ ActiveRecord::Base.connection.create_table :test_users do |t|
1080
+ t.string :name
1081
+ t.timestamps
1082
+ end
1083
+ end
1084
+
1085
+ unless ActiveRecord::Base.connection.table_exists?(:test_user_histories)
1086
+ ActiveRecord::Base.connection.create_table :test_user_histories do |t|
1087
+ t.integer :test_user_id, null: false
1088
+ t.string :name
1089
+ t.timestamps
1090
+ t.datetime :history_started_at, null: false
1091
+ t.datetime :history_ended_at
1092
+ t.integer :history_user_id
1093
+ t.string :snapshot_id
1094
+
1095
+ t.index :test_user_id
1096
+ t.index :history_started_at
1097
+ t.index :history_ended_at
1098
+ t.index :snapshot_id
1099
+ end
1100
+ end
1101
+
1102
+ unless ActiveRecord::Base.connection.table_exists?(:test_websites)
1103
+ ActiveRecord::Base.connection.create_table :test_websites do |t|
1104
+ t.string :name
1105
+ t.integer :user_id
1106
+ t.timestamps
1107
+ end
1108
+ end
1109
+
1110
+ unless ActiveRecord::Base.connection.table_exists?(:test_website_histories)
1111
+ ActiveRecord::Base.connection.create_table :test_website_histories do |t|
1112
+ t.integer :test_website_id, null: false
1113
+ t.string :name
1114
+ t.integer :user_id
1115
+ t.timestamps
1116
+ t.datetime :history_started_at, null: false
1117
+ t.datetime :history_ended_at
1118
+ t.integer :history_user_id
1119
+ t.string :snapshot_id
1120
+
1121
+ t.index :test_website_id
1122
+ t.index :history_started_at
1123
+ t.index :history_ended_at
1124
+ t.index :snapshot_id
1125
+ end
1126
+ end
1127
+ end
1128
+
1129
+ describe 'belongs_to associations on history models' do
1130
+ it 'does not raise error about wrong column when accessing belongs_to associations' do
1131
+ # This is the core issue: when a history model has a belongs_to association,
1132
+ # it should not use the foreign key as the primary key for lookups
1133
+
1134
+ # Create a user
1135
+ user = TestUser.create!(name: 'Test User', history_user_id: 1)
1136
+
1137
+ # Create a website belonging to the user
1138
+ website = TestWebsite.create!(
1139
+ name: 'Test Website',
1140
+ user_id: user.id,
1141
+ history_user_id: 1
1142
+ )
1143
+
1144
+ # Get the website history
1145
+ website_history = TestWebsiteHistory.last
1146
+
1147
+ # The history should have the correct user_id
1148
+ expect(website_history.user_id).to eq(user.id)
1149
+
1150
+ # The belongs_to association should work without errors
1151
+ # Previously this would fail with "column users.user_id does not exist"
1152
+ # because it was using primary_key: :user_id instead of the default :id
1153
+ expect { website_history.user }.not_to raise_error
1154
+ end
1155
+
1156
+ it 'allows direct creation of history records with foreign keys' do
1157
+ user = TestUser.create!(name: 'Another User', history_user_id: 1)
1158
+
1159
+ # Create history attributes like in the original error case
1160
+ attrs = {
1161
+ "name" => "test.example",
1162
+ "user_id" => user.id,
1163
+ "created_at" => Time.now,
1164
+ "updated_at" => Time.now,
1165
+ "test_website_id" => 100,
1166
+ "history_started_at" => Time.now,
1167
+ "history_user_id" => 1,
1168
+ "snapshot_id" => SecureRandom.uuid
1169
+ }
1170
+
1171
+ # This should not raise an error about test_users.user_id not existing
1172
+ # The original bug was that it would look for test_users.user_id instead of test_users.id
1173
+ expect { TestWebsiteHistory.create!(attrs) }.not_to raise_error
1174
+
1175
+ history = TestWebsiteHistory.last
1176
+ expect(history.user_id).to eq(user.id)
1177
+ end
1178
+ end
1179
+
1180
+ describe 'snapshot associations with history models' do
1181
+ it 'correctly filters associations by snapshot_id when using custom association methods' do
1182
+ # First create regular history records
1183
+ user = TestUser.create!(name: 'User One', history_user_id: 1)
1184
+ website = TestWebsite.create!(
1185
+ name: 'Website One',
1186
+ user_id: user.id,
1187
+ history_user_id: 1
1188
+ )
1189
+
1190
+ # Check that regular histories were created
1191
+ expect(TestUserHistory.count).to eq(1)
1192
+ expect(TestWebsiteHistory.count).to eq(1)
1193
+
1194
+ # Now create snapshot histories directly (simulating what snapshot would do)
1195
+ snapshot_id = SecureRandom.uuid
1196
+
1197
+ # Create user history with snapshot
1198
+ user_snapshot = TestUserHistory.create!(
1199
+ test_user_id: user.id,
1200
+ name: user.name,
1201
+ created_at: user.created_at,
1202
+ updated_at: user.updated_at,
1203
+ history_started_at: Time.now,
1204
+ history_user_id: 1,
1205
+ snapshot_id: snapshot_id
1206
+ )
1207
+
1208
+ # Create website history with snapshot
1209
+ website_snapshot = TestWebsiteHistory.create!(
1210
+ test_website_id: website.id,
1211
+ name: website.name,
1212
+ user_id: user.id,
1213
+ created_at: website.created_at,
1214
+ updated_at: website.updated_at,
1215
+ history_started_at: Time.now,
1216
+ history_user_id: 1,
1217
+ snapshot_id: snapshot_id
1218
+ )
1219
+
1220
+ # Now test that the association filtering works
1221
+ # The website history's user association should find the user history with the same snapshot_id
1222
+ user_from_association = website_snapshot.user
1223
+
1224
+ # Since user association points to history when snapshots are involved,
1225
+ # it should return the TestUserHistory with matching snapshot_id
1226
+ if user_from_association.is_a?(TestUserHistory)
1227
+ expect(user_from_association.snapshot_id).to eq(snapshot_id)
1228
+ expect(user_from_association.name).to eq('User One')
1229
+ else
1230
+ # If it returns the regular TestUser (non-history), that's also acceptable
1231
+ # as long as it doesn't error
1232
+ expect(user_from_association).to be_a(TestUser)
1233
+ expect(user_from_association.name).to eq('User One')
1234
+ end
1235
+ end
1236
+ end
1237
+ end
1074
1238
  end