historiographer 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/.standalone_migrations +6 -0
- data/Gemfile +29 -0
- data/Gemfile.lock +196 -0
- data/Guardfile +70 -0
- data/LICENSE.txt +20 -0
- data/README.md +124 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/historiographer.gemspec +108 -0
- data/init.rb +18 -0
- data/lib/historiographer/history.rb +175 -0
- data/lib/historiographer/history_migration.rb +78 -0
- data/lib/historiographer/history_migration_mysql.rb +54 -0
- data/lib/historiographer/mysql_migration.rb +7 -0
- data/lib/historiographer/postgres_migration.rb +7 -0
- data/lib/historiographer/safe.rb +33 -0
- data/lib/historiographer.rb +235 -0
- data/spec/db/database.yml +27 -0
- data/spec/db/migrate/20161121212228_create_posts.rb +19 -0
- data/spec/db/migrate/20161121212229_create_post_histories.rb +10 -0
- data/spec/db/migrate/20161121212230_create_authors.rb +13 -0
- data/spec/db/migrate/20161121212231_create_author_histories.rb +10 -0
- data/spec/db/migrate/20161121212232_create_users.rb +9 -0
- data/spec/db/migrate/20171011194624_create_safe_posts.rb +19 -0
- data/spec/db/migrate/20171011194715_create_safe_post_histories.rb +9 -0
- data/spec/db/schema.rb +121 -0
- data/spec/examples.txt +21 -0
- data/spec/historiographer_spec.rb +395 -0
- data/spec/spec_helper.rb +40 -0
- metadata +258 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 86529a3c7a63d3e483af58e83afbef705db737e51b437b07ca37e4b4af02f288
|
4
|
+
data.tar.gz: 16ecaf9cfebadfe4446f6b79f121907abc25160d84d6dc57374b185989d755e2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 164aa52bbf3238314d5d5c32956f6392c5d447b1c8bff5b54ba83e10d431d6b90c786ee187ebea1df2b63a03e9694a67163418e05a0c350f088a64213332d383
|
7
|
+
data.tar.gz: d86e11e66229aca81eaca8291d23393c72873c1bf8d8c194030b087796a2f5ae8123f4361f9dff59e1884301f12d2b15f1f82e3cd51bef0e0194add2618abc21
|
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.6.3
|
data/Gemfile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
ruby "2.6.3"
|
3
|
+
|
4
|
+
gem "activerecord", "~> 5.1"
|
5
|
+
gem "activesupport"
|
6
|
+
gem "rollbar"
|
7
|
+
|
8
|
+
group :development, :test do
|
9
|
+
gem "pg"
|
10
|
+
gem "pry"
|
11
|
+
gem "mysql2", "0.4.10"
|
12
|
+
gem "standalone_migrations"
|
13
|
+
gem "timecop"
|
14
|
+
gem "paranoia"
|
15
|
+
end
|
16
|
+
|
17
|
+
group :development do
|
18
|
+
gem "rdoc", "~> 3.12"
|
19
|
+
gem "bundler", "~> 1.0"
|
20
|
+
gem "jeweler"
|
21
|
+
gem "simplecov", ">= 0"
|
22
|
+
end
|
23
|
+
|
24
|
+
group :test do
|
25
|
+
gem "rspec"
|
26
|
+
gem "guard"
|
27
|
+
gem "guard-rspec"
|
28
|
+
gem "database_cleaner"
|
29
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,196 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
actionpack (5.2.3)
|
5
|
+
actionview (= 5.2.3)
|
6
|
+
activesupport (= 5.2.3)
|
7
|
+
rack (~> 2.0)
|
8
|
+
rack-test (>= 0.6.3)
|
9
|
+
rails-dom-testing (~> 2.0)
|
10
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
11
|
+
actionview (5.2.3)
|
12
|
+
activesupport (= 5.2.3)
|
13
|
+
builder (~> 3.1)
|
14
|
+
erubi (~> 1.4)
|
15
|
+
rails-dom-testing (~> 2.0)
|
16
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
17
|
+
activemodel (5.2.3)
|
18
|
+
activesupport (= 5.2.3)
|
19
|
+
activerecord (5.2.3)
|
20
|
+
activemodel (= 5.2.3)
|
21
|
+
activesupport (= 5.2.3)
|
22
|
+
arel (>= 9.0)
|
23
|
+
activesupport (5.2.3)
|
24
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
25
|
+
i18n (>= 0.7, < 2)
|
26
|
+
minitest (~> 5.1)
|
27
|
+
tzinfo (~> 1.1)
|
28
|
+
addressable (2.4.0)
|
29
|
+
arel (9.0.0)
|
30
|
+
builder (3.2.3)
|
31
|
+
coderay (1.1.2)
|
32
|
+
concurrent-ruby (1.1.5)
|
33
|
+
crass (1.0.4)
|
34
|
+
database_cleaner (1.7.0)
|
35
|
+
descendants_tracker (0.0.4)
|
36
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
37
|
+
diff-lcs (1.3)
|
38
|
+
docile (1.3.2)
|
39
|
+
erubi (1.8.0)
|
40
|
+
faraday (0.9.2)
|
41
|
+
multipart-post (>= 1.2, < 3)
|
42
|
+
ffi (1.11.1)
|
43
|
+
formatador (0.2.5)
|
44
|
+
git (1.5.0)
|
45
|
+
github_api (0.16.0)
|
46
|
+
addressable (~> 2.4.0)
|
47
|
+
descendants_tracker (~> 0.0.4)
|
48
|
+
faraday (~> 0.8, < 0.10)
|
49
|
+
hashie (>= 3.4)
|
50
|
+
mime-types (>= 1.16, < 3.0)
|
51
|
+
oauth2 (~> 1.0)
|
52
|
+
guard (2.15.0)
|
53
|
+
formatador (>= 0.2.4)
|
54
|
+
listen (>= 2.7, < 4.0)
|
55
|
+
lumberjack (>= 1.0.12, < 2.0)
|
56
|
+
nenv (~> 0.1)
|
57
|
+
notiffany (~> 0.0)
|
58
|
+
pry (>= 0.9.12)
|
59
|
+
shellany (~> 0.0)
|
60
|
+
thor (>= 0.18.1)
|
61
|
+
guard-compat (1.2.1)
|
62
|
+
guard-rspec (4.7.3)
|
63
|
+
guard (~> 2.1)
|
64
|
+
guard-compat (~> 1.1)
|
65
|
+
rspec (>= 2.99.0, < 4.0)
|
66
|
+
hashie (3.6.0)
|
67
|
+
highline (2.0.2)
|
68
|
+
i18n (1.6.0)
|
69
|
+
concurrent-ruby (~> 1.0)
|
70
|
+
jeweler (2.3.9)
|
71
|
+
builder
|
72
|
+
bundler
|
73
|
+
git (>= 1.2.5)
|
74
|
+
github_api (~> 0.16.0)
|
75
|
+
highline (>= 1.6.15)
|
76
|
+
nokogiri (>= 1.5.10)
|
77
|
+
psych
|
78
|
+
rake
|
79
|
+
rdoc
|
80
|
+
semver2
|
81
|
+
json (1.8.6)
|
82
|
+
jwt (2.2.1)
|
83
|
+
listen (3.1.5)
|
84
|
+
rb-fsevent (~> 0.9, >= 0.9.4)
|
85
|
+
rb-inotify (~> 0.9, >= 0.9.7)
|
86
|
+
ruby_dep (~> 1.2)
|
87
|
+
loofah (2.2.3)
|
88
|
+
crass (~> 1.0.2)
|
89
|
+
nokogiri (>= 1.5.9)
|
90
|
+
lumberjack (1.0.13)
|
91
|
+
method_source (0.9.2)
|
92
|
+
mime-types (2.99.3)
|
93
|
+
mini_portile2 (2.4.0)
|
94
|
+
minitest (5.11.3)
|
95
|
+
multi_json (1.13.1)
|
96
|
+
multi_xml (0.6.0)
|
97
|
+
multipart-post (2.1.1)
|
98
|
+
mysql2 (0.4.10)
|
99
|
+
nenv (0.3.0)
|
100
|
+
nokogiri (1.10.4)
|
101
|
+
mini_portile2 (~> 2.4.0)
|
102
|
+
notiffany (0.1.3)
|
103
|
+
nenv (~> 0.1)
|
104
|
+
shellany (~> 0.0)
|
105
|
+
oauth2 (1.4.1)
|
106
|
+
faraday (>= 0.8, < 0.16.0)
|
107
|
+
jwt (>= 1.0, < 3.0)
|
108
|
+
multi_json (~> 1.3)
|
109
|
+
multi_xml (~> 0.5)
|
110
|
+
rack (>= 1.2, < 3)
|
111
|
+
paranoia (2.4.2)
|
112
|
+
activerecord (>= 4.0, < 6.1)
|
113
|
+
pg (1.1.4)
|
114
|
+
pry (0.12.2)
|
115
|
+
coderay (~> 1.1.0)
|
116
|
+
method_source (~> 0.9.0)
|
117
|
+
psych (3.1.0)
|
118
|
+
rack (2.0.7)
|
119
|
+
rack-test (1.1.0)
|
120
|
+
rack (>= 1.0, < 3)
|
121
|
+
rails-dom-testing (2.0.3)
|
122
|
+
activesupport (>= 4.2.0)
|
123
|
+
nokogiri (>= 1.6)
|
124
|
+
rails-html-sanitizer (1.2.0)
|
125
|
+
loofah (~> 2.2, >= 2.2.2)
|
126
|
+
railties (5.2.3)
|
127
|
+
actionpack (= 5.2.3)
|
128
|
+
activesupport (= 5.2.3)
|
129
|
+
method_source
|
130
|
+
rake (>= 0.8.7)
|
131
|
+
thor (>= 0.19.0, < 2.0)
|
132
|
+
rake (12.3.3)
|
133
|
+
rb-fsevent (0.10.3)
|
134
|
+
rb-inotify (0.10.0)
|
135
|
+
ffi (~> 1.0)
|
136
|
+
rdoc (3.12.2)
|
137
|
+
json (~> 1.4)
|
138
|
+
rollbar (2.21.0)
|
139
|
+
rspec (3.8.0)
|
140
|
+
rspec-core (~> 3.8.0)
|
141
|
+
rspec-expectations (~> 3.8.0)
|
142
|
+
rspec-mocks (~> 3.8.0)
|
143
|
+
rspec-core (3.8.2)
|
144
|
+
rspec-support (~> 3.8.0)
|
145
|
+
rspec-expectations (3.8.4)
|
146
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
147
|
+
rspec-support (~> 3.8.0)
|
148
|
+
rspec-mocks (3.8.1)
|
149
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
150
|
+
rspec-support (~> 3.8.0)
|
151
|
+
rspec-support (3.8.2)
|
152
|
+
ruby_dep (1.5.0)
|
153
|
+
semver2 (3.4.2)
|
154
|
+
shellany (0.0.1)
|
155
|
+
simplecov (0.17.0)
|
156
|
+
docile (~> 1.1)
|
157
|
+
json (>= 1.8, < 3)
|
158
|
+
simplecov-html (~> 0.10.0)
|
159
|
+
simplecov-html (0.10.2)
|
160
|
+
standalone_migrations (5.2.7)
|
161
|
+
activerecord (>= 4.2.7, < 5.3.0)
|
162
|
+
railties (>= 4.2.7, < 5.3.0)
|
163
|
+
rake (>= 10.0)
|
164
|
+
thor (0.20.3)
|
165
|
+
thread_safe (0.3.6)
|
166
|
+
timecop (0.9.1)
|
167
|
+
tzinfo (1.2.5)
|
168
|
+
thread_safe (~> 0.1)
|
169
|
+
|
170
|
+
PLATFORMS
|
171
|
+
ruby
|
172
|
+
|
173
|
+
DEPENDENCIES
|
174
|
+
activerecord (~> 5.1)
|
175
|
+
activesupport
|
176
|
+
bundler (~> 1.0)
|
177
|
+
database_cleaner
|
178
|
+
guard
|
179
|
+
guard-rspec
|
180
|
+
jeweler
|
181
|
+
mysql2 (= 0.4.10)
|
182
|
+
paranoia
|
183
|
+
pg
|
184
|
+
pry
|
185
|
+
rdoc (~> 3.12)
|
186
|
+
rollbar
|
187
|
+
rspec
|
188
|
+
simplecov
|
189
|
+
standalone_migrations
|
190
|
+
timecop
|
191
|
+
|
192
|
+
RUBY VERSION
|
193
|
+
ruby 2.6.3p62
|
194
|
+
|
195
|
+
BUNDLED WITH
|
196
|
+
1.17.2
|
data/Guardfile
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
## Uncomment and set this to only include directories you want to watch
|
5
|
+
# directories %w(app lib config test spec features) \
|
6
|
+
# .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
|
7
|
+
|
8
|
+
## Note: if you are using the `directories` clause above and you are not
|
9
|
+
## watching the project directory ('.'), then you will want to move
|
10
|
+
## the Guardfile to a watched dir and symlink it back, e.g.
|
11
|
+
#
|
12
|
+
# $ mkdir config
|
13
|
+
# $ mv Guardfile config/
|
14
|
+
# $ ln -s config/Guardfile .
|
15
|
+
#
|
16
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
17
|
+
|
18
|
+
# Note: The cmd option is now required due to the increasing number of ways
|
19
|
+
# rspec may be run, below are examples of the most common uses.
|
20
|
+
# * bundler: 'bundle exec rspec'
|
21
|
+
# * bundler binstubs: 'bin/rspec'
|
22
|
+
# * spring: 'bin/rspec' (This will use spring if running and you have
|
23
|
+
# installed the spring binstubs per the docs)
|
24
|
+
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
25
|
+
# * 'just' rspec: 'rspec'
|
26
|
+
|
27
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
28
|
+
require "guard/rspec/dsl"
|
29
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
30
|
+
|
31
|
+
# Feel free to open issues for suggestions and improvements
|
32
|
+
|
33
|
+
# RSpec files
|
34
|
+
rspec = dsl.rspec
|
35
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
36
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
37
|
+
watch(rspec.spec_files)
|
38
|
+
|
39
|
+
# Ruby files
|
40
|
+
ruby = dsl.ruby
|
41
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
42
|
+
|
43
|
+
# Rails files
|
44
|
+
rails = dsl.rails(view_extensions: %w(erb haml slim))
|
45
|
+
dsl.watch_spec_files_for(rails.app_files)
|
46
|
+
dsl.watch_spec_files_for(rails.views)
|
47
|
+
|
48
|
+
watch(rails.controllers) do |m|
|
49
|
+
[
|
50
|
+
rspec.spec.call("routing/#{m[1]}_routing"),
|
51
|
+
rspec.spec.call("controllers/#{m[1]}_controller"),
|
52
|
+
rspec.spec.call("acceptance/#{m[1]}")
|
53
|
+
]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Rails config changes
|
57
|
+
watch(rails.spec_helper) { rspec.spec_dir }
|
58
|
+
watch(rails.routes) { "#{rspec.spec_dir}/routing" }
|
59
|
+
watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
|
60
|
+
|
61
|
+
# Capybara features specs
|
62
|
+
watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") }
|
63
|
+
watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") }
|
64
|
+
|
65
|
+
# Turnip features and steps
|
66
|
+
watch(%r{^spec/acceptance/(.+)\.feature$})
|
67
|
+
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
|
68
|
+
Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
|
69
|
+
end
|
70
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2016 Brett Shollenberger
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
# historiographer
|
2
|
+
|
3
|
+
Losing data sucks. Every time you update a record in Rails, by default, you lose the old data.
|
4
|
+
|
5
|
+
Supported for PostgreSQL and MySQL. If you need other adapters, help us make one!
|
6
|
+
|
7
|
+
## Existing auditing gems for Rails suck
|
8
|
+
|
9
|
+
The Audited gem has some serious flaws.
|
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.
|
12
|
+
|
13
|
+
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
|
+
|
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.
|
16
|
+
|
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.
|
18
|
+
|
19
|
+
## How does Historiographer solve the problem?
|
20
|
+
|
21
|
+
You have existing code written in Rails, so our existing queries need to Just Work.
|
22
|
+
|
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.
|
24
|
+
|
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?
|
26
|
+
|
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.
|
28
|
+
|
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.
|
30
|
+
|
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.
|
32
|
+
|
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.
|
34
|
+
|
35
|
+
# basic use
|
36
|
+
|
37
|
+
## migrations
|
38
|
+
|
39
|
+
You need a separate table to store histories for each model.
|
40
|
+
|
41
|
+
So if you have a Posts model:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
class CreatePosts < ActiveRecord::Migration
|
45
|
+
def change
|
46
|
+
create_table :posts do |t|
|
47
|
+
t.string :title, null: false
|
48
|
+
t.boolean :enabled
|
49
|
+
end
|
50
|
+
add_index :posts, :enabled
|
51
|
+
end
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
You should create a model named *posts_histories*:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
require "historiographer/postgres_migration"
|
59
|
+
class CreatePostHistories < ActiveRecord::Migration
|
60
|
+
def change
|
61
|
+
create_table :post_histories do |t|
|
62
|
+
t.histories
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
```
|
67
|
+
|
68
|
+
The `t.histories` method will automatically create a table with the following columns:
|
69
|
+
|
70
|
+
* `id` (because every model has a primary key)
|
71
|
+
* `post\_id` (because this is the foreign key)
|
72
|
+
* `title` (because it was on the original model)
|
73
|
+
* `enabled` (because it was on the original model)
|
74
|
+
* `history\_started\_at` (to denote when this history became the canonical version)
|
75
|
+
* `history\_ended\_at` (to denote when this history was no longer the canonical version, if it has stopped being the canonical version)
|
76
|
+
* `history\_user\_id` (to denote the user that made this change, if one is known)
|
77
|
+
|
78
|
+
Additionally it will add indices on:
|
79
|
+
|
80
|
+
* The same columns that had indices on the original model (e.g. `enabled`)
|
81
|
+
* `history\_started\_at`, `history\_ended\_at`, and `history\_user\_id`
|
82
|
+
|
83
|
+
## models
|
84
|
+
|
85
|
+
The primary model should include `Historiographer`:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
class Post
|
89
|
+
include Historiographer
|
90
|
+
end
|
91
|
+
```
|
92
|
+
|
93
|
+
You should also make a `PostHistory` class if you're going to query `PostHistory` from Rails:
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
class PostHistory
|
97
|
+
end
|
98
|
+
```
|
99
|
+
|
100
|
+
The `Posts` class will acquire a `histories` method, and the `PostHistory` model will gain a `post` method:
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
p = Post.first
|
104
|
+
p.histories.first.class
|
105
|
+
|
106
|
+
# => "PostHistory"
|
107
|
+
|
108
|
+
p.histories.first.post == p
|
109
|
+
# => true
|
110
|
+
```
|
111
|
+
|
112
|
+
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.
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
p = Post.first
|
116
|
+
p.current_history
|
117
|
+
|
118
|
+
PostHistory.current
|
119
|
+
```
|
120
|
+
|
121
|
+
== Copyright
|
122
|
+
|
123
|
+
Copyright (c) 2016-2018 brettshollenberger. See LICENSE.txt for
|
124
|
+
further details.
|
data/Rakefile
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
require 'pry'
|
6
|
+
begin
|
7
|
+
Bundler.setup(:default, :development)
|
8
|
+
rescue Bundler::BundlerError => e
|
9
|
+
$stderr.puts e.message
|
10
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
11
|
+
exit e.status_code
|
12
|
+
end
|
13
|
+
require 'rake'
|
14
|
+
require 'jeweler'
|
15
|
+
|
16
|
+
Jeweler::Tasks.new do |gem|
|
17
|
+
# gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
|
18
|
+
gem.name = "historiographer"
|
19
|
+
gem.homepage = "http://github.com/brettshollenberger/historiographer"
|
20
|
+
gem.license = "MIT"
|
21
|
+
gem.summary = %Q{Create histories of your ActiveRecord tables}
|
22
|
+
gem.description = %Q{Creates separate tables for each history table}
|
23
|
+
gem.email = "brett.shollenberger@gmail.com"
|
24
|
+
gem.authors = ["brettshollenberger"]
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
Rake::TestTask.new(:test) do |test|
|
30
|
+
test.libs << 'lib' << 'spec'
|
31
|
+
test.pattern = 'rspec/**/*_spec.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "Code coverage detail"
|
36
|
+
task :simplecov do
|
37
|
+
ENV['COVERAGE'] = "true"
|
38
|
+
Rake::Task['test'].execute
|
39
|
+
end
|
40
|
+
|
41
|
+
task :default => :test
|
42
|
+
|
43
|
+
require 'rdoc/task'
|
44
|
+
Rake::RDocTask.new do |rdoc|
|
45
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
46
|
+
|
47
|
+
rdoc.rdoc_dir = 'rdoc'
|
48
|
+
rdoc.title = "historiographer #{version}"
|
49
|
+
rdoc.rdoc_files.include('README*')
|
50
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
51
|
+
end
|
52
|
+
|
53
|
+
require 'standalone_migrations'
|
54
|
+
StandaloneMigrations::Tasks.load_tasks
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
# stub: historiographer 1.0.0 ruby lib
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "historiographer".freeze
|
9
|
+
s.version = "1.0.0"
|
10
|
+
|
11
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
12
|
+
s.require_paths = ["lib".freeze]
|
13
|
+
s.authors = ["brettshollenberger".freeze]
|
14
|
+
s.date = "2019-08-22"
|
15
|
+
s.description = "Creates separate tables for each history table".freeze
|
16
|
+
s.email = "brett.shollenberger@gmail.com".freeze
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"LICENSE.txt",
|
19
|
+
"README.md"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
".document",
|
23
|
+
".rspec",
|
24
|
+
".ruby-version",
|
25
|
+
".standalone_migrations",
|
26
|
+
"Gemfile",
|
27
|
+
"Gemfile.lock",
|
28
|
+
"Guardfile",
|
29
|
+
"LICENSE.txt",
|
30
|
+
"README.md",
|
31
|
+
"Rakefile",
|
32
|
+
"VERSION",
|
33
|
+
"historiographer.gemspec",
|
34
|
+
"init.rb",
|
35
|
+
"lib/historiographer.rb",
|
36
|
+
"lib/historiographer/history.rb",
|
37
|
+
"lib/historiographer/history_migration.rb",
|
38
|
+
"lib/historiographer/history_migration_mysql.rb",
|
39
|
+
"lib/historiographer/mysql_migration.rb",
|
40
|
+
"lib/historiographer/postgres_migration.rb",
|
41
|
+
"lib/historiographer/safe.rb",
|
42
|
+
"spec/db/database.yml",
|
43
|
+
"spec/db/migrate/20161121212228_create_posts.rb",
|
44
|
+
"spec/db/migrate/20161121212229_create_post_histories.rb",
|
45
|
+
"spec/db/migrate/20161121212230_create_authors.rb",
|
46
|
+
"spec/db/migrate/20161121212231_create_author_histories.rb",
|
47
|
+
"spec/db/migrate/20161121212232_create_users.rb",
|
48
|
+
"spec/db/migrate/20171011194624_create_safe_posts.rb",
|
49
|
+
"spec/db/migrate/20171011194715_create_safe_post_histories.rb",
|
50
|
+
"spec/db/schema.rb",
|
51
|
+
"spec/examples.txt",
|
52
|
+
"spec/historiographer_spec.rb",
|
53
|
+
"spec/spec_helper.rb"
|
54
|
+
]
|
55
|
+
s.homepage = "http://github.com/brettshollenberger/historiographer".freeze
|
56
|
+
s.licenses = ["MIT".freeze]
|
57
|
+
s.rubygems_version = "3.0.3".freeze
|
58
|
+
s.summary = "Create histories of your ActiveRecord tables".freeze
|
59
|
+
|
60
|
+
if s.respond_to? :specification_version then
|
61
|
+
s.specification_version = 4
|
62
|
+
|
63
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
64
|
+
s.add_runtime_dependency(%q<activerecord>.freeze, ["~> 5.1"])
|
65
|
+
s.add_runtime_dependency(%q<activesupport>.freeze, [">= 0"])
|
66
|
+
s.add_runtime_dependency(%q<rollbar>.freeze, [">= 0"])
|
67
|
+
s.add_development_dependency(%q<pg>.freeze, [">= 0"])
|
68
|
+
s.add_development_dependency(%q<pry>.freeze, [">= 0"])
|
69
|
+
s.add_development_dependency(%q<mysql2>.freeze, ["= 0.4.10"])
|
70
|
+
s.add_development_dependency(%q<standalone_migrations>.freeze, [">= 0"])
|
71
|
+
s.add_development_dependency(%q<timecop>.freeze, [">= 0"])
|
72
|
+
s.add_development_dependency(%q<paranoia>.freeze, [">= 0"])
|
73
|
+
s.add_development_dependency(%q<rdoc>.freeze, ["~> 3.12"])
|
74
|
+
s.add_development_dependency(%q<bundler>.freeze, ["~> 1.0"])
|
75
|
+
s.add_development_dependency(%q<jeweler>.freeze, [">= 0"])
|
76
|
+
s.add_development_dependency(%q<simplecov>.freeze, [">= 0"])
|
77
|
+
else
|
78
|
+
s.add_dependency(%q<activerecord>.freeze, ["~> 5.1"])
|
79
|
+
s.add_dependency(%q<activesupport>.freeze, [">= 0"])
|
80
|
+
s.add_dependency(%q<rollbar>.freeze, [">= 0"])
|
81
|
+
s.add_dependency(%q<pg>.freeze, [">= 0"])
|
82
|
+
s.add_dependency(%q<pry>.freeze, [">= 0"])
|
83
|
+
s.add_dependency(%q<mysql2>.freeze, ["= 0.4.10"])
|
84
|
+
s.add_dependency(%q<standalone_migrations>.freeze, [">= 0"])
|
85
|
+
s.add_dependency(%q<timecop>.freeze, [">= 0"])
|
86
|
+
s.add_dependency(%q<paranoia>.freeze, [">= 0"])
|
87
|
+
s.add_dependency(%q<rdoc>.freeze, ["~> 3.12"])
|
88
|
+
s.add_dependency(%q<bundler>.freeze, ["~> 1.0"])
|
89
|
+
s.add_dependency(%q<jeweler>.freeze, [">= 0"])
|
90
|
+
s.add_dependency(%q<simplecov>.freeze, [">= 0"])
|
91
|
+
end
|
92
|
+
else
|
93
|
+
s.add_dependency(%q<activerecord>.freeze, ["~> 5.1"])
|
94
|
+
s.add_dependency(%q<activesupport>.freeze, [">= 0"])
|
95
|
+
s.add_dependency(%q<rollbar>.freeze, [">= 0"])
|
96
|
+
s.add_dependency(%q<pg>.freeze, [">= 0"])
|
97
|
+
s.add_dependency(%q<pry>.freeze, [">= 0"])
|
98
|
+
s.add_dependency(%q<mysql2>.freeze, ["= 0.4.10"])
|
99
|
+
s.add_dependency(%q<standalone_migrations>.freeze, [">= 0"])
|
100
|
+
s.add_dependency(%q<timecop>.freeze, [">= 0"])
|
101
|
+
s.add_dependency(%q<paranoia>.freeze, [">= 0"])
|
102
|
+
s.add_dependency(%q<rdoc>.freeze, ["~> 3.12"])
|
103
|
+
s.add_dependency(%q<bundler>.freeze, ["~> 1.0"])
|
104
|
+
s.add_dependency(%q<jeweler>.freeze, [">= 0"])
|
105
|
+
s.add_dependency(%q<simplecov>.freeze, [">= 0"])
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
data/init.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "yaml"
|
2
|
+
|
3
|
+
Bundler.require(:default, :development, :test)
|
4
|
+
|
5
|
+
Dir.glob(File.expand_path("lib/**/*.rb")).each do |file|
|
6
|
+
require file
|
7
|
+
end
|
8
|
+
|
9
|
+
database_config = YAML.load(File.open(File.expand_path("spec/db/database.yml")).read)
|
10
|
+
|
11
|
+
env = ENV["HISTORIOGRAPHER_ENV"] || "development"
|
12
|
+
|
13
|
+
db_env_config = database_config[env]
|
14
|
+
|
15
|
+
if defined?(ActiveRecord::Base)
|
16
|
+
# new settings as specified here: https://devcenter.heroku.com/articles/concurrency-and-database-connections
|
17
|
+
ActiveRecord::Base.establish_connection(db_env_config)
|
18
|
+
end
|