historiographer 4.4.1 → 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 +38 -4
- data/lib/historiographer/history.rb +193 -60
- data/spec/combustion_helper.rb +34 -0
- data/spec/db/migrate/20250823000000_create_easy_ml_columns.rb +26 -0
- data/spec/db/migrate/20250824000000_create_test_articles.rb +26 -0
- data/spec/db/migrate/20250824000001_create_test_categories.rb +26 -0
- data/spec/db/migrate/20250825000000_create_bylines.rb +11 -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 +110 -40
- data/spec/historiographer_spec.rb +319 -1
- 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/author.rb +1 -0
- data/spec/models/byline.rb +4 -0
- data/spec/models/post.rb +2 -0
- data/spec/models/test_article.rb +4 -0
- data/spec/models/test_article_history.rb +3 -0
- data/spec/models/test_category.rb +4 -0
- data/spec/models/test_category_history.rb +3 -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
- data/spec/spec_helper.rb +2 -3
- metadata +42 -4
- data/spec/foreign_key_spec.rb +0 -189
@@ -78,11 +78,24 @@ module Historiographer
|
|
78
78
|
# "RetailerProductHistory."
|
79
79
|
#
|
80
80
|
foreign_class_name = base.name.gsub(/History$/) {} # e.g. "RetailerProductHistory" => "RetailerProduct"
|
81
|
-
foreign_class = foreign_class_name.constantize
|
82
81
|
association_name = foreign_class_name.split("::").last.underscore.to_sym # e.g. "RetailerProduct" => :retailer_product
|
83
82
|
|
84
|
-
#
|
85
|
-
|
83
|
+
# Defer foreign class resolution to avoid load order issues
|
84
|
+
base.define_singleton_method :foreign_class do
|
85
|
+
return class_variable_get(:@@foreign_class) if class_variable_defined?(:@@foreign_class)
|
86
|
+
begin
|
87
|
+
foreign_class = foreign_class_name.constantize
|
88
|
+
class_variable_set(:@@foreign_class, foreign_class)
|
89
|
+
foreign_class
|
90
|
+
rescue NameError => e
|
91
|
+
# If the class isn't loaded yet, return nil and it will be retried later
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Store the foreign class name for later use
|
97
|
+
class_variable_set(:@@foreign_class_name, foreign_class_name)
|
98
|
+
class_variable_set(:@@association_name, association_name)
|
86
99
|
|
87
100
|
#
|
88
101
|
# A History class will be linked to the user
|
@@ -94,12 +107,20 @@ module Historiographer
|
|
94
107
|
#
|
95
108
|
# To use histories, a user class must be defined.
|
96
109
|
#
|
97
|
-
unless
|
110
|
+
# Set up user association unless Silent module is included
|
111
|
+
# Defer this check until foreign_class is available
|
112
|
+
unless base.foreign_class && base.foreign_class.ancestors.include?(Historiographer::Silent)
|
98
113
|
belongs_to :user, foreign_key: :history_user_id
|
99
114
|
end
|
100
115
|
|
101
|
-
# Add method_added hook to the original class
|
102
|
-
|
116
|
+
# Add method_added hook to the original class when it's available
|
117
|
+
# This needs to be deferred until the foreign class is loaded
|
118
|
+
base.define_singleton_method :setup_method_delegation do
|
119
|
+
return unless foreign_class
|
120
|
+
return if class_variable_defined?(:@@method_delegation_setup) && class_variable_get(:@@method_delegation_setup)
|
121
|
+
class_variable_set(:@@method_delegation_setup, true)
|
122
|
+
|
123
|
+
foreign_class.singleton_class.class_eval do
|
103
124
|
# Keep track of original method_added if it exists
|
104
125
|
if method_defined?(:method_added)
|
105
126
|
alias_method :original_method_added, :method_added
|
@@ -120,9 +141,9 @@ module Historiographer
|
|
120
141
|
return unless method_obj.owner == self
|
121
142
|
|
122
143
|
# Skip if we've already defined this method in the history class
|
123
|
-
return if
|
144
|
+
return if self.history_class.method_defined?(method_name)
|
124
145
|
|
125
|
-
|
146
|
+
self.history_class.class_eval do
|
126
147
|
define_method(method_name) do |*args, **kwargs, &block|
|
127
148
|
forward_method(method_name, *args, **kwargs, &block)
|
128
149
|
end
|
@@ -133,16 +154,30 @@ module Historiographer
|
|
133
154
|
end
|
134
155
|
end
|
135
156
|
|
136
|
-
|
137
|
-
|
138
|
-
|
157
|
+
begin
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Try to set up method delegation if foreign class is available
|
162
|
+
base.setup_method_delegation if base.foreign_class
|
163
|
+
|
164
|
+
# Also delegate existing methods from the foreign class
|
165
|
+
if base.foreign_class
|
166
|
+
begin
|
167
|
+
(base.foreign_class.columns.map(&:name) - ["id"]).each do |method_name|
|
168
|
+
define_method(method_name) do |*args, **kwargs, &block|
|
169
|
+
forward_method(method_name, *args, **kwargs, &block)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
rescue ActiveRecord::StatementInvalid, ActiveRecord::ConnectionNotEstablished
|
173
|
+
# Table might not exist yet during setup
|
139
174
|
end
|
140
175
|
end
|
141
176
|
|
142
177
|
# Add method_missing for any methods we might have missed
|
143
178
|
def method_missing(method_name, *args, **kwargs, &block)
|
144
|
-
original_class = self.class.
|
145
|
-
if original_class.method_defined?(method_name)
|
179
|
+
original_class = self.class.foreign_class
|
180
|
+
if original_class && original_class.method_defined?(method_name)
|
146
181
|
forward_method(method_name, *args, **kwargs, &block)
|
147
182
|
else
|
148
183
|
super
|
@@ -150,8 +185,8 @@ module Historiographer
|
|
150
185
|
end
|
151
186
|
|
152
187
|
def respond_to_missing?(method_name, include_private = false)
|
153
|
-
original_class = self.class.
|
154
|
-
original_class.method_defined?(method_name) || super
|
188
|
+
original_class = self.class.foreign_class
|
189
|
+
(original_class && original_class.method_defined?(method_name)) || super
|
155
190
|
end
|
156
191
|
|
157
192
|
#
|
@@ -233,9 +268,59 @@ module Historiographer
|
|
233
268
|
# Track custom association methods
|
234
269
|
base.class_variable_set(:@@history_association_methods, [])
|
235
270
|
|
236
|
-
#
|
237
|
-
|
238
|
-
|
271
|
+
# Register this history class to have its associations set up after initialization
|
272
|
+
history_classes = Thread.current[:historiographer_history_classes] ||= []
|
273
|
+
history_classes << base
|
274
|
+
|
275
|
+
# Always define the setup_history_associations method
|
276
|
+
base.define_singleton_method :setup_history_associations do |force = false|
|
277
|
+
return if !force && class_variable_defined?(:@@associations_set_up) && class_variable_get(:@@associations_set_up)
|
278
|
+
class_variable_set(:@@associations_set_up, true)
|
279
|
+
|
280
|
+
return unless foreign_class
|
281
|
+
|
282
|
+
# Also set up method delegation if not already done
|
283
|
+
setup_method_delegation if respond_to?(:setup_method_delegation)
|
284
|
+
|
285
|
+
foreign_class.reflect_on_all_associations.each do |association|
|
286
|
+
begin
|
287
|
+
define_history_association(association)
|
288
|
+
rescue => e
|
289
|
+
# Log but don't fail
|
290
|
+
puts "Warning: Could not define history association #{association.name}: #{e.message}" if ENV['DEBUG']
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
# Set up the after_initialize hook if we're in a Rails app
|
296
|
+
if defined?(Rails) && Rails.respond_to?(:application) && Rails.application && Rails.application.config.respond_to?(:after_initialize)
|
297
|
+
Rails.application.config.after_initialize do
|
298
|
+
history_classes.each do |history_class|
|
299
|
+
history_class.setup_method_delegation if history_class.respond_to?(:setup_method_delegation)
|
300
|
+
history_class.setup_history_associations
|
301
|
+
end
|
302
|
+
end
|
303
|
+
else
|
304
|
+
# For non-Rails environments, try to set up associations immediately
|
305
|
+
|
306
|
+
# Try to set up now if possible
|
307
|
+
begin
|
308
|
+
base.setup_history_associations
|
309
|
+
rescue => e
|
310
|
+
# Will retry later
|
311
|
+
end
|
312
|
+
|
313
|
+
# Override reflect_on_association to ensure associations are defined
|
314
|
+
base.define_singleton_method :reflect_on_association do |name|
|
315
|
+
setup_history_associations rescue nil
|
316
|
+
super(name)
|
317
|
+
end
|
318
|
+
|
319
|
+
# Override reflect_on_all_associations to ensure associations are defined
|
320
|
+
base.define_singleton_method :reflect_on_all_associations do |*args|
|
321
|
+
setup_history_associations rescue nil
|
322
|
+
super(*args)
|
323
|
+
end
|
239
324
|
end
|
240
325
|
|
241
326
|
def snapshot
|
@@ -254,93 +339,140 @@ module Historiographer
|
|
254
339
|
end
|
255
340
|
|
256
341
|
def original_class
|
257
|
-
|
258
|
-
|
259
|
-
end
|
260
|
-
|
261
|
-
class_variable_get(:@@original_class)
|
342
|
+
# Use the foreign_class method we defined earlier
|
343
|
+
foreign_class
|
262
344
|
end
|
263
345
|
|
264
346
|
def define_history_association(association)
|
265
347
|
if association.is_a?(Symbol) || association.is_a?(String)
|
266
348
|
association = original_class.reflect_on_association(association)
|
349
|
+
# If the association doesn't exist on the original class, skip it
|
350
|
+
return unless association
|
267
351
|
end
|
352
|
+
|
268
353
|
assoc_name = association.name
|
269
|
-
assoc_module = association.active_record.module_parent
|
270
|
-
assoc_history_class_name = "#{association.class_name}History"
|
271
|
-
|
272
|
-
begin
|
273
|
-
assoc_module.const_get(assoc_history_class_name)
|
274
|
-
assoc_history_class_name = "#{assoc_module}::#{assoc_history_class_name}" unless assoc_history_class_name.match?(Regexp.new("#{assoc_module}::"))
|
275
|
-
rescue
|
276
|
-
end
|
277
|
-
|
278
354
|
assoc_foreign_key = association.foreign_key
|
279
355
|
|
280
356
|
# Skip through associations to history classes to avoid infinite loops
|
281
357
|
return if association.class_name.end_with?('History')
|
282
358
|
|
283
|
-
#
|
284
|
-
|
285
|
-
|
359
|
+
# Get the associated model's table name
|
360
|
+
original_assoc_class = association.class_name.safe_constantize
|
361
|
+
return unless original_assoc_class # Can't proceed without the class
|
362
|
+
|
363
|
+
assoc_table_name = original_assoc_class.table_name
|
364
|
+
history_table_name = "#{assoc_table_name.singularize}_histories"
|
365
|
+
|
366
|
+
# Check if a history table exists for this association
|
367
|
+
has_history_table = ActiveRecord::Base.connection.tables.include?(history_table_name)
|
368
|
+
|
369
|
+
if has_history_table
|
370
|
+
# This model has history tracking, use the history class
|
371
|
+
assoc_history_class_name = "#{association.class_name}History"
|
372
|
+
assoc_module = association.active_record.module_parent
|
373
|
+
|
374
|
+
begin
|
375
|
+
assoc_module.const_get(assoc_history_class_name)
|
376
|
+
assoc_history_class_name = "#{assoc_module}::#{assoc_history_class_name}" unless assoc_history_class_name.match?(Regexp.new("#{assoc_module}::"))
|
377
|
+
rescue
|
378
|
+
end
|
379
|
+
|
380
|
+
assoc_class = assoc_history_class_name.safe_constantize || OpenStruct.new(name: assoc_history_class_name)
|
381
|
+
assoc_class_name = assoc_class.name
|
382
|
+
else
|
383
|
+
# No history table, use the original model
|
384
|
+
assoc_class_name = association.class_name
|
385
|
+
end
|
286
386
|
|
287
387
|
case association.macro
|
288
388
|
when :belongs_to
|
289
|
-
#
|
389
|
+
# Start with all original association options
|
390
|
+
options = association.options.dup
|
391
|
+
|
392
|
+
# Override the class name and foreign key
|
393
|
+
options[:class_name] = assoc_class_name
|
394
|
+
options[:foreign_key] = assoc_foreign_key
|
395
|
+
|
396
|
+
# For history associations, we need to handle snapshot filtering differently
|
397
|
+
# We'll create the association but override the accessor method
|
290
398
|
if assoc_class_name.match?(/History/)
|
291
|
-
#
|
292
|
-
|
293
|
-
history_fk = association.class_name.gsub(/History$/, '').underscore + '_id'
|
399
|
+
# Create the Rails association first
|
400
|
+
belongs_to assoc_name, **options
|
294
401
|
|
295
|
-
#
|
296
|
-
|
297
|
-
methods_list << assoc_name
|
298
|
-
class_variable_set(:@@history_association_methods, methods_list)
|
402
|
+
# Then override the accessor to filter by snapshot_id
|
403
|
+
history_fk = association.class_name.gsub(/History$/, '').underscore + '_id'
|
299
404
|
|
300
|
-
define_method(assoc_name) do
|
405
|
+
define_method("#{assoc_name}_with_snapshot") do
|
301
406
|
return nil unless self[assoc_foreign_key]
|
302
407
|
assoc_class.where(
|
303
408
|
history_fk => self[assoc_foreign_key],
|
304
409
|
snapshot_id: self.snapshot_id
|
305
410
|
).first
|
306
411
|
end
|
412
|
+
|
413
|
+
# Alias the original method and replace it
|
414
|
+
alias_method "#{assoc_name}_without_snapshot", assoc_name
|
415
|
+
alias_method assoc_name, "#{assoc_name}_with_snapshot"
|
307
416
|
else
|
308
|
-
belongs_to assoc_name,
|
417
|
+
belongs_to assoc_name, **options
|
309
418
|
end
|
310
419
|
when :has_one
|
420
|
+
# Start with all original association options
|
421
|
+
options = association.options.dup
|
422
|
+
|
423
|
+
# Override the class name and keys
|
424
|
+
options[:class_name] = assoc_class_name
|
425
|
+
options[:foreign_key] = assoc_foreign_key
|
426
|
+
options[:primary_key] = history_foreign_key
|
427
|
+
|
311
428
|
if assoc_class_name.match?(/History/)
|
312
|
-
|
429
|
+
# Create the Rails association first
|
430
|
+
has_one assoc_name, **options
|
313
431
|
|
314
|
-
#
|
315
|
-
|
316
|
-
methods_list << assoc_name
|
317
|
-
class_variable_set(:@@history_association_methods, methods_list)
|
432
|
+
# Then override the accessor to filter by snapshot_id
|
433
|
+
hfk = history_foreign_key
|
318
434
|
|
319
|
-
define_method(assoc_name) do
|
435
|
+
define_method("#{assoc_name}_with_snapshot") do
|
320
436
|
assoc_class.where(
|
321
437
|
assoc_foreign_key => self[hfk],
|
322
438
|
snapshot_id: self.snapshot_id
|
323
439
|
).first
|
324
440
|
end
|
441
|
+
|
442
|
+
# Alias the original method and replace it
|
443
|
+
alias_method "#{assoc_name}_without_snapshot", assoc_name
|
444
|
+
alias_method assoc_name, "#{assoc_name}_with_snapshot"
|
325
445
|
else
|
326
|
-
has_one assoc_name,
|
446
|
+
has_one assoc_name, **options
|
327
447
|
end
|
328
448
|
when :has_many
|
449
|
+
# Start with all original association options
|
450
|
+
options = association.options.dup
|
451
|
+
|
452
|
+
# Override the class name and keys
|
453
|
+
options[:class_name] = assoc_class_name
|
454
|
+
options[:foreign_key] = assoc_foreign_key
|
455
|
+
options[:primary_key] = history_foreign_key
|
456
|
+
|
329
457
|
if assoc_class_name.match?(/History/)
|
458
|
+
# Create the Rails association first
|
459
|
+
has_many assoc_name, **options
|
460
|
+
|
461
|
+
# Then override the accessor to filter by snapshot_id
|
330
462
|
hfk = history_foreign_key
|
331
|
-
# Track this custom method
|
332
|
-
methods_list = class_variable_get(:@@history_association_methods) rescue []
|
333
|
-
methods_list << assoc_name
|
334
|
-
class_variable_set(:@@history_association_methods, methods_list)
|
335
463
|
|
336
|
-
define_method(assoc_name) do
|
464
|
+
define_method("#{assoc_name}_with_snapshot") do
|
337
465
|
assoc_class.where(
|
338
466
|
assoc_foreign_key => self[hfk],
|
339
467
|
snapshot_id: self.snapshot_id
|
340
468
|
)
|
341
469
|
end
|
470
|
+
|
471
|
+
# Alias the original method and replace it
|
472
|
+
alias_method "#{assoc_name}_without_snapshot", assoc_name
|
473
|
+
alias_method assoc_name, "#{assoc_name}_with_snapshot"
|
342
474
|
else
|
343
|
-
has_many assoc_name,
|
475
|
+
has_many assoc_name, **options
|
344
476
|
end
|
345
477
|
end
|
346
478
|
end
|
@@ -353,8 +485,9 @@ module Historiographer
|
|
353
485
|
def history_foreign_key
|
354
486
|
return @history_foreign_key if @history_foreign_key
|
355
487
|
|
356
|
-
#
|
357
|
-
|
488
|
+
# Use the table name to generate the foreign key to properly handle namespaced models
|
489
|
+
# E.g. EasyML::Column -> easy_ml_columns -> easy_ml_column_id
|
490
|
+
@history_foreign_key = original_class.base_class.table_name.singularize.foreign_key
|
358
491
|
end
|
359
492
|
|
360
493
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
ENV['RAILS_ENV'] ||= 'test'
|
4
|
+
|
5
|
+
require 'bundler'
|
6
|
+
Bundler.require :default, :test
|
7
|
+
|
8
|
+
require 'historiographer'
|
9
|
+
require 'combustion'
|
10
|
+
|
11
|
+
Combustion.path = 'spec/internal'
|
12
|
+
Combustion.initialize! :active_record do
|
13
|
+
config.load_defaults Rails::VERSION::STRING.to_f
|
14
|
+
end
|
15
|
+
|
16
|
+
require 'rspec/rails'
|
17
|
+
require 'database_cleaner'
|
18
|
+
|
19
|
+
RSpec.configure do |config|
|
20
|
+
config.use_transactional_fixtures = false
|
21
|
+
|
22
|
+
config.before(:suite) do
|
23
|
+
DatabaseCleaner.strategy = :transaction
|
24
|
+
DatabaseCleaner.clean_with(:truncation)
|
25
|
+
end
|
26
|
+
|
27
|
+
config.before(:each) do
|
28
|
+
DatabaseCleaner.start
|
29
|
+
end
|
30
|
+
|
31
|
+
config.after(:each) do
|
32
|
+
DatabaseCleaner.clean
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class CreateEasyMlColumns < ActiveRecord::Migration[7.0]
|
2
|
+
def change
|
3
|
+
create_table :easy_ml_columns do |t|
|
4
|
+
t.string :name
|
5
|
+
t.string :data_type
|
6
|
+
t.timestamps
|
7
|
+
end
|
8
|
+
|
9
|
+
create_table :easy_ml_column_histories do |t|
|
10
|
+
t.integer :easy_ml_column_id, null: false
|
11
|
+
t.string :name
|
12
|
+
t.string :data_type
|
13
|
+
t.timestamps
|
14
|
+
|
15
|
+
t.datetime :history_started_at, null: false
|
16
|
+
t.datetime :history_ended_at
|
17
|
+
t.integer :history_user_id
|
18
|
+
t.string :snapshot_id
|
19
|
+
|
20
|
+
t.index :easy_ml_column_id
|
21
|
+
t.index :history_started_at
|
22
|
+
t.index :history_ended_at
|
23
|
+
t.index :snapshot_id
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class CreateTestArticles < ActiveRecord::Migration[7.0]
|
2
|
+
def change
|
3
|
+
create_table :test_articles do |t|
|
4
|
+
t.string :title
|
5
|
+
t.integer :test_category_id
|
6
|
+
t.timestamps
|
7
|
+
end
|
8
|
+
|
9
|
+
create_table :test_article_histories do |t|
|
10
|
+
t.integer :test_article_id, null: false
|
11
|
+
t.string :title
|
12
|
+
t.integer :test_category_id
|
13
|
+
t.timestamps
|
14
|
+
|
15
|
+
t.datetime :history_started_at, null: false
|
16
|
+
t.datetime :history_ended_at
|
17
|
+
t.integer :history_user_id
|
18
|
+
t.string :snapshot_id
|
19
|
+
|
20
|
+
t.index :test_article_id
|
21
|
+
t.index :history_started_at
|
22
|
+
t.index :history_ended_at
|
23
|
+
t.index :snapshot_id
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class CreateTestCategories < ActiveRecord::Migration[7.0]
|
2
|
+
def change
|
3
|
+
create_table :test_categories do |t|
|
4
|
+
t.string :name
|
5
|
+
t.integer :test_articles_count, default: 0
|
6
|
+
t.timestamps
|
7
|
+
end
|
8
|
+
|
9
|
+
create_table :test_category_histories do |t|
|
10
|
+
t.integer :test_category_id, null: false
|
11
|
+
t.string :name
|
12
|
+
t.integer :test_articles_count, default: 0
|
13
|
+
t.timestamps
|
14
|
+
|
15
|
+
t.datetime :history_started_at, null: false
|
16
|
+
t.datetime :history_ended_at
|
17
|
+
t.integer :history_user_id
|
18
|
+
t.string :snapshot_id
|
19
|
+
|
20
|
+
t.index :test_category_id
|
21
|
+
t.index :history_started_at
|
22
|
+
t.index :history_ended_at
|
23
|
+
t.index :snapshot_id
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -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,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
|