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.
- checksums.yaml +4 -4
- data/DEVELOPMENT.md +124 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +14 -0
- data/README.md +16 -1
- data/Rakefile +54 -0
- data/VERSION +1 -1
- data/bin/console +10 -0
- data/bin/setup +15 -0
- data/bin/test +5 -0
- data/bin/test-all +10 -0
- data/bin/test-rails +5 -0
- data/historiographer.gemspec +28 -3
- data/lib/historiographer/history.rb +72 -37
- data/spec/combustion_helper.rb +34 -0
- data/spec/db/migrate/20250826000000_create_test_users.rb +8 -0
- data/spec/db/migrate/20250826000001_create_test_user_histories.rb +18 -0
- data/spec/db/migrate/20250826000002_create_test_websites.rb +9 -0
- data/spec/db/migrate/20250826000003_create_test_website_histories.rb +19 -0
- data/spec/db/schema.rb +45 -1
- data/spec/historiographer_spec.rb +164 -0
- data/spec/integration/historiographer_safe_integration_spec.rb +154 -0
- data/spec/internal/app/models/application_record.rb +5 -0
- data/spec/internal/app/models/deploy.rb +5 -0
- data/spec/internal/app/models/user.rb +4 -0
- data/spec/internal/app/models/website.rb +5 -0
- data/spec/internal/app/models/website_history.rb +7 -0
- data/spec/internal/config/database.yml +9 -0
- data/spec/internal/config/routes.rb +2 -0
- data/spec/internal/db/schema.rb +48 -0
- data/spec/models/test_user.rb +4 -0
- data/spec/models/test_user_history.rb +3 -0
- data/spec/models/test_website.rb +4 -0
- data/spec/models/test_website_history.rb +3 -0
- data/spec/rails_integration/historiographer_rails_integration_spec.rb +106 -0
- metadata +32 -3
- 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:
|
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,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,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,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
|