historiographer 4.4.2 → 4.4.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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/DEVELOPMENT.md +124 -0
  3. data/Gemfile +2 -0
  4. data/Gemfile.lock +14 -0
  5. data/README.md +16 -1
  6. data/Rakefile +54 -0
  7. data/VERSION +1 -1
  8. data/bin/console +10 -0
  9. data/bin/setup +15 -0
  10. data/bin/test +5 -0
  11. data/bin/test-all +10 -0
  12. data/bin/test-rails +5 -0
  13. data/historiographer.gemspec +28 -3
  14. data/lib/historiographer/history.rb +72 -37
  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/schema.rb +45 -1
  21. data/spec/historiographer_spec.rb +164 -0
  22. data/spec/integration/historiographer_safe_integration_spec.rb +154 -0
  23. data/spec/internal/app/models/application_record.rb +5 -0
  24. data/spec/internal/app/models/deploy.rb +5 -0
  25. data/spec/internal/app/models/user.rb +4 -0
  26. data/spec/internal/app/models/website.rb +5 -0
  27. data/spec/internal/app/models/website_history.rb +7 -0
  28. data/spec/internal/config/database.yml +9 -0
  29. data/spec/internal/config/routes.rb +2 -0
  30. data/spec/internal/db/schema.rb +48 -0
  31. data/spec/models/test_user.rb +4 -0
  32. data/spec/models/test_user_history.rb +3 -0
  33. data/spec/models/test_website.rb +4 -0
  34. data/spec/models/test_website_history.rb +3 -0
  35. data/spec/rails_integration/historiographer_rails_integration_spec.rb +106 -0
  36. metadata +32 -3
  37. data/spec/foreign_key_spec.rb +0 -189
@@ -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
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_26_000003) do
14
14
  # These are extensions that must be enabled in order to support this database
15
15
  enable_extension "plpgsql"
16
16
 
@@ -345,6 +345,50 @@ ActiveRecord::Schema[7.1].define(version: 2025_08_25_000000) do
345
345
  t.index ["test_category_id"], name: "index_test_category_histories_on_test_category_id"
346
346
  end
347
347
 
348
+ create_table "test_user_histories", force: :cascade do |t|
349
+ t.integer "test_user_id", null: false
350
+ t.string "name"
351
+ t.datetime "created_at", null: false
352
+ t.datetime "updated_at", null: false
353
+ t.datetime "history_started_at", null: false
354
+ t.datetime "history_ended_at"
355
+ t.integer "history_user_id"
356
+ t.string "snapshot_id"
357
+ t.index ["history_ended_at"], name: "index_test_user_histories_on_history_ended_at"
358
+ t.index ["history_started_at"], name: "index_test_user_histories_on_history_started_at"
359
+ t.index ["snapshot_id"], name: "index_test_user_histories_on_snapshot_id"
360
+ t.index ["test_user_id"], name: "index_test_user_histories_on_test_user_id"
361
+ end
362
+
363
+ create_table "test_users", force: :cascade do |t|
364
+ t.string "name"
365
+ t.datetime "created_at", null: false
366
+ t.datetime "updated_at", null: false
367
+ end
368
+
369
+ create_table "test_website_histories", force: :cascade do |t|
370
+ t.integer "test_website_id", null: false
371
+ t.string "name"
372
+ t.integer "user_id"
373
+ t.datetime "created_at", null: false
374
+ t.datetime "updated_at", null: false
375
+ t.datetime "history_started_at", null: false
376
+ t.datetime "history_ended_at"
377
+ t.integer "history_user_id"
378
+ t.string "snapshot_id"
379
+ t.index ["history_ended_at"], name: "index_test_website_histories_on_history_ended_at"
380
+ t.index ["history_started_at"], name: "index_test_website_histories_on_history_started_at"
381
+ t.index ["snapshot_id"], name: "index_test_website_histories_on_snapshot_id"
382
+ t.index ["test_website_id"], name: "index_test_website_histories_on_test_website_id"
383
+ end
384
+
385
+ create_table "test_websites", force: :cascade do |t|
386
+ t.string "name"
387
+ t.integer "user_id"
388
+ t.datetime "created_at", null: false
389
+ t.datetime "updated_at", null: false
390
+ end
391
+
348
392
  create_table "thing_with_compound_index_histories", force: :cascade do |t|
349
393
  t.integer "thing_with_compound_index_id", null: false
350
394
  t.string "key"
@@ -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
@@ -0,0 +1,154 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe 'Historiographer::Safe Integration' do
4
+ # This test reproduces the exact error from a real Rails app where:
5
+ # 1. WebsiteHistory is defined first and includes Historiographer::History
6
+ # 2. Website is defined later and includes Historiographer::Safe
7
+ # 3. The error occurs because foreign_class.constantize fails when Website isn't loaded yet
8
+
9
+ context 'when history class is loaded before the main model' do
10
+ before(:each) do
11
+ # Ensure clean state
12
+ Object.send(:remove_const, :RealAppWebsite) if defined?(RealAppWebsite)
13
+ Object.send(:remove_const, :RealAppWebsiteHistory) if defined?(RealAppWebsiteHistory)
14
+
15
+ # Create the tables
16
+ ActiveRecord::Base.connection.create_table :real_app_websites, force: true do |t|
17
+ t.string :name
18
+ t.integer :project_id
19
+ t.integer :user_id
20
+ t.integer :template_id
21
+ t.timestamps
22
+ end
23
+
24
+ ActiveRecord::Base.connection.create_table :real_app_website_histories, force: true do |t|
25
+ t.integer :real_app_website_id, null: false
26
+ t.string :name
27
+ t.integer :project_id
28
+ t.integer :user_id
29
+ t.integer :template_id
30
+ t.datetime :created_at, null: false
31
+ t.datetime :updated_at, null: false
32
+ t.datetime :history_started_at, null: false
33
+ t.datetime :history_ended_at
34
+ t.integer :history_user_id
35
+ t.string :snapshot_id
36
+ t.string :thread_id
37
+ end
38
+
39
+ ActiveRecord::Base.connection.add_index :real_app_website_histories, :real_app_website_id
40
+ ActiveRecord::Base.connection.add_index :real_app_website_histories, :history_started_at
41
+ ActiveRecord::Base.connection.add_index :real_app_website_histories, :history_ended_at
42
+ end
43
+
44
+ after(:each) do
45
+ # Clean up tables
46
+ ActiveRecord::Base.connection.drop_table :real_app_website_histories if ActiveRecord::Base.connection.table_exists?(:real_app_website_histories)
47
+ ActiveRecord::Base.connection.drop_table :real_app_websites if ActiveRecord::Base.connection.table_exists?(:real_app_websites)
48
+
49
+ # Clean up constants
50
+ Object.send(:remove_const, :RealAppWebsiteHistory) if defined?(RealAppWebsiteHistory)
51
+ Object.send(:remove_const, :RealAppWebsite) if defined?(RealAppWebsite)
52
+ end
53
+
54
+ it 'handles history class being defined before the main model exists' do
55
+ # This is the exact scenario from the error report:
56
+ # WebsiteHistory is loaded/required first (common in Rails autoloading)
57
+
58
+ expect {
59
+ class RealAppWebsiteHistory < ApplicationRecord
60
+ self.table_name = 'real_app_website_histories'
61
+ include Historiographer::History
62
+ end
63
+ }.not_to raise_error
64
+
65
+ # At this point, RealAppWebsite doesn't exist yet
66
+ # The history class should handle this gracefully
67
+ expect(RealAppWebsiteHistory.foreign_class).to be_nil
68
+
69
+ # Now define the main model (simulating Rails autoloading it later)
70
+ class RealAppWebsite < ApplicationRecord
71
+ self.table_name = 'real_app_websites'
72
+ include Historiographer::Safe
73
+ end
74
+
75
+ # After the main model is defined, foreign_class should resolve
76
+ expect(RealAppWebsiteHistory.foreign_class).to eq(RealAppWebsite)
77
+
78
+ # And all functionality should work
79
+ website = RealAppWebsite.create!(name: 'Test Site', history_user_id: 1)
80
+ expect(website.histories.count).to eq(1)
81
+
82
+ history = website.histories.first
83
+ expect(history).to be_a(RealAppWebsiteHistory)
84
+ expect(history.real_app_website_id).to eq(website.id)
85
+ expect(history.name).to eq('Test Site')
86
+ end
87
+
88
+ it 'allows history class to define associations even when parent model is not loaded' do
89
+ # Define a Deploy model for association testing
90
+ ActiveRecord::Base.connection.create_table :deploys, force: true do |t|
91
+ t.integer :real_app_website_history_id
92
+ t.string :status
93
+ t.timestamps
94
+ end
95
+
96
+ class Deploy < ApplicationRecord
97
+ self.table_name = 'deploys'
98
+ end
99
+
100
+ # Define history class with associations before main model exists
101
+ expect {
102
+ class RealAppWebsiteHistory < ApplicationRecord
103
+ self.table_name = 'real_app_website_histories'
104
+ include Historiographer::History
105
+
106
+ # This should work even though RealAppWebsite doesn't exist yet
107
+ has_many :deploys, foreign_key: :real_app_website_history_id, dependent: :destroy
108
+ end
109
+ }.not_to raise_error
110
+
111
+ # Now define the main model
112
+ class RealAppWebsite < ApplicationRecord
113
+ self.table_name = 'real_app_websites'
114
+ include Historiographer::Safe
115
+ end
116
+
117
+ # Test that associations work
118
+ website = RealAppWebsite.create!(name: 'Test Site', history_user_id: 1)
119
+ history = website.histories.first
120
+
121
+ deploy = Deploy.create!(real_app_website_history_id: history.id, status: 'pending')
122
+ expect(history.deploys).to include(deploy)
123
+
124
+ # Clean up
125
+ ActiveRecord::Base.connection.drop_table :deploys
126
+ Object.send(:remove_const, :Deploy)
127
+ end
128
+
129
+ it 'supports after_initialize Rails hook when available' do
130
+ # Simulate Rails being available with after_initialize
131
+ rails_app = double('Rails App')
132
+ config = double('Rails Config')
133
+ allow(config).to receive(:after_initialize).and_yield
134
+ allow(rails_app).to receive(:config).and_return(config)
135
+ allow(Rails).to receive(:application).and_return(rails_app)
136
+
137
+ # Define history class
138
+ class RealAppWebsiteHistory < ApplicationRecord
139
+ self.table_name = 'real_app_website_histories'
140
+ include Historiographer::History
141
+ end
142
+
143
+ # Define main model
144
+ class RealAppWebsite < ApplicationRecord
145
+ self.table_name = 'real_app_websites'
146
+ include Historiographer::Safe
147
+ end
148
+
149
+ # The after_initialize should have set up associations
150
+ expect(RealAppWebsiteHistory).to respond_to(:setup_history_associations)
151
+ expect { RealAppWebsiteHistory.setup_history_associations }.not_to raise_error
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ApplicationRecord < ActiveRecord::Base
4
+ self.abstract_class = true
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Deploy < ApplicationRecord
4
+ belongs_to :website_history
5
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class User < ApplicationRecord
4
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Website < ApplicationRecord
4
+ include Historiographer::Safe
5
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class WebsiteHistory < ApplicationRecord
4
+ include Historiographer::History
5
+
6
+ has_many :deploys
7
+ end
@@ -0,0 +1,9 @@
1
+ test:
2
+ adapter: postgresql
3
+ database: historiographer_combustion_test
4
+ username: <%= ENV['DB_USERNAME'] || 'postgres' %>
5
+ password: <%= ENV['DB_PASSWORD'] || '' %>
6
+ host: <%= ENV['DB_HOST'] || 'localhost' %>
7
+ port: <%= ENV['DB_PORT'] || 5432 %>
8
+ pool: 5
9
+ timeout: 5000
@@ -0,0 +1,2 @@
1
+ Rails.application.routes.draw do
2
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ ActiveRecord::Schema.define(version: 1) do
4
+ # Website table - the main model
5
+ create_table :websites, force: true do |t|
6
+ t.string :name
7
+ t.integer :project_id
8
+ t.integer :user_id
9
+ t.integer :template_id
10
+ t.timestamps
11
+ end
12
+
13
+ # Website history table
14
+ create_table :website_histories, force: true do |t|
15
+ t.integer :website_id, null: false
16
+ t.string :name
17
+ t.integer :project_id
18
+ t.integer :user_id
19
+ t.integer :template_id
20
+ t.datetime :created_at, null: false
21
+ t.datetime :updated_at, null: false
22
+ t.datetime :history_started_at, null: false
23
+ t.datetime :history_ended_at
24
+ t.integer :history_user_id
25
+ t.string :snapshot_id
26
+ t.string :thread_id
27
+ end
28
+
29
+ add_index :website_histories, :website_id
30
+ add_index :website_histories, :history_started_at
31
+ add_index :website_histories, :history_ended_at
32
+ add_index :website_histories, :history_user_id
33
+ add_index :website_histories, :snapshot_id
34
+ add_index :website_histories, [:thread_id], unique: true, name: 'index_website_histories_on_thread_id'
35
+
36
+ # Deploy table - to test associations on history models
37
+ create_table :deploys, force: true do |t|
38
+ t.integer :website_history_id
39
+ t.string :status
40
+ t.timestamps
41
+ end
42
+
43
+ # User table
44
+ create_table :users, force: true do |t|
45
+ t.string :name
46
+ t.timestamps
47
+ end
48
+ end
@@ -0,0 +1,4 @@
1
+ class TestUser < ApplicationRecord
2
+ include Historiographer
3
+ has_many :test_websites, foreign_key: 'user_id'
4
+ end
@@ -0,0 +1,3 @@
1
+ class TestUserHistory < ApplicationRecord
2
+ include Historiographer::History
3
+ end
@@ -0,0 +1,4 @@
1
+ class TestWebsite < ApplicationRecord
2
+ include Historiographer
3
+ belongs_to :user, class_name: 'TestUser', foreign_key: 'user_id', optional: true
4
+ end
@@ -0,0 +1,3 @@
1
+ class TestWebsiteHistory < ApplicationRecord
2
+ include Historiographer::History
3
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'combustion_helper'
4
+
5
+ RSpec.describe 'Historiographer Rails Integration', type: :model do
6
+ describe 'when WebsiteHistory is loaded before Website (Rails autoloading scenario)' do
7
+ it 'handles the load order gracefully' do
8
+ expect(defined?(WebsiteHistory)).to be_truthy
9
+ expect(WebsiteHistory.ancestors).to include(Historiographer::History)
10
+
11
+ expect(defined?(Website)).to be_truthy
12
+ expect(Website.ancestors).to include(Historiographer::Safe)
13
+
14
+ expect(WebsiteHistory.foreign_class).to eq(Website)
15
+
16
+ expect(WebsiteHistory).to respond_to(:setup_history_associations)
17
+ expect(WebsiteHistory).to respond_to(:original_class)
18
+
19
+ expect { WebsiteHistory.setup_history_associations }.not_to raise_error
20
+ end
21
+
22
+ it 'allows creating and querying history records' do
23
+ user = User.create!(name: 'Test User')
24
+
25
+ website = Website.create!(
26
+ name: 'Production Site',
27
+ project_id: 1,
28
+ user_id: user.id,
29
+ history_user_id: user.id
30
+ )
31
+
32
+ expect(website.histories.count).to eq(1)
33
+
34
+ history = website.histories.first
35
+ expect(history).to be_a(WebsiteHistory)
36
+ expect(history.website_id).to eq(website.id)
37
+ expect(history.name).to eq('Production Site')
38
+ expect(history.history_user_id).to eq(user.id)
39
+ expect(history.history_started_at).to be_present
40
+ expect(history.history_ended_at).to be_nil
41
+
42
+ website.update!(name: 'Updated Site', history_user_id: user.id)
43
+
44
+ expect(website.histories.count).to eq(2)
45
+
46
+ old_history = website.histories.where.not(history_ended_at: nil).first
47
+ expect(old_history.name).to eq('Production Site')
48
+ expect(old_history.history_ended_at).to be_present
49
+
50
+ current_history = website.histories.current.first
51
+ expect(current_history.name).to eq('Updated Site')
52
+ expect(current_history.history_ended_at).to be_nil
53
+ end
54
+
55
+ it 'supports associations on history models' do
56
+ website = Website.create!(name: 'Deploy Test Site', history_user_id: 1)
57
+ history = website.histories.first
58
+
59
+ deploy = Deploy.create!(
60
+ website_history_id: history.id,
61
+ status: 'pending'
62
+ )
63
+
64
+ expect(history.deploys).to include(deploy)
65
+ expect(deploy.website_history).to eq(history)
66
+
67
+ expect(history.deploys.where(status: 'pending').count).to eq(1)
68
+ end
69
+
70
+ it 'handles Safe mode without requiring history_user_id after initial creation' do
71
+ website = Website.create!(name: 'Safe Mode Test', history_user_id: 1)
72
+
73
+ expect { website.update!(name: 'Updated Safe Mode Test') }.not_to raise_error
74
+
75
+ expect(website.histories.count).to eq(2)
76
+
77
+ current_history = website.histories.current.first
78
+ expect(current_history.name).to eq('Updated Safe Mode Test')
79
+ expect(current_history.history_user_id).to eq(1)
80
+
81
+ website.update!(name: 'Another Update', history_user_id: nil)
82
+ expect(website.histories.count).to eq(3)
83
+ newest_history = website.histories.current.first
84
+ expect(newest_history.history_user_id).to be_nil
85
+ end
86
+
87
+ it 'properly sets up delegated methods on history instances' do
88
+ website = Website.create!(name: 'Method Delegation Test', history_user_id: 1)
89
+ history = website.histories.first
90
+
91
+ expect(history).to respond_to(:name)
92
+
93
+ expect(history.name).to eq('Method Delegation Test')
94
+ end
95
+ end
96
+
97
+ describe 'Rails after_initialize hook' do
98
+ it 'sets up associations after Rails initialization' do
99
+ expect(WebsiteHistory.reflect_on_association(:website)).to be_present
100
+ expect(WebsiteHistory.reflect_on_association(:deploys)).to be_present
101
+
102
+ website_association = WebsiteHistory.reflect_on_association(:website)
103
+ expect(website_association.class_name).to eq('Website')
104
+ end
105
+ end
106
+ end