historiographer 3.0.0 → 3.1.0
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/VERSION +1 -1
- data/historiographer.gemspec +6 -3
- data/lib/historiographer/silent.rb +29 -0
- data/spec/db/migrate/20221018204220_create_silent_posts.rb +21 -0
- data/spec/db/migrate/20221018204255_create_silent_post_histories.rb +9 -0
- data/spec/db/schema.rb +39 -1
- data/spec/historiographer_spec.rb +197 -141
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 193d6326a6d665c589bcf2c67c16434b3cbf6e4bacd1b1d2f28f80d6ce3b229c
|
4
|
+
data.tar.gz: 1e09d0ecf55bf816d444376fc11cbf00a38a8c9f2a2c6ebbbf62f6a7d173282d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 32854e876a7a557c8c0c7008b93cfaf33bd5e06748c97f86890cdcfc269b5baef958555ada0c49c551f035b9b655c41a7036debb908347e028554ec1c55ea5fb
|
7
|
+
data.tar.gz: 3a27716e6159f01260bbb682309d9a82b1bf261cb3ba3eae13f38d8265894a99db938c1473b24a465c63ae24dbf27d0a4d7d66c9a823bf5e16ed0ce6fd8cab6a
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.
|
1
|
+
3.1.0
|
data/historiographer.gemspec
CHANGED
@@ -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 3.
|
5
|
+
# stub: historiographer 3.1.0 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "historiographer".freeze
|
9
|
-
s.version = "3.
|
9
|
+
s.version = "3.1.0"
|
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 = "2022-10-
|
14
|
+
s.date = "2022-10-18"
|
15
15
|
s.description = "Creates separate tables for each history table".freeze
|
16
16
|
s.email = "brett.shollenberger@gmail.com".freeze
|
17
17
|
s.extra_rdoc_files = [
|
@@ -40,6 +40,7 @@ Gem::Specification.new do |s|
|
|
40
40
|
"lib/historiographer/postgres_migration.rb",
|
41
41
|
"lib/historiographer/relation.rb",
|
42
42
|
"lib/historiographer/safe.rb",
|
43
|
+
"lib/historiographer/silent.rb",
|
43
44
|
"spec/db/database.yml",
|
44
45
|
"spec/db/migrate/20161121212228_create_posts.rb",
|
45
46
|
"spec/db/migrate/20161121212229_create_post_histories.rb",
|
@@ -51,6 +52,8 @@ Gem::Specification.new do |s|
|
|
51
52
|
"spec/db/migrate/20191024142304_create_thing_with_compound_index.rb",
|
52
53
|
"spec/db/migrate/20191024142352_create_thing_with_compound_index_history.rb",
|
53
54
|
"spec/db/migrate/20191024203106_create_thing_without_history.rb",
|
55
|
+
"spec/db/migrate/20221018204220_create_silent_posts.rb",
|
56
|
+
"spec/db/migrate/20221018204255_create_silent_post_histories.rb",
|
54
57
|
"spec/db/schema.rb",
|
55
58
|
"spec/examples.txt",
|
56
59
|
"spec/factories/post.rb",
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Historiographer::Silent is intended to be used to migrate an existing model
|
4
|
+
# to Historiographer, not as a long-term solution.
|
5
|
+
#
|
6
|
+
# Historiographer will throw an error if a model is saved without a user present,
|
7
|
+
# unless you explicitly call save_without_history.
|
8
|
+
#
|
9
|
+
# Historiographer::Silent will not throw an error, and will not produce a Rollbar
|
10
|
+
#
|
11
|
+
module Historiographer
|
12
|
+
module Silent
|
13
|
+
extend ActiveSupport::Concern
|
14
|
+
|
15
|
+
included do
|
16
|
+
include Historiographer
|
17
|
+
|
18
|
+
def should_validate_history_user_id_present?
|
19
|
+
false
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def history_user_absent_action
|
25
|
+
# noop
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateSilentPosts < ActiveRecord::Migration[5.1]
|
4
|
+
def change
|
5
|
+
create_table :silent_posts do |t|
|
6
|
+
t.string :title, null: false
|
7
|
+
t.text :body, null: false
|
8
|
+
t.integer :author_id, null: false
|
9
|
+
t.boolean :enabled, default: false
|
10
|
+
t.datetime :live_at
|
11
|
+
t.datetime :deleted_at
|
12
|
+
|
13
|
+
t.timestamps
|
14
|
+
end
|
15
|
+
|
16
|
+
add_index :silent_posts, :author_id
|
17
|
+
add_index :silent_posts, :enabled
|
18
|
+
add_index :silent_posts, :live_at
|
19
|
+
add_index :silent_posts, :deleted_at
|
20
|
+
end
|
21
|
+
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.define(version:
|
13
|
+
ActiveRecord::Schema.define(version: 2022_10_18_204255) do
|
14
14
|
|
15
15
|
# These are extensions that must be enabled in order to support this database
|
16
16
|
enable_extension "plpgsql"
|
@@ -117,6 +117,44 @@ ActiveRecord::Schema.define(version: 2019_10_24_203106) do
|
|
117
117
|
t.index ["live_at"], name: "index_safe_posts_on_live_at"
|
118
118
|
end
|
119
119
|
|
120
|
+
create_table "silent_post_histories", force: :cascade do |t|
|
121
|
+
t.integer "silent_post_id", null: false
|
122
|
+
t.string "title", null: false
|
123
|
+
t.text "body", null: false
|
124
|
+
t.integer "author_id", null: false
|
125
|
+
t.boolean "enabled", default: false
|
126
|
+
t.datetime "live_at"
|
127
|
+
t.datetime "deleted_at"
|
128
|
+
t.datetime "created_at", null: false
|
129
|
+
t.datetime "updated_at", null: false
|
130
|
+
t.datetime "history_started_at", null: false
|
131
|
+
t.datetime "history_ended_at"
|
132
|
+
t.integer "history_user_id"
|
133
|
+
t.index ["author_id"], name: "index_silent_post_histories_on_author_id"
|
134
|
+
t.index ["deleted_at"], name: "index_silent_post_histories_on_deleted_at"
|
135
|
+
t.index ["enabled"], name: "index_silent_post_histories_on_enabled"
|
136
|
+
t.index ["history_ended_at"], name: "index_silent_post_histories_on_history_ended_at"
|
137
|
+
t.index ["history_started_at"], name: "index_silent_post_histories_on_history_started_at"
|
138
|
+
t.index ["history_user_id"], name: "index_silent_post_histories_on_history_user_id"
|
139
|
+
t.index ["live_at"], name: "index_silent_post_histories_on_live_at"
|
140
|
+
t.index ["silent_post_id"], name: "index_silent_post_histories_on_silent_post_id"
|
141
|
+
end
|
142
|
+
|
143
|
+
create_table "silent_posts", force: :cascade do |t|
|
144
|
+
t.string "title", null: false
|
145
|
+
t.text "body", null: false
|
146
|
+
t.integer "author_id", null: false
|
147
|
+
t.boolean "enabled", default: false
|
148
|
+
t.datetime "live_at"
|
149
|
+
t.datetime "deleted_at"
|
150
|
+
t.datetime "created_at", null: false
|
151
|
+
t.datetime "updated_at", null: false
|
152
|
+
t.index ["author_id"], name: "index_silent_posts_on_author_id"
|
153
|
+
t.index ["deleted_at"], name: "index_silent_posts_on_deleted_at"
|
154
|
+
t.index ["enabled"], name: "index_silent_posts_on_enabled"
|
155
|
+
t.index ["live_at"], name: "index_silent_posts_on_live_at"
|
156
|
+
end
|
157
|
+
|
120
158
|
create_table "thing_with_compound_index_histories", force: :cascade do |t|
|
121
159
|
t.integer "thing_with_compound_index_id", null: false
|
122
160
|
t.string "key"
|
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
2
4
|
|
3
5
|
class Post < ActiveRecord::Base
|
4
6
|
include Historiographer
|
@@ -16,6 +18,14 @@ end
|
|
16
18
|
class SafePostHistory < ActiveRecord::Base
|
17
19
|
end
|
18
20
|
|
21
|
+
class SilentPost < ActiveRecord::Base
|
22
|
+
include Historiographer::Silent
|
23
|
+
acts_as_paranoid
|
24
|
+
end
|
25
|
+
|
26
|
+
class SilentPostHistory < ActiveRecord::Base
|
27
|
+
end
|
28
|
+
|
19
29
|
class Author < ActiveRecord::Base
|
20
30
|
include Historiographer
|
21
31
|
end
|
@@ -45,7 +55,7 @@ describe Historiographer do
|
|
45
55
|
Timecop.return
|
46
56
|
end
|
47
57
|
|
48
|
-
let(:username) {
|
58
|
+
let(:username) { 'Test User' }
|
49
59
|
|
50
60
|
let(:user) do
|
51
61
|
User.create(name: username)
|
@@ -53,8 +63,8 @@ describe Historiographer do
|
|
53
63
|
|
54
64
|
let(:create_post) do
|
55
65
|
Post.create(
|
56
|
-
title:
|
57
|
-
body:
|
66
|
+
title: 'Post 1',
|
67
|
+
body: 'Great post',
|
58
68
|
author_id: 1,
|
59
69
|
history_user_id: user.id
|
60
70
|
)
|
@@ -62,43 +72,43 @@ describe Historiographer do
|
|
62
72
|
|
63
73
|
let(:create_author) do
|
64
74
|
Author.create(
|
65
|
-
full_name:
|
75
|
+
full_name: 'Breezy',
|
66
76
|
history_user_id: user.id
|
67
77
|
)
|
68
78
|
end
|
69
79
|
|
70
|
-
describe
|
71
|
-
it
|
72
|
-
expect
|
80
|
+
describe 'History counting' do
|
81
|
+
it 'creates history on creation of primary model record' do
|
82
|
+
expect do
|
73
83
|
create_post
|
74
|
-
|
84
|
+
end.to change {
|
75
85
|
PostHistory.count
|
76
86
|
}.by 1
|
77
87
|
end
|
78
88
|
|
79
|
-
it
|
89
|
+
it 'appends new history on update' do
|
80
90
|
post = create_post
|
81
91
|
|
82
|
-
expect
|
83
|
-
post.update(title:
|
84
|
-
|
92
|
+
expect do
|
93
|
+
post.update(title: 'Better Title')
|
94
|
+
end.to change {
|
85
95
|
PostHistory.count
|
86
96
|
}.by 1
|
87
97
|
end
|
88
98
|
|
89
|
-
it
|
99
|
+
it 'does not append new history if nothing has changed' do
|
90
100
|
post = create_post
|
91
101
|
|
92
|
-
expect
|
102
|
+
expect do
|
93
103
|
post.update(title: post.title)
|
94
|
-
|
104
|
+
end.to_not change {
|
95
105
|
PostHistory.count
|
96
106
|
}
|
97
107
|
end
|
98
108
|
end
|
99
109
|
|
100
|
-
describe
|
101
|
-
it
|
110
|
+
describe 'History recording' do
|
111
|
+
it 'records all fields from the parent' do
|
102
112
|
post = create_post
|
103
113
|
post_history = post.histories.first
|
104
114
|
|
@@ -110,8 +120,8 @@ describe Historiographer do
|
|
110
120
|
expect(post_history.history_ended_at).to be_nil
|
111
121
|
expect(post_history.history_user_id).to eq user.id
|
112
122
|
|
113
|
-
post.update(title:
|
114
|
-
post_histories = post.histories.reload.order(
|
123
|
+
post.update(title: 'Better title')
|
124
|
+
post_histories = post.histories.reload.order('id asc')
|
115
125
|
first_history = post_histories.first
|
116
126
|
second_history = post_histories.second
|
117
127
|
|
@@ -119,24 +129,24 @@ describe Historiographer do
|
|
119
129
|
expect(second_history.history_ended_at).to be_nil
|
120
130
|
end
|
121
131
|
|
122
|
-
it
|
132
|
+
it 'cannot create without history_user_id' do
|
123
133
|
post = Post.create(
|
124
|
-
title:
|
125
|
-
body:
|
126
|
-
author_id: 1
|
127
|
-
)
|
128
|
-
expect(post.errors.to_h).to eq(
|
134
|
+
title: 'Post 1',
|
135
|
+
body: 'Great post',
|
136
|
+
author_id: 1
|
137
|
+
)
|
138
|
+
expect(post.errors.to_h).to eq(history_user_id: 'must be an integer')
|
129
139
|
|
130
|
-
expect
|
140
|
+
expect do
|
131
141
|
post.send(:record_history)
|
132
|
-
|
142
|
+
end.to raise_error(
|
133
143
|
Historiographer::HistoryUserIdMissingError
|
134
144
|
)
|
135
145
|
end
|
136
146
|
|
137
|
-
context
|
138
|
-
context
|
139
|
-
it
|
147
|
+
context 'When directly hitting the database via SQL' do
|
148
|
+
context '#update_all' do
|
149
|
+
it 'still updates histories' do
|
140
150
|
FactoryBot.create_list(:post, 3, history_user_id: 1)
|
141
151
|
|
142
152
|
posts = Post.all
|
@@ -144,12 +154,12 @@ describe Historiographer do
|
|
144
154
|
expect(PostHistory.count).to eq 3
|
145
155
|
expect(posts.map(&:histories).map(&:count)).to all (eq 1)
|
146
156
|
|
147
|
-
posts.update_all(title:
|
157
|
+
posts.update_all(title: 'My New Post Title', history_user_id: 1)
|
148
158
|
|
149
159
|
expect(PostHistory.count).to eq 6
|
150
160
|
expect(PostHistory.current.count).to eq 3
|
151
161
|
expect(posts.map(&:histories).map(&:count)).to all(eq 2)
|
152
|
-
expect(posts.map(&:current_history).map(&:title)).to all (eq
|
162
|
+
expect(posts.map(&:current_history).map(&:title)).to all (eq 'My New Post Title')
|
153
163
|
expect(Post.all).to respond_to :has_histories?
|
154
164
|
|
155
165
|
# It can update by sub-query
|
@@ -159,10 +169,10 @@ describe Historiographer do
|
|
159
169
|
expect(posts.second.histories.count).to eq 2
|
160
170
|
expect(posts.third.histories.count).to eq 3
|
161
171
|
expect(posts.first.title).to eq "Brett's Post"
|
162
|
-
expect(posts.second.title).to eq
|
172
|
+
expect(posts.second.title).to eq 'My New Post Title'
|
163
173
|
expect(posts.third.title).to eq "Brett's Post"
|
164
174
|
expect(posts.first.current_history.title).to eq "Brett's Post"
|
165
|
-
expect(posts.second.current_history.title).to eq
|
175
|
+
expect(posts.second.current_history.title).to eq 'My New Post Title'
|
166
176
|
expect(posts.third.current_history.title).to eq "Brett's Post"
|
167
177
|
|
168
178
|
# It does not update histories if nothing changed
|
@@ -170,23 +180,23 @@ describe Historiographer do
|
|
170
180
|
posts = Post.all.reload.order(:id)
|
171
181
|
expect(posts.map(&:histories).map(&:count)).to all(eq 3)
|
172
182
|
|
173
|
-
posts.update_all_without_history(title:
|
183
|
+
posts.update_all_without_history(title: 'Untracked')
|
174
184
|
expect(posts.first.histories.count).to eq 3
|
175
185
|
expect(posts.second.histories.count).to eq 3
|
176
186
|
expect(posts.third.histories.count).to eq 3
|
177
187
|
|
178
|
-
thing1 = ThingWithoutHistory.create(name:
|
179
|
-
thing2 = ThingWithoutHistory.create(name:
|
188
|
+
thing1 = ThingWithoutHistory.create(name: 'Thing 1')
|
189
|
+
thing2 = ThingWithoutHistory.create(name: 'Thing 2')
|
180
190
|
|
181
|
-
ThingWithoutHistory.all.update_all(name:
|
191
|
+
ThingWithoutHistory.all.update_all(name: 'Thing 3')
|
182
192
|
|
183
|
-
expect(ThingWithoutHistory.all.map(&:name)).to all(eq
|
193
|
+
expect(ThingWithoutHistory.all.map(&:name)).to all(eq 'Thing 3')
|
184
194
|
expect(ThingWithoutHistory.all).to_not respond_to :has_histories?
|
185
195
|
expect(ThingWithoutHistory.all).to_not respond_to :update_all_without_history
|
186
196
|
expect(ThingWithoutHistory.all).to_not respond_to :delete_all_without_history
|
187
197
|
end
|
188
|
-
|
189
|
-
it
|
198
|
+
|
199
|
+
it 'respects safety' do
|
190
200
|
FactoryBot.create_list(:post, 3, history_user_id: 1)
|
191
201
|
|
192
202
|
posts = Post.all
|
@@ -194,35 +204,35 @@ describe Historiographer do
|
|
194
204
|
expect(PostHistory.count).to eq 3
|
195
205
|
expect(posts.map(&:histories).map(&:count)).to all (eq 1)
|
196
206
|
|
197
|
-
expect
|
198
|
-
posts.update_all(title:
|
199
|
-
|
207
|
+
expect do
|
208
|
+
posts.update_all(title: 'My New Post Title')
|
209
|
+
end.to raise_error
|
200
210
|
|
201
211
|
posts.reload.map(&:title).each do |title|
|
202
|
-
expect(title).to_not eq
|
212
|
+
expect(title).to_not eq 'My New Post Title'
|
203
213
|
end
|
204
214
|
|
205
215
|
SafePost.create(
|
206
|
-
title:
|
207
|
-
body:
|
208
|
-
author_id: 1
|
216
|
+
title: 'Post 1',
|
217
|
+
body: 'Great post',
|
218
|
+
author_id: 1
|
209
219
|
)
|
210
220
|
|
211
221
|
safe_posts = SafePost.all
|
212
222
|
|
213
|
-
expect
|
214
|
-
safe_posts.update_all(title:
|
215
|
-
|
223
|
+
expect do
|
224
|
+
safe_posts.update_all(title: 'New One')
|
225
|
+
end.to_not raise_error
|
216
226
|
|
217
|
-
expect(safe_posts.map(&:title)).to all(eq
|
227
|
+
expect(safe_posts.map(&:title)).to all(eq 'New One')
|
218
228
|
end
|
219
229
|
end
|
220
230
|
|
221
|
-
context
|
222
|
-
it
|
231
|
+
context '#delete_all' do
|
232
|
+
it 'includes histories when not paranoid' do
|
223
233
|
Timecop.freeze
|
224
234
|
authors = 3.times.map do
|
225
|
-
Author.create(full_name:
|
235
|
+
Author.create(full_name: 'Brett', history_user_id: 1)
|
226
236
|
end
|
227
237
|
Author.delete_all(history_user_id: 1)
|
228
238
|
expect(AuthorHistory.count).to eq 3
|
@@ -232,7 +242,7 @@ describe Historiographer do
|
|
232
242
|
Timecop.return
|
233
243
|
end
|
234
244
|
|
235
|
-
it
|
245
|
+
it 'includes histories when paranoid' do
|
236
246
|
Timecop.freeze
|
237
247
|
posts = FactoryBot.create_list(:post, 3, history_user_id: 1)
|
238
248
|
Post.delete_all(history_user_id: 1)
|
@@ -246,9 +256,9 @@ describe Historiographer do
|
|
246
256
|
Timecop.return
|
247
257
|
end
|
248
258
|
|
249
|
-
it
|
259
|
+
it 'allows delete_all_without_history' do
|
250
260
|
authors = 3.times.map do
|
251
|
-
Author.create(full_name:
|
261
|
+
Author.create(full_name: 'Brett', history_user_id: 1)
|
252
262
|
end
|
253
263
|
Author.all.delete_all_without_history
|
254
264
|
expect(AuthorHistory.current.count).to eq 3
|
@@ -256,8 +266,8 @@ describe Historiographer do
|
|
256
266
|
end
|
257
267
|
end
|
258
268
|
|
259
|
-
context
|
260
|
-
it
|
269
|
+
context '#destroy_all' do
|
270
|
+
it 'includes histories' do
|
261
271
|
Timecop.freeze
|
262
272
|
posts = FactoryBot.create_list(:post, 3, history_user_id: 1)
|
263
273
|
Post.destroy_all(history_user_id: 1)
|
@@ -271,7 +281,7 @@ describe Historiographer do
|
|
271
281
|
Timecop.return
|
272
282
|
end
|
273
283
|
|
274
|
-
it
|
284
|
+
it 'destroys without histories' do
|
275
285
|
Timecop.freeze
|
276
286
|
posts = FactoryBot.create_list(:post, 3, history_user_id: 1)
|
277
287
|
Post.all.destroy_all_without_history
|
@@ -283,79 +293,125 @@ describe Historiographer do
|
|
283
293
|
end
|
284
294
|
end
|
285
295
|
|
286
|
-
context
|
287
|
-
it
|
288
|
-
expect(Rollbar).to receive(:error).with(
|
296
|
+
context 'When Safe mode' do
|
297
|
+
it 'creates history without history_user_id' do
|
298
|
+
expect(Rollbar).to receive(:error).with('history_user_id must be passed in order to save record with histories! If you are in a context with no history_user_id, explicitly call #save_without_history')
|
289
299
|
|
290
300
|
post = SafePost.create(
|
291
|
-
title:
|
292
|
-
body:
|
293
|
-
author_id: 1
|
294
|
-
)
|
301
|
+
title: 'Post 1',
|
302
|
+
body: 'Great post',
|
303
|
+
author_id: 1
|
304
|
+
)
|
295
305
|
expect(post.errors.to_h.keys).to be_empty
|
296
306
|
expect(post).to be_persisted
|
297
307
|
expect(post.histories.count).to eq 1
|
298
308
|
expect(post.histories.first.history_user_id).to be_nil
|
299
309
|
end
|
300
310
|
|
301
|
-
it
|
311
|
+
it 'creates history with history_user_id' do
|
302
312
|
expect(Rollbar).to_not receive(:error)
|
303
313
|
|
304
314
|
post = SafePost.create(
|
305
|
-
title:
|
306
|
-
body:
|
315
|
+
title: 'Post 1',
|
316
|
+
body: 'Great post',
|
307
317
|
author_id: 1,
|
308
318
|
history_user_id: user.id
|
309
|
-
)
|
319
|
+
)
|
310
320
|
expect(post.errors.to_h.keys).to be_empty
|
311
321
|
expect(post).to be_persisted
|
312
322
|
expect(post.histories.count).to eq 1
|
313
323
|
expect(post.histories.first.history_user_id).to eq user.id
|
314
324
|
end
|
315
325
|
|
316
|
-
|
326
|
+
it 'skips history creation if desired' do
|
317
327
|
post = SafePost.new(
|
318
|
-
title:
|
319
|
-
body:
|
328
|
+
title: 'Post 1',
|
329
|
+
body: 'Great post',
|
320
330
|
author_id: 1
|
321
|
-
)
|
331
|
+
)
|
322
332
|
|
323
333
|
post.save_without_history
|
324
334
|
expect(post).to be_persisted
|
325
335
|
expect(post.histories.count).to eq 0
|
326
|
-
|
336
|
+
end
|
327
337
|
end
|
328
338
|
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
339
|
+
context 'When Silent mode' do
|
340
|
+
it 'creates history without history_user_id' do
|
341
|
+
expect(Rollbar).to_not receive(:error)
|
342
|
+
|
343
|
+
post = SilentPost.create(
|
344
|
+
title: 'Post 1',
|
345
|
+
body: 'Great post',
|
346
|
+
author_id: 1
|
347
|
+
)
|
348
|
+
expect(post.errors.to_h.keys).to be_empty
|
349
|
+
expect(post).to be_persisted
|
350
|
+
expect(post.histories.count).to eq 1
|
351
|
+
expect(post.histories.first.history_user_id).to be_nil
|
352
|
+
|
353
|
+
post.update(title: 'New Title')
|
354
|
+
post.reload
|
355
|
+
expect(post.title).to eq 'New Title' # No error was raised
|
356
|
+
end
|
357
|
+
|
358
|
+
it 'creates history with history_user_id' do
|
359
|
+
expect(Rollbar).to_not receive(:error)
|
360
|
+
|
361
|
+
post = SilentPost.create(
|
362
|
+
title: 'Post 1',
|
363
|
+
body: 'Great post',
|
334
364
|
author_id: 1,
|
335
|
-
|
365
|
+
history_user_id: user.id
|
366
|
+
)
|
367
|
+
expect(post.errors.to_h.keys).to be_empty
|
368
|
+
expect(post).to be_persisted
|
369
|
+
expect(post.histories.count).to eq 1
|
370
|
+
expect(post.histories.first.history_user_id).to eq user.id
|
371
|
+
end
|
372
|
+
|
373
|
+
it 'skips history creation if desired' do
|
374
|
+
post = SilentPost.new(
|
375
|
+
title: 'Post 1',
|
376
|
+
body: 'Great post',
|
377
|
+
author_id: 1
|
378
|
+
)
|
379
|
+
|
380
|
+
post.save_without_history
|
381
|
+
expect(post).to be_persisted
|
382
|
+
expect(post.histories.count).to eq 0
|
383
|
+
end
|
384
|
+
end
|
385
|
+
it 'can override without history_user_id' do
|
386
|
+
expect do
|
387
|
+
post = Post.new(
|
388
|
+
title: 'Post 1',
|
389
|
+
body: 'Great post',
|
390
|
+
author_id: 1
|
391
|
+
)
|
336
392
|
|
337
393
|
post.save_without_history
|
338
|
-
|
394
|
+
end.to_not raise_error
|
339
395
|
end
|
340
396
|
|
341
|
-
it
|
342
|
-
expect
|
397
|
+
it 'can override without history_user_id' do
|
398
|
+
expect do
|
343
399
|
post = Post.new(
|
344
|
-
title:
|
345
|
-
body:
|
346
|
-
author_id: 1
|
347
|
-
)
|
400
|
+
title: 'Post 1',
|
401
|
+
body: 'Great post',
|
402
|
+
author_id: 1
|
403
|
+
)
|
348
404
|
|
349
405
|
post.save_without_history!
|
350
|
-
|
406
|
+
end.to_not raise_error
|
351
407
|
end
|
352
408
|
|
353
|
-
it
|
409
|
+
it 'does not record histories when main model fails to save' do
|
354
410
|
class Post
|
355
411
|
after_save :raise_error, prepend: true
|
356
412
|
|
357
413
|
def raise_error
|
358
|
-
raise
|
414
|
+
raise 'Oh no, db issue!'
|
359
415
|
end
|
360
416
|
end
|
361
417
|
|
@@ -367,21 +423,21 @@ describe Historiographer do
|
|
367
423
|
end
|
368
424
|
end
|
369
425
|
|
370
|
-
describe
|
371
|
-
it
|
426
|
+
describe 'Scopes' do
|
427
|
+
it 'finds current histories' do
|
372
428
|
post1 = create_post
|
373
|
-
post1.update(title:
|
429
|
+
post1.update(title: 'Better title')
|
374
430
|
|
375
431
|
post2 = create_post
|
376
|
-
post2.update(title:
|
432
|
+
post2.update(title: 'Better title')
|
377
433
|
|
378
|
-
expect(PostHistory.current.pluck(:title)).to all eq
|
379
|
-
expect(post1.current_history.title).to eq
|
434
|
+
expect(PostHistory.current.pluck(:title)).to all eq 'Better title'
|
435
|
+
expect(post1.current_history.title).to eq 'Better title'
|
380
436
|
end
|
381
437
|
end
|
382
438
|
|
383
|
-
describe
|
384
|
-
it
|
439
|
+
describe 'Associations' do
|
440
|
+
it 'names associated records' do
|
385
441
|
post1 = create_post
|
386
442
|
expect(post1.histories.first).to be_a(PostHistory)
|
387
443
|
|
@@ -394,27 +450,27 @@ describe Historiographer do
|
|
394
450
|
end
|
395
451
|
end
|
396
452
|
|
397
|
-
describe
|
398
|
-
it
|
453
|
+
describe 'Histories' do
|
454
|
+
it 'does not allow direct updates of histories' do
|
399
455
|
post1 = create_post
|
400
456
|
hist1 = post1.histories.first
|
401
457
|
|
402
|
-
expect(hist1.update(title:
|
458
|
+
expect(hist1.update(title: 'A different title')).to be false
|
403
459
|
expect(hist1.reload.title).to eq post1.title
|
404
460
|
|
405
|
-
expect(hist1.update!(title:
|
461
|
+
expect(hist1.update!(title: 'A different title')).to be false
|
406
462
|
expect(hist1.reload.title).to eq post1.title
|
407
463
|
|
408
|
-
hist1.title =
|
464
|
+
hist1.title = 'A different title'
|
409
465
|
expect(hist1.save).to be false
|
410
466
|
expect(hist1.reload.title).to eq post1.title
|
411
467
|
|
412
|
-
hist1.title =
|
468
|
+
hist1.title = 'A different title'
|
413
469
|
expect(hist1.save!).to be false
|
414
470
|
expect(hist1.reload.title).to eq post1.title
|
415
471
|
end
|
416
472
|
|
417
|
-
it
|
473
|
+
it 'does not allow destroys of histories' do
|
418
474
|
post1 = create_post
|
419
475
|
hist1 = post1.histories.first
|
420
476
|
original_history_count = post1.histories.count
|
@@ -425,19 +481,19 @@ describe Historiographer do
|
|
425
481
|
expect(post1.histories.count).to be original_history_count
|
426
482
|
end
|
427
483
|
end
|
428
|
-
|
429
|
-
describe
|
430
|
-
it
|
484
|
+
|
485
|
+
describe 'Deletion' do
|
486
|
+
it 'records deleted_at and history_user_id on primary and history if you use acts_as_paranoid' do
|
431
487
|
post = Post.create(
|
432
|
-
title:
|
433
|
-
body:
|
488
|
+
title: 'Post 1',
|
489
|
+
body: 'Great post',
|
434
490
|
author_id: 1,
|
435
491
|
history_user_id: user.id
|
436
492
|
)
|
437
493
|
|
438
|
-
expect
|
494
|
+
expect do
|
439
495
|
post.destroy(history_user_id: 2)
|
440
|
-
|
496
|
+
end.to change {
|
441
497
|
PostHistory.count
|
442
498
|
}.by 1
|
443
499
|
|
@@ -447,23 +503,23 @@ describe Historiographer do
|
|
447
503
|
expect(PostHistory.last.history_user_id).to eq 2
|
448
504
|
end
|
449
505
|
|
450
|
-
it
|
451
|
-
post = SafePost.create(title:
|
506
|
+
it 'works with Historiographer::Safe' do
|
507
|
+
post = SafePost.create(title: 'HELLO', body: 'YO', author_id: 1)
|
452
508
|
|
453
|
-
expect
|
509
|
+
expect do
|
454
510
|
post.destroy
|
455
|
-
|
511
|
+
end.to_not raise_error
|
456
512
|
|
457
513
|
expect(SafePost.count).to eq 0
|
458
514
|
expect(post.deleted_at).to_not be_nil
|
459
515
|
expect(SafePostHistory.count).to eq 2
|
460
516
|
expect(SafePostHistory.current.last.deleted_at).to eq post.deleted_at
|
461
517
|
|
462
|
-
post2 = SafePost.create(title:
|
518
|
+
post2 = SafePost.create(title: 'HELLO', body: 'YO', author_id: 1)
|
463
519
|
|
464
|
-
expect
|
520
|
+
expect do
|
465
521
|
post2.destroy!
|
466
|
-
|
522
|
+
end.to_not raise_error
|
467
523
|
|
468
524
|
expect(SafePost.count).to eq 0
|
469
525
|
expect(post2.deleted_at).to_not be_nil
|
@@ -472,18 +528,18 @@ describe Historiographer do
|
|
472
528
|
end
|
473
529
|
end
|
474
530
|
|
475
|
-
describe
|
476
|
-
it
|
531
|
+
describe 'Scopes' do
|
532
|
+
it 'finds current' do
|
477
533
|
post = create_post
|
478
|
-
post.update(title:
|
479
|
-
post.update(title:
|
534
|
+
post.update(title: 'New Title')
|
535
|
+
post.update(title: 'New Title 2')
|
480
536
|
|
481
537
|
expect(PostHistory.current.count).to be 1
|
482
538
|
end
|
483
539
|
end
|
484
540
|
|
485
|
-
describe
|
486
|
-
it
|
541
|
+
describe 'User associations' do
|
542
|
+
it 'links to user' do
|
487
543
|
post = create_post
|
488
544
|
author = create_author
|
489
545
|
|
@@ -492,10 +548,10 @@ describe Historiographer do
|
|
492
548
|
end
|
493
549
|
end
|
494
550
|
|
495
|
-
describe
|
496
|
-
it
|
497
|
-
indices_sql =
|
498
|
-
SELECT
|
551
|
+
describe 'Migrations with compound indexes' do
|
552
|
+
it 'supports renaming compound indexes and migrating them to history tables' do
|
553
|
+
indices_sql = "
|
554
|
+
SELECT
|
499
555
|
DISTINCT(
|
500
556
|
ARRAY_TO_STRING(ARRAY(
|
501
557
|
SELECT pg_get_indexdef(idx.indexrelid, k + 1, true)
|
@@ -514,19 +570,19 @@ describe Historiographer do
|
|
514
570
|
AND a.attnum = ANY(idx.indkey)
|
515
571
|
AND t.relkind = 'r'
|
516
572
|
AND t.relname = ?;
|
517
|
-
|
573
|
+
"
|
518
574
|
|
519
575
|
indices_query_array = [indices_sql, :thing_with_compound_index_histories]
|
520
576
|
indices_sanitized_query = ThingWithCompoundIndexHistory.send(:sanitize_sql_array, indices_query_array)
|
521
577
|
|
522
|
-
indexes = ThingWithCompoundIndexHistory.connection.execute(indices_sanitized_query).to_a.map(&:values).flatten.map { |i| i.split(
|
578
|
+
indexes = ThingWithCompoundIndexHistory.connection.execute(indices_sanitized_query).to_a.map(&:values).flatten.map { |i| i.split(',') }
|
523
579
|
|
524
|
-
expect(indexes).to include([
|
525
|
-
expect(indexes).to include([
|
526
|
-
expect(indexes).to include([
|
527
|
-
expect(indexes).to include([
|
528
|
-
expect(indexes).to include([
|
529
|
-
expect(indexes).to include([
|
580
|
+
expect(indexes).to include(['history_started_at'])
|
581
|
+
expect(indexes).to include(['history_ended_at'])
|
582
|
+
expect(indexes).to include(['history_user_id'])
|
583
|
+
expect(indexes).to include(['id'])
|
584
|
+
expect(indexes).to include(%w[key value])
|
585
|
+
expect(indexes).to include(['thing_with_compound_index_id'])
|
530
586
|
end
|
531
587
|
end
|
532
588
|
end
|
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: 3.
|
4
|
+
version: 3.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- brettshollenberger
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-10-
|
11
|
+
date: 2022-10-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -235,6 +235,7 @@ files:
|
|
235
235
|
- lib/historiographer/postgres_migration.rb
|
236
236
|
- lib/historiographer/relation.rb
|
237
237
|
- lib/historiographer/safe.rb
|
238
|
+
- lib/historiographer/silent.rb
|
238
239
|
- spec/db/database.yml
|
239
240
|
- spec/db/migrate/20161121212228_create_posts.rb
|
240
241
|
- spec/db/migrate/20161121212229_create_post_histories.rb
|
@@ -246,6 +247,8 @@ files:
|
|
246
247
|
- spec/db/migrate/20191024142304_create_thing_with_compound_index.rb
|
247
248
|
- spec/db/migrate/20191024142352_create_thing_with_compound_index_history.rb
|
248
249
|
- spec/db/migrate/20191024203106_create_thing_without_history.rb
|
250
|
+
- spec/db/migrate/20221018204220_create_silent_posts.rb
|
251
|
+
- spec/db/migrate/20221018204255_create_silent_post_histories.rb
|
249
252
|
- spec/db/schema.rb
|
250
253
|
- spec/examples.txt
|
251
254
|
- spec/factories/post.rb
|