hoardable 0.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 +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: []
|