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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 66fb9c7d713df84dd4ad5470c6ace5730e5a762d378d0d1fc523a00767b94a2b
4
- data.tar.gz: e9b1cfaa4911d4e596aaa8808d1e4cf69b7e57a40101bae4cf826ec48832d3c9
3
+ metadata.gz: 60587470dc09b843fa049daa514e78c23492a10e1652535762e05838e827d75a
4
+ data.tar.gz: 248a9a9b6dc13d2a7fa9a7926e6bad37c22e2d70cb2a27ba85ca57c079d788ef
5
5
  SHA512:
6
- metadata.gz: a6291260e457cc89940cbdd622e1e2075e05ad7682005f4bd4dab9e63d302e2cd1dd3d55d494f336b442fe36827b5934251f3fb155480ab46e1bf7e84cb77414
7
- data.tar.gz: e1a2afe3202d4c3949424afda77a5c9b307baa50c1e43bde0c488ecff3bd85aff08b5ce57c6664dba058b188603e35c845e508487769848fa811003174b751e0
6
+ metadata.gz: 558b8819149579b0f9c5a21bb1c16082036661e37791fd40b9adbca3df395af99795cc9afad7d3c6e83dbfbb6a4e5b664380c3bbe73aa952366eb752d5e38db9
7
+ data.tar.gz: 8dc727ba00be315e3d4067de27b93e5544f4bf854b9f63ab80ff86a7c53927018b6072d0f5590fbc23cd022544e004f908108a22c1ff58b83679d9594794cd97
data/DEVELOPMENT.md ADDED
@@ -0,0 +1,124 @@
1
+ # Historiographer Development Guide
2
+
3
+ ## Quick Start
4
+
5
+ ```bash
6
+ # Initial setup
7
+ bin/setup
8
+
9
+ # Run tests
10
+ bin/test # Regular test suite (fast)
11
+ bin/test-rails # Rails integration tests (slower)
12
+ bin/test-all # Both test suites
13
+
14
+ # Interactive console
15
+ bin/console
16
+ ```
17
+
18
+ ## Available Commands
19
+
20
+ ### Using Rake Tasks
21
+
22
+ ```bash
23
+ # Testing
24
+ rake spec # Run regular specs (default)
25
+ rake spec:regular # Run regular specs explicitly
26
+ rake spec:rails # Run Rails integration specs
27
+ rake spec:all # Run all specs
28
+
29
+ # Database
30
+ rake db:create # Create test database
31
+ rake db:migrate # Run migrations
32
+ rake db:rollback # Rollback migrations
33
+ rake test_setup # Setup test database
34
+ rake test_reset # Reset test database
35
+
36
+ # Other
37
+ rake console # Launch console with gem loaded
38
+ rake help # List all available tasks
39
+ ```
40
+
41
+ ### Using Bin Scripts
42
+
43
+ All scripts are in the `bin/` directory:
44
+
45
+ - `bin/setup` - Initial development setup
46
+ - `bin/test` - Run regular test suite
47
+ - `bin/test-rails` - Run Rails integration tests
48
+ - `bin/test-all` - Run all tests
49
+ - `bin/console` - Interactive console
50
+
51
+ ### Direct Commands
52
+
53
+ ```bash
54
+ # Run specific tests
55
+ bundle exec rspec spec/historiographer_spec.rb
56
+ bundle exec rspec spec/historiographer_spec.rb:100 # Run specific line
57
+
58
+ # Rails integration tests only
59
+ bundle exec rspec spec/rails_integration
60
+
61
+ # Database operations
62
+ bundle exec rake db:create
63
+ bundle exec rake db:migrate
64
+ bundle exec rake db:rollback
65
+ ```
66
+
67
+ ## Test Organization
68
+
69
+ The test suite is split into two parts:
70
+
71
+ 1. **Regular Tests** (`spec/*.rb`, `spec/models/*.rb`, etc.)
72
+ - Fast, lightweight Rails simulation
73
+ - Uses standalone_migrations
74
+ - Default when running `rake spec` or `bin/test`
75
+
76
+ 2. **Rails Integration Tests** (`spec/rails_integration/*.rb`)
77
+ - Uses Combustion to create a real Rails app
78
+ - Tests Rails-specific behaviors like autoloading
79
+ - Run separately with `rake spec:rails` or `bin/test-rails`
80
+
81
+ ## Database Setup
82
+
83
+ The gem uses PostgreSQL for testing. Two databases are used:
84
+
85
+ 1. `historiographer_test` - Main test database
86
+ 2. `historiographer_combustion_test` - Rails integration test database
87
+
88
+ Configure database connection in:
89
+ - `spec/db/database.yml` - Main test database
90
+ - `spec/internal/config/database.yml` - Combustion test database
91
+
92
+ ## Adding New Tests
93
+
94
+ ### Regular Tests
95
+ Place in `spec/` directory. Models go in `spec/models/`.
96
+
97
+ ### Rails Integration Tests
98
+ Place in `spec/rails_integration/`. These tests use Combustion and require:
99
+ ```ruby
100
+ require 'combustion_helper'
101
+ ```
102
+
103
+ ## Debugging
104
+
105
+ ```bash
106
+ # Launch console with all models loaded
107
+ bin/console
108
+
109
+ # Or with rake
110
+ rake console
111
+
112
+ # Or manually
113
+ bundle exec pry -r ./init.rb
114
+ ```
115
+
116
+ ## Gem Development
117
+
118
+ ```bash
119
+ # Build gem
120
+ rake build
121
+
122
+ # Release new version
123
+ rake release
124
+ ```
data/Gemfile CHANGED
@@ -25,9 +25,11 @@ group :development do
25
25
  end
26
26
 
27
27
  group :test do
28
+ gem 'combustion', '~> 1.3'
28
29
  gem 'database_cleaner'
29
30
  gem 'factory_bot_rails'
30
31
  gem 'guard'
31
32
  gem 'guard-rspec'
32
33
  gem 'rspec'
34
+ gem 'rspec-rails'
33
35
  end
data/README.md CHANGED
@@ -358,7 +358,22 @@ For contributors on OSX, you may have difficulty installing mysql:
358
358
  gem install mysql2 -v '0.4.10' --source 'https://rubygems.org/' -- --with-ldflags=-L/usr/local/opt/openssl/lib --with-cppflags=-I/usr/local/opt/openssl/include
359
359
  ```
360
360
 
361
+ == Testing
362
+
363
+ ```bash
364
+ # Initial setup
365
+ bin/setup
366
+
367
+ # Run tests
368
+ bin/test # Regular test suite (fast)
369
+ bin/test-rails # Rails integration tests (slower)
370
+ bin/test-all # Both test suites
371
+
372
+ # Interactive console
373
+ bin/console
374
+ ```
375
+
361
376
  == Copyright
362
377
 
363
- Copyright (c) 2016-2020 brettshollenberger. See LICENSE.txt for
378
+ Copyright (c) 2016-2025 brettshollenberger. See LICENSE.txt for
364
379
  further details.
data/Rakefile CHANGED
@@ -22,6 +22,20 @@ Jeweler::Tasks.new do |gem|
22
22
  gem.description = %Q{Creates separate tables for each history table}
23
23
  gem.email = "brett.shollenberger@gmail.com"
24
24
  gem.authors = ["brettshollenberger"]
25
+
26
+ # Use glob patterns to automatically include all relevant files
27
+ gem.files = `git ls-files`.split("\n").reject { |f|
28
+ f.match(/^(test|spec|features)\//) ||
29
+ f.match(/\.gem$/) ||
30
+ f.match(/^\./) ||
31
+ f == 'Gemfile.lock'
32
+ }
33
+
34
+ # Include spec files for development/testing
35
+ gem.files += Dir.glob("spec/**/*")
36
+
37
+ # Ensure we have all the executables
38
+ gem.executables = Dir.glob("bin/*").map { |f| File.basename(f) }
25
39
  end
26
40
  Jeweler::RubygemsDotOrgTasks.new
27
41
 
@@ -52,3 +66,57 @@ end
52
66
 
53
67
  require 'standalone_migrations'
54
68
  StandaloneMigrations::Tasks.load_tasks
69
+
70
+ # Custom tasks for common operations
71
+ namespace :spec do
72
+ require 'rspec/core/rake_task'
73
+
74
+ desc "Run regular specs (excluding Rails integration)"
75
+ RSpec::Core::RakeTask.new(:regular) do |t|
76
+ t.rspec_opts = "--exclude-pattern spec/rails_integration/**/*_spec.rb"
77
+ end
78
+
79
+ desc "Run Rails integration specs with Combustion"
80
+ RSpec::Core::RakeTask.new(:rails) do |t|
81
+ t.pattern = "spec/rails_integration/**/*_spec.rb"
82
+ end
83
+
84
+ desc "Run all specs (regular and Rails integration separately)"
85
+ task :all do
86
+ puts "\n========== Running Regular Specs ==========\n"
87
+ Rake::Task['spec:regular'].invoke
88
+ puts "\n========== Running Rails Integration Specs ==========\n"
89
+ Rake::Task['spec:rails'].invoke
90
+ end
91
+ end
92
+
93
+ desc "Run regular test suite (default)"
94
+ task :spec => 'spec:regular'
95
+
96
+ desc "Setup test database"
97
+ task :test_setup do
98
+ sh "bundle exec rake db:create"
99
+ sh "bundle exec rake db:migrate"
100
+ end
101
+
102
+ desc "Reset test database"
103
+ task :test_reset do
104
+ sh "bundle exec rake db:drop"
105
+ sh "bundle exec rake db:create"
106
+ sh "bundle exec rake db:migrate"
107
+ end
108
+
109
+ desc "Run linting and type checking"
110
+ task :lint do
111
+ puts "No linting configured yet. Consider adding rubocop."
112
+ end
113
+
114
+ desc "Console with the gem loaded"
115
+ task :console do
116
+ sh "bundle exec pry -r ./init.rb"
117
+ end
118
+
119
+ desc "List all available tasks"
120
+ task :help do
121
+ sh "rake -T"
122
+ end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 4.4.2
1
+ 4.4.4
data/bin/console ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # Launch an interactive console with the gem loaded
3
+
4
+ require "bundler/setup"
5
+ require "pry"
6
+ require_relative "../init"
7
+
8
+ puts "Loading Historiographer console..."
9
+ puts "Models available: Post, Author, Comment, User, etc."
10
+ Pry.start
data/bin/setup ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # Setup script for development
3
+
4
+ puts "Setting up Historiographer development environment..."
5
+
6
+ system("bundle install")
7
+ system("bundle exec rake db:create")
8
+ system("bundle exec rake db:migrate")
9
+ system("createdb historiographer_combustion_test 2>/dev/null")
10
+
11
+ puts "\nSetup complete! You can now run:"
12
+ puts " bin/test - Run regular tests"
13
+ puts " bin/test-rails - Run Rails integration tests"
14
+ puts " bin/test-all - Run all tests"
15
+ puts " bin/console - Launch interactive console"
data/bin/test ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ # Run the regular test suite (excluding Rails integration tests)
3
+
4
+ puts "Running regular test suite..."
5
+ exec "bundle exec rspec --exclude-pattern spec/rails_integration/**/*_spec.rb"
data/bin/test-all ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # Run all tests (regular and Rails integration)
3
+
4
+ puts "\n========== Running Regular Test Suite ==========\n"
5
+ system("bundle exec rspec --exclude-pattern spec/rails_integration/**/*_spec.rb")
6
+
7
+ puts "\n========== Running Rails Integration Tests ==========\n"
8
+ system("bundle exec rspec spec/rails_integration")
9
+
10
+ exit($?.exitstatus || 0)
data/bin/test-rails ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ # Run Rails integration tests with Combustion
3
+
4
+ puts "Running Rails integration tests..."
5
+ exec "bundle exec rspec spec/rails_integration/historiographer_rails_integration_spec.rb"
@@ -2,38 +2,36 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: historiographer 4.4.2 ruby lib
5
+ # stub: historiographer 4.4.4 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "historiographer".freeze
9
- s.version = "4.4.2"
9
+ s.version = "4.4.4"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
13
13
  s.authors = ["brettshollenberger".freeze]
14
- s.date = "2025-08-22"
14
+ s.date = "2025-09-19"
15
15
  s.description = "Creates separate tables for each history table".freeze
16
16
  s.email = "brett.shollenberger@gmail.com".freeze
17
+ s.executables = ["console".freeze, "setup".freeze, "test".freeze, "test-all".freeze, "test-rails".freeze]
17
18
  s.extra_rdoc_files = [
18
19
  "LICENSE.txt",
19
20
  "README.md"
20
21
  ]
21
22
  s.files = [
22
- ".document",
23
- ".rspec",
24
- ".ruby-version",
25
- ".standalone_migrations",
23
+ "DEVELOPMENT.md",
26
24
  "Gemfile",
27
- "Gemfile.lock",
28
25
  "Guardfile",
29
26
  "LICENSE.txt",
30
27
  "README.md",
31
28
  "Rakefile",
32
29
  "VERSION",
33
- "historiographer-4.1.12.gem",
34
- "historiographer-4.1.13.gem",
35
- "historiographer-4.1.14.gem",
36
- "historiographer-4.3.0.gem",
30
+ "bin/console",
31
+ "bin/setup",
32
+ "bin/test",
33
+ "bin/test-all",
34
+ "bin/test-rails",
37
35
  "historiographer.gemspec",
38
36
  "init.rb",
39
37
  "instructions/implementation.md",
@@ -49,6 +47,7 @@ Gem::Specification.new do |s|
49
47
  "lib/historiographer/safe.rb",
50
48
  "lib/historiographer/silent.rb",
51
49
  "lib/historiographer/version.rb",
50
+ "spec/combustion_helper.rb",
52
51
  "spec/db/database.yml",
53
52
  "spec/db/migrate/20161121212228_create_posts.rb",
54
53
  "spec/db/migrate/20161121212229_create_post_histories.rb",
@@ -71,14 +70,39 @@ Gem::Specification.new do |s|
71
70
  "spec/db/migrate/20250824000000_create_test_articles.rb",
72
71
  "spec/db/migrate/20250824000001_create_test_categories.rb",
73
72
  "spec/db/migrate/20250825000000_create_bylines.rb",
73
+ "spec/db/migrate/20250826000000_create_test_users.rb",
74
+ "spec/db/migrate/20250826000001_create_test_user_histories.rb",
75
+ "spec/db/migrate/20250826000002_create_test_websites.rb",
76
+ "spec/db/migrate/20250826000003_create_test_website_histories.rb",
77
+ "spec/db/migrate/20250827000000_create_templates.rb",
78
+ "spec/db/migrate/20250827000001_create_template_histories.rb",
79
+ "spec/db/migrate/20250827000002_create_websites.rb",
80
+ "spec/db/migrate/20250827000003_create_website_histories.rb",
81
+ "spec/db/migrate/20250827000004_create_template_files.rb",
82
+ "spec/db/migrate/20250827000005_create_template_file_histories.rb",
83
+ "spec/db/migrate/20250827000006_create_website_files.rb",
84
+ "spec/db/migrate/20250827000007_create_website_file_histories.rb",
85
+ "spec/db/migrate/20250827000008_create_code_files_view.rb",
74
86
  "spec/db/schema.rb",
87
+ "spec/examples.txt",
75
88
  "spec/factories/post.rb",
76
- "spec/foreign_key_spec.rb",
77
89
  "spec/historiographer_spec.rb",
90
+ "spec/integration/historiographer_safe_integration_spec.rb",
91
+ "spec/internal/app/models/application_record.rb",
92
+ "spec/internal/app/models/deploy.rb",
93
+ "spec/internal/app/models/user.rb",
94
+ "spec/internal/app/models/website.rb",
95
+ "spec/internal/app/models/website_history.rb",
96
+ "spec/internal/config/database.yml",
97
+ "spec/internal/config/routes.rb",
98
+ "spec/internal/db/schema.rb",
99
+ "spec/internal/log/development.log",
100
+ "spec/internal/log/test.log",
78
101
  "spec/models/application_record.rb",
79
102
  "spec/models/author.rb",
80
103
  "spec/models/author_history.rb",
81
104
  "spec/models/byline.rb",
105
+ "spec/models/code_file.rb",
82
106
  "spec/models/comment.rb",
83
107
  "spec/models/comment_history.rb",
84
108
  "spec/models/easy_ml/column.rb",
@@ -93,15 +117,29 @@ Gem::Specification.new do |s|
93
117
  "spec/models/safe_post_history.rb",
94
118
  "spec/models/silent_post.rb",
95
119
  "spec/models/silent_post_history.rb",
120
+ "spec/models/template.rb",
121
+ "spec/models/template_file.rb",
122
+ "spec/models/template_file_history.rb",
123
+ "spec/models/template_history.rb",
96
124
  "spec/models/test_article.rb",
97
125
  "spec/models/test_article_history.rb",
98
126
  "spec/models/test_category.rb",
99
127
  "spec/models/test_category_history.rb",
128
+ "spec/models/test_user.rb",
129
+ "spec/models/test_user_history.rb",
130
+ "spec/models/test_website.rb",
131
+ "spec/models/test_website_history.rb",
100
132
  "spec/models/thing_with_compound_index.rb",
101
133
  "spec/models/thing_with_compound_index_history.rb",
102
134
  "spec/models/thing_without_history.rb",
103
135
  "spec/models/user.rb",
104
- "spec/spec_helper.rb"
136
+ "spec/models/website.rb",
137
+ "spec/models/website_file.rb",
138
+ "spec/models/website_file_history.rb",
139
+ "spec/models/website_history.rb",
140
+ "spec/rails_integration/historiographer_rails_integration_spec.rb",
141
+ "spec/spec_helper.rb",
142
+ "spec/view_backed_model_spec.rb"
105
143
  ]
106
144
  s.homepage = "http://github.com/brettshollenberger/historiographer".freeze
107
145
  s.licenses = ["MIT".freeze]
@@ -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
- # Store the original class for method delegation
85
- class_variable_set(:@@original_class, foreign_class)
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 foreign_class.ancestors.include?(Historiographer::Silent)
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
- foreign_class.singleton_class.class_eval do
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 foreign_class.history_class.method_defined?(method_name)
144
+ return if self.history_class.method_defined?(method_name)
124
145
 
125
- foreign_class.history_class.class_eval do
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
@@ -134,19 +155,29 @@ module Historiographer
134
155
  end
135
156
 
136
157
  begin
137
- (foreign_class.columns.map(&:name) - ["id"]).each do |method_name|
138
- define_method(method_name) do |*args, **kwargs, &block|
139
- forward_method(method_name, *args, **kwargs, &block)
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
140
171
  end
172
+ rescue ActiveRecord::StatementInvalid, ActiveRecord::ConnectionNotEstablished
173
+ # Table might not exist yet during setup
141
174
  end
142
- rescue ActiveRecord::StatementInvalid, ActiveRecord::ConnectionNotEstablished
143
- # Table might not exist yet during setup
144
175
  end
145
176
 
146
177
  # Add method_missing for any methods we might have missed
147
178
  def method_missing(method_name, *args, **kwargs, &block)
148
- original_class = self.class.class_variable_get(:@@original_class)
149
- if original_class.method_defined?(method_name)
179
+ original_class = self.class.foreign_class
180
+ if original_class && original_class.method_defined?(method_name)
150
181
  forward_method(method_name, *args, **kwargs, &block)
151
182
  else
152
183
  super
@@ -154,8 +185,8 @@ module Historiographer
154
185
  end
155
186
 
156
187
  def respond_to_missing?(method_name, include_private = false)
157
- original_class = self.class.class_variable_get(:@@original_class)
158
- original_class.method_defined?(method_name) || super
188
+ original_class = self.class.foreign_class
189
+ (original_class && original_class.method_defined?(method_name)) || super
159
190
  end
160
191
 
161
192
  #
@@ -241,29 +272,36 @@ module Historiographer
241
272
  history_classes = Thread.current[:historiographer_history_classes] ||= []
242
273
  history_classes << base
243
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
+
244
295
  # Set up the after_initialize hook if we're in a Rails app
245
296
  if defined?(Rails) && Rails.respond_to?(:application) && Rails.application && Rails.application.config.respond_to?(:after_initialize)
246
297
  Rails.application.config.after_initialize do
247
298
  history_classes.each do |history_class|
299
+ history_class.setup_method_delegation if history_class.respond_to?(:setup_method_delegation)
248
300
  history_class.setup_history_associations
249
301
  end
250
302
  end
251
303
  else
252
- # For non-Rails environments (like our test suite), set up associations immediately
253
- # but defer if models aren't loaded yet
254
- base.define_singleton_method :setup_history_associations do |force = false|
255
- return if !force && class_variable_defined?(:@@associations_set_up) && class_variable_get(:@@associations_set_up)
256
- class_variable_set(:@@associations_set_up, true)
257
-
258
- foreign_class.reflect_on_all_associations.each do |association|
259
- begin
260
- define_history_association(association)
261
- rescue => e
262
- # Log but don't fail
263
- puts "Warning: Could not define history association #{association.name}: #{e.message}" if ENV['DEBUG']
264
- end
265
- end
266
- end
304
+ # For non-Rails environments, try to set up associations immediately
267
305
 
268
306
  # Try to set up now if possible
269
307
  begin
@@ -301,11 +339,8 @@ module Historiographer
301
339
  end
302
340
 
303
341
  def original_class
304
- unless class_variable_defined?(:@@original_class)
305
- class_variable_set(:@@original_class, self.name.gsub(/History$/, '').constantize)
306
- end
307
-
308
- class_variable_get(:@@original_class)
342
+ # Use the foreign_class method we defined earlier
343
+ foreign_class
309
344
  end
310
345
 
311
346
  def define_history_association(association)
@@ -304,6 +304,11 @@ module Historiographer
304
304
 
305
305
  # Recursively snapshot associations, avoiding infinite loops
306
306
  self.class.reflect_on_all_associations.each do |association|
307
+ # Skip associations to models without primary keys (e.g., database views)
308
+ association_class = association.klass rescue nil
309
+ next if association_class.nil?
310
+ next if association_class.primary_key.nil?
311
+
307
312
  associated_records = send(association.name)&.reload
308
313
  if associated_records.respond_to?(:order)
309
314
  associated_records = associated_records.order(id: :asc)
@@ -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,8 @@
1
+ class CreateTestUsers < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :test_users do |t|
4
+ t.string :name
5
+ t.timestamps
6
+ end
7
+ end
8
+ end