historiographer 1.3.0 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cceab0e39bc86b8e410a55d5c5b23556ad8ecf543fae2720d0761cbfc9070b1d
4
- data.tar.gz: 70fd87e0ecf2fbf6829a50ac315746bced3e2533e0a3664ce005c237557265bd
3
+ metadata.gz: f2d85a348f2e7196cccef533c9562b53c9449df6a0dde03509a1d48a5d66c490
4
+ data.tar.gz: f3b9f822e53fdadc04d20a0f4066b8b47e019c11333d9dccc0140ea1e9f12991
5
5
  SHA512:
6
- metadata.gz: e77e1c8d9dc96c8d6db182b568529fc1d16e1947dc6e16a1234109a90ad3f02229ec9532ba560ea56246fa79d9ccc928801a885a9a6b5a14d2d132202d3cb220
7
- data.tar.gz: 9e94ed498ca13062259f8031bfbd529ae516261e7a08e46a32e73bec86a066df4d9d83e1ea70754a7260c0f5fa38a933bcc4ce1431cffbd0a2944fbd3f21c686
6
+ metadata.gz: 5011fe73d7a09abd888f362b1e3923436c5f29068f9b46650ac4e39f09ab3226d7f2d66072293d9908dc37693a7afc5b73f73c2db2c1f6f1b0e9119c153c511b
7
+ data.tar.gz: ccca124547a124739d68183b9374ddd1b8fc452ed29b568da485981abb37cab8f055384065b00016981ea3844177c5e7e050d0fb6e5f8faef7844e5835387591
data/README.md CHANGED
@@ -1,40 +1,99 @@
1
- # historiographer
1
+ # Historiographer
2
2
 
3
- Losing data sucks. Every time you update a record in Rails, by default, you lose the old data.
3
+ Losing data sucks. Every time you update or destroy a record in Rails, you lose the old data.
4
4
 
5
- Supported for PostgreSQL and MySQL. If you need other adapters, help us make one!
5
+ Historiographer fixes this problem in a better way than existing auditing gems.
6
6
 
7
7
  ## Existing auditing gems for Rails suck
8
8
 
9
9
  The Audited gem has some serious flaws.
10
10
 
11
- First, it only tracks a record of what changed, so there's no way to "go back in time" and see what the data looked like back when a problem occurred without replaying every single audit.
11
+ 🤚Hands up if your `versions` table has gotten too big to query 🤚
12
+
13
+ 🤚Hands up if your `versions` table doesn't have the indexes you need 🤚
14
+
15
+ 🤚Hands up if you've ever iterated over `versions` records in Ruby to recreate a snapshot of what data looked like at a point in time. 🤚
16
+
17
+ Why does this happen?
18
+
19
+ First, `audited` only tracks a record of what changed, so there's no way to "go back in time" and see what the data looked like back when a problem occurred without replaying every single audit.
12
20
 
13
21
  Second, it tracks changes as JSON. While some data stores have JSON querying semantics, not all do, making it very hard to ask complex questions of your historical data -- that's the whole reason you're keeping it around.
14
22
 
15
- Third, it doesn't maintain efficient indexes on your data. If you maintain an index on the primary records, wouldn't you also want to look up historical records using the same columns? Historical data is MUCH larger than "latest snapshot" data, so, duh, of course you do.
23
+ Third, it doesn't maintain indexes on your data. If you maintain an index on the primary table, wouldn't you also want to look up historical records using the same columns? Historical data is MUCH larger than "latest snapshot" data, so, duh, of course you do.
24
+
25
+ Finally, Audited creates just one table for all audits. Historical data is big. It's not unusual for an audited gem table to get into the many millions of rows, and need to be constantly partitioned to maintain any kind of efficiency.
26
+
27
+ ## How does Historiographer solve these problems?
28
+
29
+ Historiographer introduces the concept of _history tables:_ append-only tables that have the same structure and indexes as your primary table.
30
+
31
+ If you have a `posts` table:
32
+
33
+ | id | title |
34
+ | :----------- | :----------- |
35
+ | 1 | My Great Post |
36
+ | 2 | My Second Post |
16
37
 
17
- Finally, Audited creates one table for every audit. As mentioned before, historical data is big. It's not unusual for an audited gem table to get into the many millions of rows, and need to be constnatly partitioned to maintain any kind of efficiency.
38
+ You'll also have a `post_histories_table`:
18
39
 
19
- ## How does Historiographer solve the problem?
40
+ | id | post_id | title | history_started_at | history_ended_at | history_user_id |
41
+ | :----------- | :----------- | :----------- | :----------- | :----------- | :----------- |
42
+ | 1 | 1 | My Great Post | '2019-11-08' | NULL | 1 |
43
+ | 2 | 2| My Second Post | '2019-11-08' | NULL | 1 |
20
44
 
21
- You have existing code written in Rails, so our existing queries need to Just Work.
45
+ If you change the title of the 1st post:
22
46
 
23
- Moreover, there are benefits to the Active Record model of updating records in place: the latest snapshot is cached, and accessing it is efficient.
47
+ ```Post.find(1).update(title: "Title With Better SEO", history_user_id: current_user.id)```
24
48
 
25
- So how can we get the benefits of caching but NOT losing data, and continue to create, update, and destroy like we normally would in Rails?
49
+ You'll expect your `posts` table to be updated directly:
26
50
 
27
- Historiographer introduces the concept of _history tables:_ tables that have the exact same structure as tables storing the latest snapshots of data. So if you have a `posts` table, you'll also have a `post_histories` table with all the same columns and indexes.
51
+ | id | title |
52
+ | :----------- | :----------- |
53
+ | 1 | Title With Better SEO |
54
+ | 2 | My Second Post |
28
55
 
29
- Whenever you include the `Historiographer` gem in your ActiveRecord model, it allows you to insert, update, or delete data as you normally would. If the changes are successful, it also inserts a new history snapshot in the histories table--an exact snapshot of the data at that point in time.
56
+ But also, your `histories` table will be updated:
30
57
 
31
- These tables feature two useful indexes: `history_started_at` and `history_ended_at`, which allow you to see what the data looked like and when. You can easily create views which show the total lifecycle of a piece of data, or restore earlier versions directly from these snapshots.
58
+ | id | post_id | title | history_started_at | history_ended_at | history_user_id |
59
+ | :----------- | :----------- | :----------- | :----------- | :----------- | :----------- |
60
+ | 1 | 1 | My Great Post | '2019-11-08' | '2019-11-09' | 1 |
61
+ | 2 | 2| My Second Post | '2019-11-08' | NULL | 1 |
62
+ | 1 | 1 | Title With Better SEO | '2019-11-09' | NULL | 1 |
32
63
 
33
- And of course, it contains a migrations tool to help you write the same migrations you normally would without having to worry about also synchronizing histories tables.
64
+ A few things have happened here:
34
65
 
35
- # basic use
66
+ 1. The primary table (`posts`) is updated directly
67
+ 2. The existing history for `post_id=1` is timestamped when its `history_ended_at`, so that we can see when the post had the title "My Great Post"
68
+ 3. A new history record is appended to the table containing a complete snapshot of the record, and a `NULL` `history_ended_at`. That's because this is the current history.
69
+ 4. A record of _who_ made the change is saved (`history_user_id`). You can join to your users table to see more data.
70
+
71
+ # Getting Started
72
+
73
+ Whenever you include the `Historiographer` gem in your ActiveRecord model, it allows you to insert, update, or delete data as you normally would.
74
+
75
+ ```ruby
76
+ class Post < ActiveRecord::Base
77
+ include Historiographer
78
+ end
79
+ ```
36
80
 
37
- ## migrations
81
+ By default, Historiographer will require all SQL-backed methods to provide a `history_user_id` to track who made the changes.
82
+
83
+ ```ruby
84
+ Post.create(title: "My Post", history_user_id: current_user.id) # => OK
85
+ Post.create(title: "My Post") # => Error!
86
+ ```
87
+
88
+ Many existing models will be better off using `Historiographer::Safe` when getting started, which will not raise an error, but will alert you of locations where your app is missing `history_user_id`.
89
+
90
+ ```ruby
91
+ class Post < ActiveRecord::Base
92
+ include Historiographer::Safe
93
+ end
94
+ ```
95
+
96
+ ## Create A Migration
38
97
 
39
98
  You need a separate table to store histories for each model.
40
99
 
@@ -80,25 +139,7 @@ Additionally it will add indices on:
80
139
  - The same columns that had indices on the original model (e.g. `enabled`)
81
140
  - `history_started_at`, `history_ended_at`, and `history_user_id`
82
141
 
83
- ### what to do when generated index names are too long
84
-
85
- Sometimes the generated index names are too long. Just like with standard Rails migrations, you can override the name of the index to fix this problem. To do so, use the `index_names` argument to override individual index names:
86
-
87
- ```ruby
88
- require "historiographer/postgres_migration"
89
- class CreatePostHistories < ActiveRecord::Migration
90
- def change
91
- create_table :post_histories do |t|
92
- t.histories, index_names: {
93
- title: "my_index_name",
94
- [:compound, :index] => "my_compound_index_name"
95
- }
96
- end
97
- end
98
- end
99
- ```
100
-
101
- ## models
142
+ ## Models
102
143
 
103
144
  The primary model should include `Historiographer`:
104
145
 
@@ -127,6 +168,18 @@ p.histories.first.post == p
127
168
  # => true
128
169
  ```
129
170
 
171
+ ## Creating, Updating, and Destroying Data:
172
+
173
+ You can just use normal ActiveRecord methods, and all will record histories:
174
+
175
+ ```ruby
176
+ Post.create(title: "My Great Title", history_user_id: current_user.id)
177
+ Post.find_by(title: "My Great Title").update(title: "A New Title", history_user_id: current_user.id)
178
+ Post.update_all(title: "They're all the same!", history_user_id: current_user.id)
179
+ Post.last.destroy!(history_user_id: current_user.id)
180
+ Post.destroy_all(history_user_id: current_user.id)
181
+ ```
182
+
130
183
  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.
131
184
 
132
185
  ```ruby
@@ -136,7 +189,25 @@ p.current_history
136
189
  PostHistory.current
137
190
  ```
138
191
 
192
+ ### What to do when generated index names are too long
193
+
194
+ Sometimes the generated index names are too long. Just like with standard Rails migrations, you can override the name of the index to fix this problem. To do so, use the `index_names` argument to override individual index names:
195
+
196
+ ```ruby
197
+ require "historiographer/postgres_migration"
198
+ class CreatePostHistories < ActiveRecord::Migration
199
+ def change
200
+ create_table :post_histories do |t|
201
+ t.histories, index_names: {
202
+ title: "my_index_name",
203
+ [:compound, :index] => "my_compound_index_name"
204
+ }
205
+ end
206
+ end
207
+ end
208
+ ```
209
+
139
210
  == Copyright
140
211
 
141
- Copyright (c) 2016-2018 brettshollenberger. See LICENSE.txt for
212
+ Copyright (c) 2016-2020 brettshollenberger. See LICENSE.txt for
142
213
  further details.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.3.0
1
+ 1.3.1
@@ -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 1.3.0 ruby lib
5
+ # stub: historiographer 1.3.1 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "historiographer".freeze
9
- s.version = "1.3.0"
9
+ s.version = "1.3.1"
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 = "2019-11-08"
14
+ s.date = "2019-11-11"
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 = [
@@ -2,6 +2,7 @@ require "active_support/all"
2
2
  require_relative "./historiographer/history"
3
3
  require_relative "./historiographer/postgres_migration"
4
4
  require_relative "./historiographer/safe"
5
+ require_relative "./historiographer/relation"
5
6
 
6
7
  # Historiographer takes "histories" (think audits or snapshots) of your model whenever you make changes.
7
8
  #
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: 1.3.0
4
+ version: 1.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - brettshollenberger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-11-08 00:00:00.000000000 Z
11
+ date: 2019-11-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord