hoardable 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rubocop.yml +10 -0
- data/.tool-versions +2 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +205 -0
- data/Rakefile +16 -0
- data/lib/generators/hoardable/migration_generator.rb +22 -0
- data/lib/generators/hoardable/templates/migration.rb.erb +12 -0
- data/lib/hoardable/hoardable.rb +46 -0
- data/lib/hoardable/model.rb +78 -0
- data/lib/hoardable/version_model.rb +72 -0
- data/lib/hoardable.rb +6 -0
- data/sig/hoardable.rbs +4 -0
- metadata +141 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 24eee9ce1b76bd2d5a40a6b527ef4e048c25850ea454f75b2838f45b21339c25
|
4
|
+
data.tar.gz: e464f7418ec2527ed6fe598b4575481a01d50fea236631749d0722bdd328c66f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a617926045371fa040ae329241fe37a5ea58ea4fdcd43c2c66f0c3fe2b89cc0b5c2723371c88c9946b4bda4cf20d477a936494a32f6e8e39b9a1b5e727c7e9b4
|
7
|
+
data.tar.gz: 283ee7c613a1d07b227e32dc823914ea2cdc9b144a9b9d03c0155adf21cdeeacecd92ba8d173d2c0c4bf17febbd1343571898db7c5e24ea828e0f261ae82cbb7
|
data/.rubocop.yml
ADDED
data/.tool-versions
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2022 justin talbott
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
# Hoardable
|
2
|
+
|
3
|
+
Hoardable is an ActiveRecord extension for Ruby 2.6+, Rails 6.1+, and PostgreSQL that allows for
|
4
|
+
versioning and soft-deletion of records through the use of **uni-temporal inherited tables**.
|
5
|
+
|
6
|
+
### Huh?
|
7
|
+
|
8
|
+
[Temporal tables](https://en.wikipedia.org/wiki/Temporal_database) are a database design pattern
|
9
|
+
where each row contains data as well as one or more time ranges. In the case of a temporal table
|
10
|
+
representing versions, each row has one time range representing the row’s valid time range, hence
|
11
|
+
"uni-temporal".
|
12
|
+
|
13
|
+
[Table inheritance](https://www.postgresql.org/docs/14/ddl-inherit.html) is a feature of PostgreSQL
|
14
|
+
that allows a table to inherit all columns of another table. The descendant table’s schema will stay
|
15
|
+
in sync with all columns that it inherits from it’s parent. If a new column or removed from the
|
16
|
+
parent, the schema change is reflected on its descendants.
|
17
|
+
|
18
|
+
With these principles combined, `hoardable` offers a simple and effective model versioning system,
|
19
|
+
where versions of records are stored in a separate, inherited table with the validity time range and
|
20
|
+
other versioning data.
|
21
|
+
|
22
|
+
## Installation
|
23
|
+
|
24
|
+
Add this line to your application's Gemfile:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
gem 'hoardable'
|
28
|
+
```
|
29
|
+
|
30
|
+
And then execute `bundle install`.
|
31
|
+
|
32
|
+
### Model Installation
|
33
|
+
|
34
|
+
First, include `Hoardable::Model` into a model you would like to hoard versions of:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
class Post < ActiveRecord::Base
|
38
|
+
include Hoardable::Model
|
39
|
+
belongs_to :user
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
Then, run the generator command to create a database migration and migrate it:
|
44
|
+
|
45
|
+
```
|
46
|
+
bin/rails g hoardable:migration posts
|
47
|
+
bin/rails db:migrate
|
48
|
+
```
|
49
|
+
|
50
|
+
## Usage
|
51
|
+
|
52
|
+
### Basics
|
53
|
+
|
54
|
+
Once you include `Hoardable::Model` into a model, it will dynamically generate a "Version" subclass
|
55
|
+
of that model. Continuing our example above:
|
56
|
+
|
57
|
+
```
|
58
|
+
>> Post
|
59
|
+
=> Post(id: integer, body: text, user_id: integer, created_at: datetime)
|
60
|
+
>> PostVersion
|
61
|
+
=> PostVersion(id: integer, body: text, user_id: integer, created_at: datetime, _data: jsonb, _during: tsrange, post_id: integer)
|
62
|
+
```
|
63
|
+
|
64
|
+
A `Post` now `has_many :versions` which are created on every update and deletion of a `Post` (by
|
65
|
+
default):
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
post_id = post.id
|
69
|
+
post.versions.size # => 0
|
70
|
+
post.update!(title: "Title")
|
71
|
+
post.versions.size # => 1
|
72
|
+
post.destroy!
|
73
|
+
post.reload # => ActiveRecord::RecordNotFound
|
74
|
+
PostVersion.where(post_id: post_id).size # => 2
|
75
|
+
```
|
76
|
+
|
77
|
+
Each `PostVersion` has access to the same attributes, relationships, and other model behavior that
|
78
|
+
`Post` has, but is a read-only record.
|
79
|
+
|
80
|
+
If you ever need to revert to a specific version, you can call `version.revert!` on it. If the
|
81
|
+
source post had been deleted, this will untrash it with it’s original primary key.
|
82
|
+
|
83
|
+
### Querying and Temporal Lookup
|
84
|
+
|
85
|
+
Since a `PostVersion` is just a normal `ActiveRecord`, you can query them like another model
|
86
|
+
resource, i.e:
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
post.versions.where(user_id: Current.user.id, body: nil)
|
90
|
+
```
|
91
|
+
|
92
|
+
If you want to look-up the version of a `Post` at a specific time, you can use the `.at` method:
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
post.at(1.day.ago) # => #<PostVersion:0x000000010d44fa30>
|
96
|
+
```
|
97
|
+
|
98
|
+
By default, `hoardable` will keep copies of records you have destroyed. You can query for them as
|
99
|
+
well:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
PostVersion.trashed
|
103
|
+
```
|
104
|
+
|
105
|
+
_Note:_ Creating an inherited table does not copy over the indexes from the parent table. If you
|
106
|
+
need to query versions often, you will need to add those indexes to the `_versions` tables manually.
|
107
|
+
|
108
|
+
### Tracking contextual data
|
109
|
+
|
110
|
+
You’ll often want to track contextual data about a version. `hoardable` will automatically capture
|
111
|
+
the ActiveRecord `changes` hash and `operation` that cause the version (`update` or `delete`).
|
112
|
+
|
113
|
+
There are also 3 other optional keys that are provided for tracking contextual information:
|
114
|
+
|
115
|
+
- `whodunit` - an identifier for who is responsible for creating the version
|
116
|
+
- `note` - a string containing a description regarding the versioning
|
117
|
+
- `meta` - any other contextual information you’d like to store along with the version
|
118
|
+
|
119
|
+
This information is stored in a `jsonb` column. Each key’s value can be in the format of your
|
120
|
+
choosing.
|
121
|
+
|
122
|
+
One convenient way to assign this contextual data is with a proc in an initializer, i.e.:
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
Hoardable.whodunit = -> { Current.user&.id }
|
126
|
+
```
|
127
|
+
|
128
|
+
You can also set this context manually as well, just remember to clear them afterwards.
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
Hoardable.note = "reverting due to accidental deletion"
|
132
|
+
post.update!(title: "We’re back!")
|
133
|
+
Hoardable.note = nil
|
134
|
+
post.versions.last.hoardable_note # => "reverting due to accidental deletion"
|
135
|
+
```
|
136
|
+
|
137
|
+
Another useful pattern is to use `Hoardable.with` to set the context around a block. A good example
|
138
|
+
of this would be in `ApplicationController`:
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
class ApplicationController < ActionController::Base
|
142
|
+
around_action :use_hoardable_context
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def use_hoardable_context
|
147
|
+
Hoardable.with(whodunit: current_user.id, meta: { request_uuid: request.uuid }) do
|
148
|
+
yield
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
```
|
153
|
+
|
154
|
+
### Model Callbacks
|
155
|
+
|
156
|
+
Sometimes you might want to do something with a version before it gets saved. You can access it in a
|
157
|
+
`before_save` callback as `hoardable_version`. There is also an `after_reverted` callback available
|
158
|
+
as well.
|
159
|
+
|
160
|
+
``` ruby
|
161
|
+
class User
|
162
|
+
before_save :sanitize_version
|
163
|
+
after_reverted :track_reverted_event
|
164
|
+
|
165
|
+
private
|
166
|
+
|
167
|
+
def sanitize_version
|
168
|
+
hoardable_version.sanitize_password
|
169
|
+
end
|
170
|
+
|
171
|
+
def track_reverted_event
|
172
|
+
track_event(:user_reverted, self)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
```
|
176
|
+
|
177
|
+
### Configuration
|
178
|
+
|
179
|
+
There are two available options:
|
180
|
+
|
181
|
+
``` ruby
|
182
|
+
Hoardable.enabled # => default true
|
183
|
+
Hoardable.save_trash # => default true
|
184
|
+
```
|
185
|
+
|
186
|
+
`Hoardable.enabled` controls whether versions will be created at all.
|
187
|
+
|
188
|
+
`Hoardable.save_trash` controls whether to create versions upon record deletion. When this is set to
|
189
|
+
`false`, all versions of a record will be deleted when the record is destroyed.
|
190
|
+
|
191
|
+
If you would like to temporarily set a config setting, you can use `Hoardable.with` as well:
|
192
|
+
|
193
|
+
```ruby
|
194
|
+
Hoardable.with(enabled: false) do
|
195
|
+
post.update!(title: 'unimportant change to create version for')
|
196
|
+
end
|
197
|
+
```
|
198
|
+
|
199
|
+
## Contributing
|
200
|
+
|
201
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/waymondo/hoardable.
|
202
|
+
|
203
|
+
## License
|
204
|
+
|
205
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rake/testtask'
|
5
|
+
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
7
|
+
t.libs << 'test'
|
8
|
+
t.libs << 'lib'
|
9
|
+
t.test_files = FileList['test/**/test_*.rb']
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'rubocop/rake_task'
|
13
|
+
|
14
|
+
RuboCop::RakeTask.new
|
15
|
+
|
16
|
+
task default: %i[test rubocop]
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/generators'
|
4
|
+
require 'rails/generators/active_record/migration/migration_generator'
|
5
|
+
|
6
|
+
module Hoardable
|
7
|
+
# Generates a migration for an inherited temporal table of a model including {Hoardable::Model}
|
8
|
+
class MigrationGenerator < ActiveRecord::Generators::Base
|
9
|
+
source_root File.expand_path('templates', __dir__)
|
10
|
+
include Rails::Generators::Migration
|
11
|
+
|
12
|
+
def create_versions_table
|
13
|
+
migration_template 'migration.rb.erb', "db/migrate/create_#{singularized_table_name}_versions.rb"
|
14
|
+
end
|
15
|
+
|
16
|
+
no_tasks do
|
17
|
+
def singularized_table_name
|
18
|
+
@singularized_table_name ||= table_name.singularize
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Create<%= class_name.singularize %>Versions < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
4
|
+
def change
|
5
|
+
create_table :<%= singularized_table_name %>_versions, id: false, options: 'INHERITS (<%= table_name %>)' do |t|
|
6
|
+
t.jsonb :_data
|
7
|
+
t.tsrange :_during, null: false
|
8
|
+
t.bigint :<%= singularized_table_name %>_id, null: false, index: true
|
9
|
+
end
|
10
|
+
add_index :<%= singularized_table_name %>_versions, %i[_during <%= singularized_table_name %>_id]
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# An ActiveRecord extension for keeping versions of records in temporal inherited tables
|
4
|
+
module Hoardable
|
5
|
+
VERSION = '0.1.0'
|
6
|
+
DATA_KEYS = %i[changes meta whodunit note operation].freeze
|
7
|
+
CONFIG_KEYS = %i[enabled save_trash].freeze
|
8
|
+
|
9
|
+
@context = {}
|
10
|
+
@config = CONFIG_KEYS.to_h do |key|
|
11
|
+
[key, true]
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
CONFIG_KEYS.each do |key|
|
16
|
+
define_method(key) do
|
17
|
+
@config[key]
|
18
|
+
end
|
19
|
+
|
20
|
+
define_method("#{key}=") do |value|
|
21
|
+
@config[key] = value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
DATA_KEYS.each do |key|
|
26
|
+
define_method(key) do
|
27
|
+
@context[key]
|
28
|
+
end
|
29
|
+
|
30
|
+
define_method("#{key}=") do |value|
|
31
|
+
@context[key] = value
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def with(hash)
|
36
|
+
current_config = @config
|
37
|
+
current_context = @context
|
38
|
+
@config = current_config.merge(hash.slice(*CONFIG_KEYS))
|
39
|
+
@context = current_context.merge(hash.slice(*DATA_KEYS))
|
40
|
+
yield
|
41
|
+
ensure
|
42
|
+
@config = current_config
|
43
|
+
@context = current_context
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hoardable
|
4
|
+
# This concern includes the Hoardable API methods on ActiveRecord instances and dynamically
|
5
|
+
# generates the Version variant of the class
|
6
|
+
module Model
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
default_scope { where("#{table_name}.tableoid = '#{table_name}'::regclass") }
|
11
|
+
|
12
|
+
before_update :initialize_hoardable_version, if: -> { Hoardable.enabled }
|
13
|
+
before_destroy :initialize_hoardable_version, if: -> { Hoardable.enabled && Hoardable.save_trash }
|
14
|
+
after_update :save_hoardable_version, if: -> { Hoardable.enabled }
|
15
|
+
before_destroy :delete_hoardable_versions, if: -> { Hoardable.enabled && !Hoardable.save_trash }
|
16
|
+
after_destroy :save_hoardable_version, if: -> { Hoardable.enabled && Hoardable.save_trash }
|
17
|
+
|
18
|
+
attr_reader :hoardable_version
|
19
|
+
|
20
|
+
define_model_callbacks :reverted, only: :after
|
21
|
+
|
22
|
+
TracePoint.new(:end) do |trace|
|
23
|
+
next unless self == trace.self
|
24
|
+
|
25
|
+
version_class_name = "#{name}Version"
|
26
|
+
next if Object.const_defined?(version_class_name)
|
27
|
+
|
28
|
+
Object.const_set(version_class_name, Class.new(self) { include VersionModel })
|
29
|
+
has_many(
|
30
|
+
:versions, -> { order(:_during) },
|
31
|
+
dependent: nil,
|
32
|
+
class_name: version_class_name,
|
33
|
+
inverse_of: model_name.i18n_key
|
34
|
+
)
|
35
|
+
trace.disable
|
36
|
+
end.enable
|
37
|
+
end
|
38
|
+
|
39
|
+
def at(datetime)
|
40
|
+
versions.find_by('_during @> ?::timestamp', datetime) || self
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def initialize_hoardable_version
|
46
|
+
Hoardable.with(changes: changes) do
|
47
|
+
@hoardable_version = versions.new(
|
48
|
+
attributes_before_type_cast
|
49
|
+
.without('id')
|
50
|
+
.merge(changes.transform_values { |h| h[0] })
|
51
|
+
.merge(_data: initialize_hoardable_data)
|
52
|
+
)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def initialize_hoardable_data
|
57
|
+
DATA_KEYS.to_h do |key|
|
58
|
+
[key, assign_hoardable_context(key)]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def assign_hoardable_context(key)
|
63
|
+
return nil if (value = Hoardable.public_send(key)).nil?
|
64
|
+
|
65
|
+
value.is_a?(Proc) ? value.call : value
|
66
|
+
end
|
67
|
+
|
68
|
+
def save_hoardable_version
|
69
|
+
hoardable_version._data['operation'] = persisted? ? 'update' : 'delete'
|
70
|
+
hoardable_version.save!(validate: false, touch: false)
|
71
|
+
@hoardable_version = nil
|
72
|
+
end
|
73
|
+
|
74
|
+
def delete_hoardable_versions
|
75
|
+
versions.delete_all(:delete_all)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hoardable
|
4
|
+
# This concern is included into the dynamically generated Version models.
|
5
|
+
module VersionModel
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
hoardable_source_key = superclass.model_name.i18n_key
|
10
|
+
belongs_to hoardable_source_key, inverse_of: :versions
|
11
|
+
alias_method :hoardable_source, hoardable_source_key
|
12
|
+
|
13
|
+
self.table_name = "#{table_name.singularize}_versions"
|
14
|
+
|
15
|
+
alias_method :readonly?, :persisted?
|
16
|
+
|
17
|
+
before_create :assign_temporal_tsrange
|
18
|
+
|
19
|
+
scope :trashed, lambda {
|
20
|
+
left_outer_joins(hoardable_source_key)
|
21
|
+
.where(superclass.table_name => { id: nil })
|
22
|
+
.where("_data ->> 'operation' = 'delete'")
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def revert!
|
27
|
+
transaction do
|
28
|
+
(
|
29
|
+
hoardable_source&.tap { |tapped| tapped.update!(hoardable_source_attributes.without('id')) } ||
|
30
|
+
untrash
|
31
|
+
).tap do |tapped|
|
32
|
+
tapped.run_callbacks(:reverted)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
DATA_KEYS.each do |key|
|
38
|
+
define_method("hoardable_#{key}") do
|
39
|
+
_data&.dig(key.to_s)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
alias changes hoardable_changes
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def untrash
|
48
|
+
foreign_id = public_send(hoardable_source_foreign_key)
|
49
|
+
self.class.superclass.insert(hoardable_source_attributes.merge('id' => foreign_id, 'updated_at' => Time.now))
|
50
|
+
self.class.superclass.find(foreign_id)
|
51
|
+
end
|
52
|
+
|
53
|
+
def hoardable_source_attributes
|
54
|
+
@hoardable_source_attributes ||=
|
55
|
+
attributes_before_type_cast
|
56
|
+
.without(hoardable_source_foreign_key)
|
57
|
+
.reject { |k, _v| k.start_with?('_') }
|
58
|
+
end
|
59
|
+
|
60
|
+
def hoardable_source_foreign_key
|
61
|
+
@hoardable_source_foreign_key ||= "#{self.class.superclass.model_name.i18n_key}_id"
|
62
|
+
end
|
63
|
+
|
64
|
+
def previous_temporal_tsrange_end
|
65
|
+
hoardable_source.versions.limit(1).order(_during: :desc).pluck('_during').first&.end
|
66
|
+
end
|
67
|
+
|
68
|
+
def assign_temporal_tsrange
|
69
|
+
self._during = ((previous_temporal_tsrange_end || hoardable_source.created_at)..Time.now)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/hoardable.rb
ADDED
data/sig/hoardable.rbs
ADDED
metadata
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hoardable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- justin talbott
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-07-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '6.1'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '8'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '6.1'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '8'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: activesupport
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '6.1'
|
40
|
+
- - "<"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '8'
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '6.1'
|
50
|
+
- - "<"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '8'
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: pg
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '1.0'
|
60
|
+
- - "<"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '2'
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '1.0'
|
70
|
+
- - "<"
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '2'
|
73
|
+
- !ruby/object:Gem::Dependency
|
74
|
+
name: railties
|
75
|
+
requirement: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '6.1'
|
80
|
+
- - "<"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '8'
|
83
|
+
type: :runtime
|
84
|
+
prerelease: false
|
85
|
+
version_requirements: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '6.1'
|
90
|
+
- - "<"
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '8'
|
93
|
+
description: Rails model versioning with the power of uni-temporal inherited tables
|
94
|
+
email:
|
95
|
+
- justin@waymondo.com
|
96
|
+
executables: []
|
97
|
+
extensions: []
|
98
|
+
extra_rdoc_files: []
|
99
|
+
files:
|
100
|
+
- ".rubocop.yml"
|
101
|
+
- ".tool-versions"
|
102
|
+
- CHANGELOG.md
|
103
|
+
- Gemfile
|
104
|
+
- LICENSE.txt
|
105
|
+
- README.md
|
106
|
+
- Rakefile
|
107
|
+
- lib/generators/hoardable/migration_generator.rb
|
108
|
+
- lib/generators/hoardable/templates/migration.rb.erb
|
109
|
+
- lib/hoardable.rb
|
110
|
+
- lib/hoardable/hoardable.rb
|
111
|
+
- lib/hoardable/model.rb
|
112
|
+
- lib/hoardable/version_model.rb
|
113
|
+
- sig/hoardable.rbs
|
114
|
+
homepage: https://github.com/waymondo/hoardable
|
115
|
+
licenses:
|
116
|
+
- MIT
|
117
|
+
metadata:
|
118
|
+
homepage_uri: https://github.com/waymondo/hoardable
|
119
|
+
source_code_uri: https://github.com/waymondo/hoardable
|
120
|
+
rubygems_mfa_required: 'true'
|
121
|
+
post_install_message:
|
122
|
+
rdoc_options: []
|
123
|
+
require_paths:
|
124
|
+
- lib
|
125
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: 2.6.0
|
130
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - ">="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '0'
|
135
|
+
requirements: []
|
136
|
+
rubygems_version: 3.3.7
|
137
|
+
signing_key:
|
138
|
+
specification_version: 4
|
139
|
+
summary: An ActiveRecord extension for versioning and soft-deletion of records in
|
140
|
+
Postgres
|
141
|
+
test_files: []
|