historiographer 4.4.4 → 4.4.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 60587470dc09b843fa049daa514e78c23492a10e1652535762e05838e827d75a
4
- data.tar.gz: 248a9a9b6dc13d2a7fa9a7926e6bad37c22e2d70cb2a27ba85ca57c079d788ef
3
+ metadata.gz: 99a33a798a97f440f2d4c0d2d1973303a013e99bb5f8d3f566c51a0d9375961f
4
+ data.tar.gz: 56b51522b7f4b254b9ba1e4e1813aa449dec80579e9e79c8e4f446e629ce74c7
5
5
  SHA512:
6
- metadata.gz: 558b8819149579b0f9c5a21bb1c16082036661e37791fd40b9adbca3df395af99795cc9afad7d3c6e83dbfbb6a4e5b664380c3bbe73aa952366eb752d5e38db9
7
- data.tar.gz: 8dc727ba00be315e3d4067de27b93e5544f4bf854b9f63ab80ff86a7c53927018b6072d0f5590fbc23cd022544e004f908108a22c1ff58b83679d9594794cd97
6
+ metadata.gz: d27ffa3d2aaca0a91d9283c5f18701e7c576475d2619c569dd7edc21cdc8983c3376e823067ffb233a1f14ec4123823564b486c906da6b7860c285eb555ecc67
7
+ data.tar.gz: 7b1608e2c572ab02da5e2371679fe5facc463ac4d788e1e9e499a2078be074e3345758e9a9aed97931ade4c15f59fd220d2f8957e7d7101f44290217647a9ac2
data/README.md CHANGED
@@ -12,7 +12,7 @@ The Audited gem has some serious flaws.
12
12
 
13
13
  2. It doesn't provide the indexes you need from your primary tables
14
14
 
15
- 3. It doesn't provdie out-of-the-box snapshots
15
+ 3. It doesn't provide out-of-the-box snapshots
16
16
 
17
17
  ## How does Historiographer solve these problems?
18
18
 
@@ -86,14 +86,17 @@ You can take a snapshot of a record and all its associated records:
86
86
 
87
87
  ```ruby
88
88
  post = Post.find(1)
89
- post.snapshot(history_user_id: current_user.id)
89
+ post.snapshot
90
90
  ```
91
91
 
92
92
  This will:
93
93
 
94
94
  1. Create a history record for the post
95
- 2. Create history records for all associated records (comments, author, etc.)
95
+ 2. Recursively create history records for all associated records that also include Historiographer (comments, author, etc.)
96
96
  3. Link these history records together with a shared `snapshot_id`
97
+ 4. Skip any view-backed models (models without a primary key)
98
+
99
+ **Note:** The snapshot cascades to ALL associations where the associated model includes Historiographer. There is currently no built-in way to limit which associations get snapshotted. If you need finer control, you can either not include Historiographer on models you don't want snapshotted, or override the `snapshot` method.
97
100
 
98
101
  You can retrieve the latest snapshot using:
99
102
 
@@ -130,6 +133,38 @@ This can be useful when:
130
133
  - You're versioning training data for machine learning models
131
134
  - You need to maintain immutable audit trails at specific checkpoints
132
135
 
136
+ ## Safe and Silent Modes
137
+
138
+ Historiographer provides two additional modes for migrating existing models:
139
+
140
+ ### Historiographer::Safe
141
+
142
+ Use `Historiographer::Safe` when migrating an existing model to Historiographer. Instead of raising an error when `history_user_id` is missing, it logs to Rollbar, allowing you to find all locations that need to be updated:
143
+
144
+ ```ruby
145
+ class Post < ActiveRecord::Base
146
+ include Historiographer::Safe
147
+ end
148
+
149
+ # This will create a history record and log to Rollbar (instead of raising an error)
150
+ Post.create(title: "My Post")
151
+ ```
152
+
153
+ ### Historiographer::Silent
154
+
155
+ Use `Historiographer::Silent` when you want to allow missing `history_user_id` without any errors or logging:
156
+
157
+ ```ruby
158
+ class Post < ActiveRecord::Base
159
+ include Historiographer::Silent
160
+ end
161
+
162
+ # This will create a history record silently without history_user_id
163
+ Post.create(title: "My Post")
164
+ ```
165
+
166
+ **Note:** Both Safe and Silent modes are intended for migration purposes, not as long-term solutions.
167
+
133
168
  ## Namespaced Models
134
169
 
135
170
  When using namespaced models, Rails handles foreign key naming differently than with non-namespaced models. For example, if you have a model namespaced like this:
@@ -296,6 +331,7 @@ You should also make a `PostHistory` class if you're going to query `PostHistory
296
331
  ```ruby
297
332
  class PostHistory < ActiveRecord::Base
298
333
  self.table_name = "post_histories"
334
+ include Historiographer::History
299
335
  end
300
336
  ```
301
337
 
@@ -323,6 +359,21 @@ Post.last.destroy!(history_user_id: current_user.id)
323
359
  Post.destroy_all(history_user_id: current_user.id)
324
360
  ```
325
361
 
362
+ ### Skipping History
363
+
364
+ If you need to save a record without creating a history entry:
365
+
366
+ ```ruby
367
+ post = Post.new(title: "My Post")
368
+ post.save_without_history # No history_user_id required
369
+ post.save_without_history! # Bang version
370
+
371
+ # For bulk operations:
372
+ Post.all.update_all_without_history(title: "New Title")
373
+ Post.all.delete_all_without_history
374
+ Post.all.destroy_all_without_history
375
+ ```
376
+
326
377
  The `histories` classes have a `current` method, which only finds current history records. These records will also be the same as the data in the primary table.
327
378
 
328
379
  ```ruby
data/VERSION CHANGED
@@ -1 +1 @@
1
- 4.4.4
1
+ 4.4.5
@@ -2,16 +2,16 @@
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.4 ruby lib
5
+ # stub: historiographer 4.4.5 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "historiographer".freeze
9
- s.version = "4.4.4"
9
+ s.version = "4.4.5"
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-09-19"
14
+ s.date = "2026-01-14"
15
15
  s.description = "Creates separate tables for each history table".freeze
16
16
  s.email = "brett.shollenberger@gmail.com".freeze
17
17
  s.executables = ["console".freeze, "setup".freeze, "test".freeze, "test-all".freeze, "test-rails".freeze]
@@ -109,8 +109,11 @@ module Historiographer
109
109
  #
110
110
  # Set up user association unless Silent module is included
111
111
  # Defer this check until foreign_class is available
112
+ # Use optional: true because history_user_id validation is handled separately
113
+ # by Historiographer's validate_history_user_id_present method, and we need
114
+ # to allow updates without a user when using without_history_user_id blocks
112
115
  unless base.foreign_class && base.foreign_class.ancestors.include?(Historiographer::Silent)
113
- belongs_to :user, foreign_key: :history_user_id
116
+ belongs_to :user, foreign_key: :history_user_id, optional: true
114
117
  end
115
118
 
116
119
  # Add method_added hook to the original class when it's available
@@ -188,7 +188,7 @@ module Historiographer
188
188
  rescue NameError
189
189
  # Get the base table name without _histories suffix
190
190
  base_table = base.table_name.singularize.sub(/_histories$/, '')
191
-
191
+
192
192
  history_class_initializer = Class.new(ActiveRecord::Base) do
193
193
  self.table_name = "#{base_table}_histories"
194
194
  end
@@ -204,7 +204,7 @@ module Historiographer
204
204
 
205
205
  # Set the constant in the correct module
206
206
  history_class = target_module.const_set(final_class_name, history_class_initializer)
207
-
207
+
208
208
  # Now that the class is named, include the History module and extend class methods
209
209
  history_class.send(:include, Historiographer::History)
210
210
  end
@@ -279,7 +279,7 @@ module Historiographer
279
279
  save!(*args, &block)
280
280
  @no_history = false
281
281
  end
282
-
282
+
283
283
  def snapshot(tree = {}, snapshot_id = nil)
284
284
  return if is_history_class?
285
285
 
@@ -296,7 +296,7 @@ module Historiographer
296
296
  null_snapshot = history_class.where(foreign_key => attrs[primary_key], snapshot_id: nil).first
297
297
  snapshot = nil
298
298
  if null_snapshot.present?
299
- null_snapshot.update(snapshot_id: snapshot_id)
299
+ null_snapshot.update!(snapshot_id: snapshot_id)
300
300
  snapshot = null_snapshot
301
301
  else
302
302
  snapshot = record_history(snapshot_id: snapshot_id)
@@ -308,7 +308,7 @@ module Historiographer
308
308
  association_class = association.klass rescue nil
309
309
  next if association_class.nil?
310
310
  next if association_class.primary_key.nil?
311
-
311
+
312
312
  associated_records = send(association.name)&.reload
313
313
  if associated_records.respond_to?(:order)
314
314
  associated_records = associated_records.order(id: :asc)
@@ -383,7 +383,7 @@ module Historiographer
383
383
 
384
384
  if history_class.history_foreign_key.present? && history_class.present?
385
385
  result = history_class.insert_all([attrs])
386
-
386
+
387
387
  # Check if the insertion was successful
388
388
  if result.rows.empty?
389
389
  # insert_all returned empty rows, likely due to a duplicate/conflict
@@ -393,7 +393,7 @@ module Historiographer
393
393
  foreign_key => attrs[foreign_key],
394
394
  history_started_at: attrs['history_started_at']
395
395
  ).first
396
-
396
+
397
397
  if existing_history
398
398
  # A duplicate history already exists (race condition or retry)
399
399
  # This is acceptable - return the existing history
@@ -404,7 +404,7 @@ module Historiographer
404
404
  history_class.create!(attrs) # This will raise the correct error since it will fail the unique constraint
405
405
  end
406
406
  end
407
-
407
+
408
408
  inserted_id = result.rows.first.first if history_class.primary_key == 'id'
409
409
  instance = history_class.find(inserted_id)
410
410
  current_history.update_columns(history_ended_at: now) if current_history.present?
data/spec/examples.txt CHANGED
@@ -1,64 +1,65 @@
1
1
  example_id | status | run_time |
2
2
  ------------------------------------------------------------------ | ------ | --------------- |
3
- ./spec/historiographer_spec.rb[1:1:1] | passed | 0.06528 seconds |
4
- ./spec/historiographer_spec.rb[1:1:2] | passed | 0.06876 seconds |
5
- ./spec/historiographer_spec.rb[1:1:3] | passed | 0.06676 seconds |
6
- ./spec/historiographer_spec.rb[1:2:1] | passed | 0.07069 seconds |
7
- ./spec/historiographer_spec.rb[1:2:2] | passed | 0.05746 seconds |
8
- ./spec/historiographer_spec.rb[1:2:3:1:1] | passed | 0.09942 seconds |
9
- ./spec/historiographer_spec.rb[1:2:3:1:2] | passed | 0.09532 seconds |
10
- ./spec/historiographer_spec.rb[1:2:3:2:1] | passed | 0.0666 seconds |
11
- ./spec/historiographer_spec.rb[1:2:3:2:2] | passed | 0.09223 seconds |
12
- ./spec/historiographer_spec.rb[1:2:3:2:3] | passed | 0.16142 seconds |
13
- ./spec/historiographer_spec.rb[1:2:3:3:1] | passed | 0.09003 seconds |
14
- ./spec/historiographer_spec.rb[1:2:3:3:2] | passed | 0.0727 seconds |
15
- ./spec/historiographer_spec.rb[1:2:4:1] | passed | 0.07277 seconds |
16
- ./spec/historiographer_spec.rb[1:2:4:2] | passed | 0.07295 seconds |
17
- ./spec/historiographer_spec.rb[1:2:4:3] | passed | 0.06818 seconds |
18
- ./spec/historiographer_spec.rb[1:2:5:1] | passed | 0.07794 seconds |
19
- ./spec/historiographer_spec.rb[1:2:5:2] | passed | 0.06574 seconds |
20
- ./spec/historiographer_spec.rb[1:2:5:3] | passed | 0.07246 seconds |
21
- ./spec/historiographer_spec.rb[1:2:6] | passed | 0.05671 seconds |
22
- ./spec/historiographer_spec.rb[1:2:7] | passed | 0.06192 seconds |
23
- ./spec/historiographer_spec.rb[1:2:8] | passed | 0.06312 seconds |
24
- ./spec/historiographer_spec.rb[1:3:1] | passed | 0.08252 seconds |
25
- ./spec/historiographer_spec.rb[1:4:1] | passed | 0.08901 seconds |
26
- ./spec/historiographer_spec.rb[1:5:1] | passed | 0.06204 seconds |
27
- ./spec/historiographer_spec.rb[1:5:2] | passed | 0.06017 seconds |
28
- ./spec/historiographer_spec.rb[1:6:1] | passed | 0.06542 seconds |
29
- ./spec/historiographer_spec.rb[1:6:2] | passed | 0.07392 seconds |
30
- ./spec/historiographer_spec.rb[1:7:1] | passed | 0.08007 seconds |
31
- ./spec/historiographer_spec.rb[1:7:2] | passed | 0.06545 seconds |
32
- ./spec/historiographer_spec.rb[1:7:3] | passed | 0.06212 seconds |
33
- ./spec/historiographer_spec.rb[1:7:4] | passed | 0.06293 seconds |
34
- ./spec/historiographer_spec.rb[1:7:5] | passed | 0.06288 seconds |
35
- ./spec/historiographer_spec.rb[1:8:1] | passed | 0.062 seconds |
36
- ./spec/historiographer_spec.rb[1:9:1] | passed | 0.09362 seconds |
37
- ./spec/historiographer_spec.rb[1:10:1] | passed | 0.05997 seconds |
38
- ./spec/historiographer_spec.rb[1:11:1] | passed | 0.07282 seconds |
39
- ./spec/historiographer_spec.rb[1:11:2] | passed | 0.06606 seconds |
40
- ./spec/historiographer_spec.rb[1:11:3] | passed | 0.06809 seconds |
41
- ./spec/historiographer_spec.rb[1:11:4] | passed | 0.07018 seconds |
42
- ./spec/historiographer_spec.rb[1:12:1] | passed | 0.08426 seconds |
43
- ./spec/historiographer_spec.rb[1:12:2] | passed | 0.06852 seconds |
44
- ./spec/historiographer_spec.rb[1:12:3] | passed | 0.10016 seconds |
45
- ./spec/historiographer_spec.rb[1:12:4] | passed | 0.09182 seconds |
46
- ./spec/historiographer_spec.rb[1:12:5] | passed | 0.08378 seconds |
47
- ./spec/historiographer_spec.rb[1:12:6] | passed | 0.11921 seconds |
48
- ./spec/historiographer_spec.rb[1:13:1] | passed | 0.11958 seconds |
49
- ./spec/historiographer_spec.rb[1:14:1] | passed | 0.05717 seconds |
50
- ./spec/historiographer_spec.rb[1:14:2] | passed | 0.07805 seconds |
51
- ./spec/historiographer_spec.rb[1:14:3] | passed | 0.06426 seconds |
52
- ./spec/historiographer_spec.rb[1:14:4] | passed | 0.06178 seconds |
53
- ./spec/historiographer_spec.rb[1:15:1] | passed | 0.06755 seconds |
54
- ./spec/historiographer_spec.rb[1:15:2] | passed | 0.08706 seconds |
55
- ./spec/historiographer_spec.rb[1:16:1] | passed | 0.06589 seconds |
56
- ./spec/historiographer_spec.rb[1:16:2] | passed | 0.04756 seconds |
57
- ./spec/historiographer_spec.rb[1:16:3] | passed | 0.04635 seconds |
58
- ./spec/historiographer_spec.rb[1:16:4] | passed | 0.07578 seconds |
59
- ./spec/historiographer_spec.rb[1:17:1:1] | passed | 0.06365 seconds |
60
- ./spec/historiographer_spec.rb[1:17:1:2] | passed | 0.10348 seconds |
61
- ./spec/historiographer_spec.rb[1:17:2:1] | passed | 0.06393 seconds |
3
+ ./spec/historiographer_spec.rb[1:1:1] | passed | 0.07846 seconds |
4
+ ./spec/historiographer_spec.rb[1:1:2] | passed | 0.05421 seconds |
5
+ ./spec/historiographer_spec.rb[1:1:3] | passed | 0.05161 seconds |
6
+ ./spec/historiographer_spec.rb[1:2:1] | passed | 0.05923 seconds |
7
+ ./spec/historiographer_spec.rb[1:2:2] | passed | 0.0649 seconds |
8
+ ./spec/historiographer_spec.rb[1:2:3:1:1] | passed | 0.10678 seconds |
9
+ ./spec/historiographer_spec.rb[1:2:3:1:2] | passed | 0.07362 seconds |
10
+ ./spec/historiographer_spec.rb[1:2:3:2:1] | passed | 0.0583 seconds |
11
+ ./spec/historiographer_spec.rb[1:2:3:2:2] | passed | 0.06499 seconds |
12
+ ./spec/historiographer_spec.rb[1:2:3:2:3] | passed | 0.07272 seconds |
13
+ ./spec/historiographer_spec.rb[1:2:3:3:1] | passed | 0.07946 seconds |
14
+ ./spec/historiographer_spec.rb[1:2:3:3:2] | passed | 0.0671 seconds |
15
+ ./spec/historiographer_spec.rb[1:2:4:1] | passed | 0.06979 seconds |
16
+ ./spec/historiographer_spec.rb[1:2:4:2] | passed | 0.06026 seconds |
17
+ ./spec/historiographer_spec.rb[1:2:4:3] | passed | 0.05019 seconds |
18
+ ./spec/historiographer_spec.rb[1:2:5:1] | passed | 0.0693 seconds |
19
+ ./spec/historiographer_spec.rb[1:2:5:2] | passed | 0.06955 seconds |
20
+ ./spec/historiographer_spec.rb[1:2:5:3] | passed | 0.05246 seconds |
21
+ ./spec/historiographer_spec.rb[1:2:6] | passed | 0.04995 seconds |
22
+ ./spec/historiographer_spec.rb[1:2:7] | passed | 0.05019 seconds |
23
+ ./spec/historiographer_spec.rb[1:2:8] | passed | 0.05534 seconds |
24
+ ./spec/historiographer_spec.rb[1:3:1] | passed | 0.05769 seconds |
25
+ ./spec/historiographer_spec.rb[1:4:1] | passed | 0.07179 seconds |
26
+ ./spec/historiographer_spec.rb[1:5:1] | passed | 0.05631 seconds |
27
+ ./spec/historiographer_spec.rb[1:5:2] | passed | 0.0544 seconds |
28
+ ./spec/historiographer_spec.rb[1:6:1] | passed | 0.05814 seconds |
29
+ ./spec/historiographer_spec.rb[1:6:2] | passed | 0.08519 seconds |
30
+ ./spec/historiographer_spec.rb[1:7:1] | passed | 0.0567 seconds |
31
+ ./spec/historiographer_spec.rb[1:7:2] | passed | 0.0759 seconds |
32
+ ./spec/historiographer_spec.rb[1:7:3] | passed | 0.05536 seconds |
33
+ ./spec/historiographer_spec.rb[1:7:4] | passed | 0.05564 seconds |
34
+ ./spec/historiographer_spec.rb[1:7:5] | passed | 0.07313 seconds |
35
+ ./spec/historiographer_spec.rb[1:8:1] | passed | 0.05466 seconds |
36
+ ./spec/historiographer_spec.rb[1:9:1] | passed | 0.07755 seconds |
37
+ ./spec/historiographer_spec.rb[1:10:1] | passed | 0.05444 seconds |
38
+ ./spec/historiographer_spec.rb[1:11:1] | passed | 0.05663 seconds |
39
+ ./spec/historiographer_spec.rb[1:11:2] | passed | 0.07322 seconds |
40
+ ./spec/historiographer_spec.rb[1:11:3] | passed | 0.07853 seconds |
41
+ ./spec/historiographer_spec.rb[1:11:4] | passed | 0.05709 seconds |
42
+ ./spec/historiographer_spec.rb[1:12:1] | passed | 0.07377 seconds |
43
+ ./spec/historiographer_spec.rb[1:12:2] | passed | 0.05676 seconds |
44
+ ./spec/historiographer_spec.rb[1:12:3] | passed | 0.07933 seconds |
45
+ ./spec/historiographer_spec.rb[1:12:4] | passed | 0.07886 seconds |
46
+ ./spec/historiographer_spec.rb[1:12:5] | passed | 0.07857 seconds |
47
+ ./spec/historiographer_spec.rb[1:12:6] | passed | 0.06854 seconds |
48
+ ./spec/historiographer_spec.rb[1:12:7] | passed | 0.08045 seconds |
49
+ ./spec/historiographer_spec.rb[1:13:1] | passed | 0.07652 seconds |
50
+ ./spec/historiographer_spec.rb[1:14:1] | passed | 0.05116 seconds |
51
+ ./spec/historiographer_spec.rb[1:14:2] | passed | 0.06274 seconds |
52
+ ./spec/historiographer_spec.rb[1:14:3] | passed | 0.0698 seconds |
53
+ ./spec/historiographer_spec.rb[1:14:4] | passed | 0.05648 seconds |
54
+ ./spec/historiographer_spec.rb[1:15:1] | passed | 0.05722 seconds |
55
+ ./spec/historiographer_spec.rb[1:15:2] | passed | 0.08476 seconds |
56
+ ./spec/historiographer_spec.rb[1:16:1] | passed | 0.04781 seconds |
57
+ ./spec/historiographer_spec.rb[1:16:2] | passed | 0.04802 seconds |
58
+ ./spec/historiographer_spec.rb[1:16:3] | passed | 0.06019 seconds |
59
+ ./spec/historiographer_spec.rb[1:16:4] | passed | 0.07289 seconds |
60
+ ./spec/historiographer_spec.rb[1:17:1:1] | passed | 0.07235 seconds |
61
+ ./spec/historiographer_spec.rb[1:17:1:2] | passed | 0.06784 seconds |
62
+ ./spec/historiographer_spec.rb[1:17:2:1] | passed | 0.05847 seconds |
62
63
  ./spec/integration/historiographer_safe_integration_spec.rb[1:1:1] | passed | 0.06664 seconds |
63
64
  ./spec/integration/historiographer_safe_integration_spec.rb[1:1:2] | passed | 0.07357 seconds |
64
65
  ./spec/integration/historiographer_safe_integration_spec.rb[1:1:3] | passed | 0.05953 seconds |
@@ -823,6 +823,36 @@ describe Historiographer do
823
823
  expect(post.latest_snapshot.comment_count).to eq 1
824
824
  end
825
825
 
826
+ it "snapshots all children when one has a null_snapshot history record" do
827
+ # This tests a bug where if one child record already has a history with snapshot_id: nil,
828
+ # the snapshot would silently fail to update it due to belongs_to :user validation
829
+ Historiographer::Configuration.mode = :snapshot_only
830
+
831
+ # Create records without triggering histories (snapshot_only mode)
832
+ author1 = Author.create(full_name: 'Author One', history_user_id: user.id)
833
+ author2 = Author.create(full_name: 'Author Two', history_user_id: user.id)
834
+
835
+ # Manually create a null_snapshot history for author1 (simulating previous state)
836
+ # This mimics what happens when a record has a history created outside of snapshot
837
+ AuthorHistory.create!(
838
+ author_id: author1.id,
839
+ full_name: author1.full_name,
840
+ history_user_id: nil, # No user - this will trigger the bug
841
+ snapshot_id: nil, # Null snapshot
842
+ history_started_at: Time.current
843
+ )
844
+
845
+ expect(AuthorHistory.count).to eq 1
846
+ expect(AuthorHistory.where(snapshot_id: nil).count).to eq 1
847
+
848
+ # Now snapshot author1 - this should work even though there's a null_snapshot
849
+ # The bug: without_history_user_id doesn't prevent belongs_to :user validation
850
+ author1.snapshot
851
+
852
+ expect(AuthorHistory.where.not(snapshot_id: nil).count).to eq 1
853
+ expect(AuthorHistory.first.snapshot_id).to be_present
854
+ end
855
+
826
856
  it "doesn't explode" do
827
857
  project = Project.create(name: "test_project")
828
858
  project_file = ProjectFile.create(project: project, name: "test_file", content: "Hello world")
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: historiographer
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.4.4
4
+ version: 4.4.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - brettshollenberger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-09-19 00:00:00.000000000 Z
11
+ date: 2026-01-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord