rabbit_tracks 1.0.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/.DS_Store +0 -0
- data/.github/workflows/gem-push.yml +48 -0
- data/.gitignore +1 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +185 -0
- data/LICENSE.txt +21 -0
- data/README.md +154 -0
- data/Rakefile +8 -0
- data/lib/.DS_Store +0 -0
- data/lib/generators/rabbit_tracks/install.rb +17 -0
- data/lib/generators/rabbit_tracks/templates/create_rabbit_tracks.rb +19 -0
- data/lib/rabbit_tracks/config.rb +13 -0
- data/lib/rabbit_tracks/controller.rb +29 -0
- data/lib/rabbit_tracks/has_rabbit_tracks.rb +125 -0
- data/lib/rabbit_tracks/rabbit_track.rb +34 -0
- data/lib/rabbit_tracks/railtie.rb +13 -0
- data/lib/rabbit_tracks/version.rb +5 -0
- data/lib/rabbit_tracks.rb +43 -0
- data/rabbit_tracks.gemspec +47 -0
- metadata +162 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 63c5da5a2ae609680959081a677d6041fc96ab61173ae0b7ab421945b983f21d
|
|
4
|
+
data.tar.gz: 50d62e9ae89dd1c3f9f95b1c78a42f576333bff54dc069b1bfbaf52f7e5b847b
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e1f8ec6d872d963efb9a7d9ef71b38f2ded6784a1f537fdceeccec452d7565e091ec9decfa9bc50d16e6c614f7b2d22aa8b255f5af35ddfbef11e7de639a9d65
|
|
7
|
+
data.tar.gz: a89bad85be6898594ef8ad1ba30cd134d460040edf5764e37d3cd8d64fb8f5465d9733d94df1f1ccff7728deac381de1ef6346153dd69b0556da62b7122f0f45
|
data/.DS_Store
ADDED
|
Binary file
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
name: Ruby Gem
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ "main" ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ "main" ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
name: Build + Publish
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
packages: write
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- name: Set up Ruby 2.6
|
|
20
|
+
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
|
|
21
|
+
# change this to (see https://github.com/ruby/setup-ruby#versioning):
|
|
22
|
+
# uses: ruby/setup-ruby@v1
|
|
23
|
+
uses: ruby/setup-ruby@55283cc23133118229fd3f97f9336ee23a179fcf # v1.146.0
|
|
24
|
+
with:
|
|
25
|
+
ruby-version: 2.6.x
|
|
26
|
+
|
|
27
|
+
- name: Publish to GPR
|
|
28
|
+
run: |
|
|
29
|
+
mkdir -p $HOME/.gem
|
|
30
|
+
touch $HOME/.gem/credentials
|
|
31
|
+
chmod 0600 $HOME/.gem/credentials
|
|
32
|
+
printf -- "---\n:github: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
|
|
33
|
+
gem build *.gemspec
|
|
34
|
+
gem push --KEY github --host https://rubygems.pkg.github.com/${OWNER} *.gem
|
|
35
|
+
env:
|
|
36
|
+
GEM_HOST_API_KEY: "Bearer ${{secrets.GITHUB_TOKEN}}"
|
|
37
|
+
OWNER: ${{ github.repository_owner }}
|
|
38
|
+
|
|
39
|
+
- name: Publish to RubyGems
|
|
40
|
+
run: |
|
|
41
|
+
mkdir -p $HOME/.gem
|
|
42
|
+
touch $HOME/.gem/credentials
|
|
43
|
+
chmod 0600 $HOME/.gem/credentials
|
|
44
|
+
printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
|
|
45
|
+
gem build *.gemspec
|
|
46
|
+
gem push *.gem
|
|
47
|
+
env:
|
|
48
|
+
GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"
|
data/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.rspec_status
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
rabbit_tracks (1.0.0)
|
|
5
|
+
activerecord-import (~> 2.2.0)
|
|
6
|
+
|
|
7
|
+
GEM
|
|
8
|
+
remote: https://rubygems.org/
|
|
9
|
+
specs:
|
|
10
|
+
actionpack (8.1.2)
|
|
11
|
+
actionview (= 8.1.2)
|
|
12
|
+
activesupport (= 8.1.2)
|
|
13
|
+
nokogiri (>= 1.8.5)
|
|
14
|
+
rack (>= 2.2.4)
|
|
15
|
+
rack-session (>= 1.0.1)
|
|
16
|
+
rack-test (>= 0.6.3)
|
|
17
|
+
rails-dom-testing (~> 2.2)
|
|
18
|
+
rails-html-sanitizer (~> 1.6)
|
|
19
|
+
useragent (~> 0.16)
|
|
20
|
+
actionview (8.1.2)
|
|
21
|
+
activesupport (= 8.1.2)
|
|
22
|
+
builder (~> 3.1)
|
|
23
|
+
erubi (~> 1.11)
|
|
24
|
+
rails-dom-testing (~> 2.2)
|
|
25
|
+
rails-html-sanitizer (~> 1.6)
|
|
26
|
+
activemodel (8.1.2)
|
|
27
|
+
activesupport (= 8.1.2)
|
|
28
|
+
activerecord (8.1.2)
|
|
29
|
+
activemodel (= 8.1.2)
|
|
30
|
+
activesupport (= 8.1.2)
|
|
31
|
+
timeout (>= 0.4.0)
|
|
32
|
+
activerecord-import (2.2.0)
|
|
33
|
+
activerecord (>= 4.2)
|
|
34
|
+
activesupport (8.1.2)
|
|
35
|
+
base64
|
|
36
|
+
bigdecimal
|
|
37
|
+
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
38
|
+
connection_pool (>= 2.2.5)
|
|
39
|
+
drb
|
|
40
|
+
i18n (>= 1.6, < 2)
|
|
41
|
+
json
|
|
42
|
+
logger (>= 1.4.2)
|
|
43
|
+
minitest (>= 5.1)
|
|
44
|
+
securerandom (>= 0.3)
|
|
45
|
+
tzinfo (~> 2.0, >= 2.0.5)
|
|
46
|
+
uri (>= 0.13.1)
|
|
47
|
+
ast (2.4.3)
|
|
48
|
+
base64 (0.3.0)
|
|
49
|
+
bigdecimal (4.0.1)
|
|
50
|
+
builder (3.3.0)
|
|
51
|
+
concurrent-ruby (1.3.6)
|
|
52
|
+
connection_pool (3.0.2)
|
|
53
|
+
crass (1.0.6)
|
|
54
|
+
date (3.5.1)
|
|
55
|
+
diff-lcs (1.6.2)
|
|
56
|
+
drb (2.2.3)
|
|
57
|
+
erb (6.0.1)
|
|
58
|
+
erubi (1.13.1)
|
|
59
|
+
generator_spec (0.10.0)
|
|
60
|
+
activesupport (>= 3.0.0)
|
|
61
|
+
railties (>= 3.0.0)
|
|
62
|
+
i18n (1.14.8)
|
|
63
|
+
concurrent-ruby (~> 1.0)
|
|
64
|
+
io-console (0.8.2)
|
|
65
|
+
irb (1.16.0)
|
|
66
|
+
pp (>= 0.6.0)
|
|
67
|
+
rdoc (>= 4.0.0)
|
|
68
|
+
reline (>= 0.4.2)
|
|
69
|
+
json (2.18.0)
|
|
70
|
+
language_server-protocol (3.17.0.5)
|
|
71
|
+
lint_roller (1.1.0)
|
|
72
|
+
logger (1.7.0)
|
|
73
|
+
loofah (2.25.0)
|
|
74
|
+
crass (~> 1.0.2)
|
|
75
|
+
nokogiri (>= 1.12.0)
|
|
76
|
+
minitest (6.0.1)
|
|
77
|
+
prism (~> 1.5)
|
|
78
|
+
nokogiri (1.19.0-aarch64-linux-gnu)
|
|
79
|
+
racc (~> 1.4)
|
|
80
|
+
nokogiri (1.19.0-x86_64-linux-gnu)
|
|
81
|
+
racc (~> 1.4)
|
|
82
|
+
parallel (1.27.0)
|
|
83
|
+
parser (3.3.10.0)
|
|
84
|
+
ast (~> 2.4.1)
|
|
85
|
+
racc
|
|
86
|
+
pp (0.6.3)
|
|
87
|
+
prettyprint
|
|
88
|
+
prettyprint (0.2.0)
|
|
89
|
+
prism (1.7.0)
|
|
90
|
+
psych (5.3.1)
|
|
91
|
+
date
|
|
92
|
+
stringio
|
|
93
|
+
racc (1.8.1)
|
|
94
|
+
rack (3.2.4)
|
|
95
|
+
rack-session (2.1.1)
|
|
96
|
+
base64 (>= 0.1.0)
|
|
97
|
+
rack (>= 3.0.0)
|
|
98
|
+
rack-test (2.2.0)
|
|
99
|
+
rack (>= 1.3)
|
|
100
|
+
rackup (2.3.1)
|
|
101
|
+
rack (>= 3)
|
|
102
|
+
rails-dom-testing (2.3.0)
|
|
103
|
+
activesupport (>= 5.0.0)
|
|
104
|
+
minitest
|
|
105
|
+
nokogiri (>= 1.6)
|
|
106
|
+
rails-html-sanitizer (1.6.2)
|
|
107
|
+
loofah (~> 2.21)
|
|
108
|
+
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
|
109
|
+
railties (8.1.2)
|
|
110
|
+
actionpack (= 8.1.2)
|
|
111
|
+
activesupport (= 8.1.2)
|
|
112
|
+
irb (~> 1.13)
|
|
113
|
+
rackup (>= 1.0.0)
|
|
114
|
+
rake (>= 12.2)
|
|
115
|
+
thor (~> 1.0, >= 1.2.2)
|
|
116
|
+
tsort (>= 0.2)
|
|
117
|
+
zeitwerk (~> 2.6)
|
|
118
|
+
rainbow (3.1.1)
|
|
119
|
+
rake (13.3.1)
|
|
120
|
+
rdoc (7.0.3)
|
|
121
|
+
erb
|
|
122
|
+
psych (>= 4.0.0)
|
|
123
|
+
tsort
|
|
124
|
+
regexp_parser (2.11.3)
|
|
125
|
+
reline (0.6.3)
|
|
126
|
+
io-console (~> 0.5)
|
|
127
|
+
rspec (3.13.2)
|
|
128
|
+
rspec-core (~> 3.13.0)
|
|
129
|
+
rspec-expectations (~> 3.13.0)
|
|
130
|
+
rspec-mocks (~> 3.13.0)
|
|
131
|
+
rspec-core (3.13.6)
|
|
132
|
+
rspec-support (~> 3.13.0)
|
|
133
|
+
rspec-expectations (3.13.5)
|
|
134
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
135
|
+
rspec-support (~> 3.13.0)
|
|
136
|
+
rspec-mocks (3.13.7)
|
|
137
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
138
|
+
rspec-support (~> 3.13.0)
|
|
139
|
+
rspec-support (3.13.6)
|
|
140
|
+
rubocop (1.82.1)
|
|
141
|
+
json (~> 2.3)
|
|
142
|
+
language_server-protocol (~> 3.17.0.2)
|
|
143
|
+
lint_roller (~> 1.1.0)
|
|
144
|
+
parallel (~> 1.10)
|
|
145
|
+
parser (>= 3.3.0.2)
|
|
146
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
147
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
148
|
+
rubocop-ast (>= 1.48.0, < 2.0)
|
|
149
|
+
ruby-progressbar (~> 1.7)
|
|
150
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
151
|
+
rubocop-ast (1.49.0)
|
|
152
|
+
parser (>= 3.3.7.2)
|
|
153
|
+
prism (~> 1.7)
|
|
154
|
+
ruby-progressbar (1.13.0)
|
|
155
|
+
securerandom (0.4.1)
|
|
156
|
+
sqlite3 (2.9.0-aarch64-linux-gnu)
|
|
157
|
+
sqlite3 (2.9.0-x86_64-linux-gnu)
|
|
158
|
+
stringio (3.2.0)
|
|
159
|
+
thor (1.5.0)
|
|
160
|
+
timeout (0.6.0)
|
|
161
|
+
tsort (0.2.0)
|
|
162
|
+
tzinfo (2.0.6)
|
|
163
|
+
concurrent-ruby (~> 1.0)
|
|
164
|
+
unicode-display_width (3.2.0)
|
|
165
|
+
unicode-emoji (~> 4.1)
|
|
166
|
+
unicode-emoji (4.2.0)
|
|
167
|
+
uri (1.1.1)
|
|
168
|
+
useragent (0.16.11)
|
|
169
|
+
zeitwerk (2.7.4)
|
|
170
|
+
|
|
171
|
+
PLATFORMS
|
|
172
|
+
aarch64-linux
|
|
173
|
+
x86_64-linux
|
|
174
|
+
|
|
175
|
+
DEPENDENCIES
|
|
176
|
+
bundler (~> 2.7.2)
|
|
177
|
+
generator_spec (~> 0.10.0)
|
|
178
|
+
rabbit_tracks!
|
|
179
|
+
rake (~> 13.3.0)
|
|
180
|
+
rspec (~> 3.0)
|
|
181
|
+
rubocop (~> 1.82.1)
|
|
182
|
+
sqlite3 (~> 2.9.0)
|
|
183
|
+
|
|
184
|
+
BUNDLED WITH
|
|
185
|
+
2.7.2
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Peter Zhang
|
|
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,154 @@
|
|
|
1
|
+
# Rabbit Tracks
|
|
2
|
+
|
|
3
|
+
Rabbit Tracks is a gem keeps all model changes you care about.
|
|
4
|
+
It keeps a record of who made which change at what time.
|
|
5
|
+
|
|
6
|
+
You can skip any database columns as well.
|
|
7
|
+
For example: `updated_at`, `created_at` and `password` etc.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Add it to your `Gemfile` then `bundle install`
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
gem 'rabbit_tracks'
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Next we need to generate a migration and create a table to keep all changes
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
rails generate rabbit_tracks:install RabbitTracks
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Then:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
rails db:migrate
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Use Rabbit Tracks Gem
|
|
30
|
+
|
|
31
|
+
1. **ActiveRecord Models**
|
|
32
|
+
Enable rabbit_tracks for Active Record Model,
|
|
33
|
+
just put following line in very beginning of model file.
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
enable_rabbit_tracks :ignore=>[:updated_at]
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Put any columns you do not want to keep in the rabbit tracks table in `:ignore` option.
|
|
40
|
+
eg. the password hash
|
|
41
|
+
|
|
42
|
+
Then the system should record every changes the user made to rabbit_tracks table.
|
|
43
|
+
If you are making changes within Model file:
|
|
44
|
+
For Example:
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
# this is a model file
|
|
48
|
+
def making_some_changes
|
|
49
|
+
user = User.first
|
|
50
|
+
user.email = 'test@example.com'
|
|
51
|
+
user.save
|
|
52
|
+
end
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
An attribute called `whodidit` is automatically available.
|
|
56
|
+
So if you want to keep the changes in this scenario, do following:
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
# this is a model file
|
|
60
|
+
def making_some_changes
|
|
61
|
+
user = User.first
|
|
62
|
+
user.email = 'test@example.com'
|
|
63
|
+
user.whodidit = 'TestUser'
|
|
64
|
+
user.save
|
|
65
|
+
end
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
2. **Optional: Add current_user Method in application_controller.rb**
|
|
69
|
+
|
|
70
|
+
This method will tell rabbit_tracks who is the current user
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
def current_user
|
|
74
|
+
return session[:user] # replace this with your own code
|
|
75
|
+
end
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
3. **About the RabbitTrack Model**
|
|
79
|
+
RabbitTrack model is core ActiveRecord model used by rabbit_tracks gem.
|
|
80
|
+
|
|
81
|
+
You can use it directly in your model, controller even in helper.
|
|
82
|
+
|
|
83
|
+
For example:
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
# List all changes
|
|
87
|
+
RabbitTrack.all
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
# List all changes made by specific user
|
|
92
|
+
RabbitTrack.where('user = ?', 'example_user')
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
# List all changes for specific table
|
|
97
|
+
RabbitTrack.where('table_name = ?', 'accounts')
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
4. **Turn RabbitTrack off in testing environment**
|
|
101
|
+
You can globally turn it off for your testing.
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
# config/environment.rb
|
|
105
|
+
RabbitTracks.enabled = false if Rails.env.test?
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
5. **Database and table name**
|
|
109
|
+
rabbit_tracks gem can save changes into separate database from the main application.
|
|
110
|
+
The database could be Postgres, SQLite, MySQL or any other database that active record is happy to connect with.
|
|
111
|
+
|
|
112
|
+
Here is an example of database.yml when using separate database for 'rabbit_tracks':
|
|
113
|
+
|
|
114
|
+
```yaml
|
|
115
|
+
rabbit_tracks:
|
|
116
|
+
adapter: mysql2
|
|
117
|
+
encoding: utf8
|
|
118
|
+
database: a_different_database
|
|
119
|
+
username: username
|
|
120
|
+
password: ********
|
|
121
|
+
host: hostname
|
|
122
|
+
port: 3306
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
And also you need to tell rabbit_tracks gem to establish the connection.
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
# config/environment.rb
|
|
129
|
+
RabbitTrack.establish_connection(:rabbit_tracks)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Table name is also configurable. Instead of 'rabbit_tracks', choose your preferred table name and run the migration.
|
|
133
|
+
Just remember in your environment.rb file, you need to tell rabbit_tracks gem what is your table name:
|
|
134
|
+
|
|
135
|
+
```ruby
|
|
136
|
+
# config/environment.rb
|
|
137
|
+
RabbitTrack.table_name :a_different_table_name
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Testing
|
|
141
|
+
Run
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
bundle exec rspec spec
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Bug reporting or any enquiries
|
|
148
|
+
|
|
149
|
+
Please use Github Issues or Discussions: https://github.com/peterpengnz/rabbit_tracks
|
|
150
|
+
|
|
151
|
+
### Author
|
|
152
|
+
|
|
153
|
+
Peter Zhang
|
|
154
|
+
Copyright (c) 2026 Peter Zhang, released under the MIT license
|
data/Rakefile
ADDED
data/lib/.DS_Store
ADDED
|
Binary file
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'rails/generators/active_record'
|
|
2
|
+
require 'rails/generators/named_base'
|
|
3
|
+
|
|
4
|
+
module RabbitTracks
|
|
5
|
+
module Generators
|
|
6
|
+
class Install < Rails::Generators::NamedBase
|
|
7
|
+
source_root File.expand_path('templates', __dir__)
|
|
8
|
+
|
|
9
|
+
def create_migration
|
|
10
|
+
migration_file_name = 'create_rabbit_tracks.rb'
|
|
11
|
+
timestamp = Time.now.utc.strftime('%Y%m%d%H%M%S')
|
|
12
|
+
destination = File.join('db', 'migrate', "#{timestamp}_#{migration_file_name}")
|
|
13
|
+
template migration_file_name, destination
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Migration template for creating the rabbit_tracks table
|
|
4
|
+
class CreateRabbitTracks < ActiveRecord::Migration[8.1]
|
|
5
|
+
def change
|
|
6
|
+
create_table :rabbit_tracks do |t|
|
|
7
|
+
t.integer :version, null: false # store version of each change
|
|
8
|
+
t.string :record_id, limit: 64 # store the actual record id
|
|
9
|
+
t.string :table_name, limit: 64 # store the table name
|
|
10
|
+
t.string :attribute_name, limit: 64 # store the column name
|
|
11
|
+
t.string :user, limit: 64 # store the user who made the change
|
|
12
|
+
t.string :action, limit: 6 # store the change action: create, read, update, delete
|
|
13
|
+
t.text :old_value # the value before change
|
|
14
|
+
t.text :new_value # value after change
|
|
15
|
+
t.string :field_type, limit: 30 # the column type eg. date, text, varchar, int etc
|
|
16
|
+
t.timestamps
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RabbitTracks
|
|
4
|
+
module Controller
|
|
5
|
+
def self.included(base)
|
|
6
|
+
base.before_filter :set_rabbit_tracks_whodidit
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
protected
|
|
10
|
+
|
|
11
|
+
# Returns the user who is responsible for any changes that occur.
|
|
12
|
+
# By default this calls `current_user` and returns the result.
|
|
13
|
+
#
|
|
14
|
+
# Override this method in your controller to call a different
|
|
15
|
+
# method, e.g. `current_login_user`, or anything.
|
|
16
|
+
def rabbit_tracks_current_user
|
|
17
|
+
current_user
|
|
18
|
+
rescue StandardError
|
|
19
|
+
nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
# Tells RabbitTracks who is responsible for any changes.
|
|
25
|
+
def set_rabbit_tracks_whodidit
|
|
26
|
+
::RabbitTracks.whodidit = rabbit_tracks_current_user
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rabbit_tracks/railtie' if defined?(Rails)
|
|
4
|
+
|
|
5
|
+
module RabbitTracks
|
|
6
|
+
module Model
|
|
7
|
+
def self.included(base)
|
|
8
|
+
base.send :extend, ClassMethods
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module ClassMethods
|
|
12
|
+
# Declare this in your model to keep a change record for every create, update, and destroy.
|
|
13
|
+
# Option:
|
|
14
|
+
# :ignore an array of attributes will be ignored when saving changes to rabbit tracks
|
|
15
|
+
def include_rabbit_tracks(options = {})
|
|
16
|
+
send :include, InstanceMethods
|
|
17
|
+
|
|
18
|
+
cattr_accessor :ignore, :whodidit
|
|
19
|
+
self.ignore = (options[:ignore] || []).map(&:to_s)
|
|
20
|
+
# Indicates whether or not RabbitTracks is active for this class.
|
|
21
|
+
# This is independent of whether RabbitTracks is globally enabled or disabled.
|
|
22
|
+
cattr_accessor :rabbit_tracks_enabled
|
|
23
|
+
self.rabbit_tracks_enabled = true
|
|
24
|
+
after_create :record_create
|
|
25
|
+
before_update :record_update
|
|
26
|
+
after_destroy :record_destroy
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Disables RabbitTracks for this class.
|
|
30
|
+
def disable_rabbit_tracks
|
|
31
|
+
self.rabbit_tracks_enabled = false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Enables RabbitTracks for this class.
|
|
35
|
+
def enable_rabbit_tracks
|
|
36
|
+
self.rabbit_tracks_enabled = true
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Wrap the following methods in a module so we can include them only in the
|
|
41
|
+
# ActiveRecord models that declare `include_rabbit_tracks`.
|
|
42
|
+
module InstanceMethods
|
|
43
|
+
def record_create
|
|
44
|
+
# do nothing if the rabbit tracks is not enabled
|
|
45
|
+
return unless enabled?
|
|
46
|
+
|
|
47
|
+
changes = []
|
|
48
|
+
attributes.map do |key, value|
|
|
49
|
+
next if ignore.include?(key.to_sym)
|
|
50
|
+
|
|
51
|
+
changes << {
|
|
52
|
+
action: 'INSERT',
|
|
53
|
+
record_id: id,
|
|
54
|
+
table_name: self.class.table_name,
|
|
55
|
+
user: RabbitTracks.whodidit,
|
|
56
|
+
attribute_name: key,
|
|
57
|
+
new_value: value,
|
|
58
|
+
version: 1
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
RabbitTrack.update_records_with(changes)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def record_update
|
|
65
|
+
# do nothing if the rabbit tracks is not enabled or failed active record validation or no changes has been made
|
|
66
|
+
return unless enabled? && valid? && changed?
|
|
67
|
+
|
|
68
|
+
changes = []
|
|
69
|
+
# saving changes to rabbit tracks
|
|
70
|
+
self.changes.each do |attribute_name, value|
|
|
71
|
+
# do not record changes between nil <=> ''
|
|
72
|
+
# and ignore the changes for ignored columns
|
|
73
|
+
if value[1].eql?(value[0]) || (value[1].blank? && value[0].blank?) || ignore.include?(attribute_name.to_s)
|
|
74
|
+
next
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
changes << {
|
|
78
|
+
action: 'UPDATE',
|
|
79
|
+
record_id: id,
|
|
80
|
+
table_name: self.class.table_name,
|
|
81
|
+
user: RabbitTracks.whodidit,
|
|
82
|
+
attribute_name: attribute_name,
|
|
83
|
+
old_value: value[0],
|
|
84
|
+
new_value: value[1],
|
|
85
|
+
version: RabbitTracks.get_version_number(id, self.class.table_name)
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
RabbitTrack.update_records_with(changes)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def record_destroy
|
|
92
|
+
return unless enabled?
|
|
93
|
+
|
|
94
|
+
changes = [{
|
|
95
|
+
action: 'DELETE',
|
|
96
|
+
table_name: self.class.table_name,
|
|
97
|
+
record_id: id,
|
|
98
|
+
user: RabbitTracks.whodidit,
|
|
99
|
+
version: RabbitTrack.get_version_number(id, self.class.table_name)
|
|
100
|
+
}]
|
|
101
|
+
RabbitTrack.update_records_with(changes)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Return a list of changes records
|
|
105
|
+
# Return empty array if not record found
|
|
106
|
+
def rabbit_tracks
|
|
107
|
+
RabbitTrack.where(table_name: self.class.table_name, record_id: id).order('created_at DESC')
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Return `true` if current record has a list of changes records
|
|
111
|
+
# otherwise `false`.
|
|
112
|
+
def rabbit_tracks?
|
|
113
|
+
RabbitTrack.exists?(table_name: self.class.table_name, record_id: id)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
private
|
|
117
|
+
|
|
118
|
+
# Returns `true` if RabbitTracks is globally enabled and active for this class,
|
|
119
|
+
# `false` otherwise.
|
|
120
|
+
def enabled?
|
|
121
|
+
RabbitTracks.enabled? && self.class.rabbit_tracks_enabled
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_record'
|
|
4
|
+
require 'activerecord-import'
|
|
5
|
+
|
|
6
|
+
class RabbitTrack < ActiveRecord::Base
|
|
7
|
+
self.table_name = 'rabbit_tracks'
|
|
8
|
+
def self.update_records_with(changes = [])
|
|
9
|
+
records = []
|
|
10
|
+
changes.each do |option|
|
|
11
|
+
record = RabbitTrack.new(option)
|
|
12
|
+
record.field_type = get_field_type(option[:action], option[:table_name], option[:attribute_name])
|
|
13
|
+
record.user = option[:user].id if option[:user].respond_to?(:id)
|
|
14
|
+
records << record
|
|
15
|
+
end
|
|
16
|
+
RabbitTrack.import records, validate: false
|
|
17
|
+
true
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# return the latest version number for this change
|
|
21
|
+
def self.get_version_number(id, table_name)
|
|
22
|
+
latest_version = RabbitTrack.where(record_id: id, table_name: table_name).maximum(:version)
|
|
23
|
+
latest_version.nil? ? 1 : latest_version.next
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.get_field_type(curd_action, table_name, field_name)
|
|
27
|
+
raise ArgumentError, 'Table name and field name cannot be blank' if table_name.blank? || field_name.blank?
|
|
28
|
+
return if curd_action.to_s.upcase == 'DELETE'
|
|
29
|
+
|
|
30
|
+
ActiveRecord::Base.connection.columns(table_name).each do |field|
|
|
31
|
+
return field.sql_type if field.name.eql?(field_name)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/railtie'
|
|
4
|
+
|
|
5
|
+
module RabbitTracks
|
|
6
|
+
class Railtie < Rails::Railtie
|
|
7
|
+
initializer 'rabbit_tracks.active_record' do
|
|
8
|
+
ActiveSupport.on_load(:active_record) do
|
|
9
|
+
include RabbitTracks::Model
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'singleton'
|
|
4
|
+
require 'yaml'
|
|
5
|
+
require 'rabbit_tracks/config'
|
|
6
|
+
require 'rabbit_tracks/controller'
|
|
7
|
+
require 'rabbit_tracks/has_rabbit_tracks'
|
|
8
|
+
require 'rabbit_tracks/rabbit_track'
|
|
9
|
+
|
|
10
|
+
# RabbitTracks's module methods can be called in both models and controllers.
|
|
11
|
+
module RabbitTracks
|
|
12
|
+
# Switches RabbitTracks on or off.
|
|
13
|
+
def self.enabled=(value)
|
|
14
|
+
RabbitTracks.config.enabled = value
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Returns `true` if RabbitTracks is on, `false` otherwise.
|
|
18
|
+
# RabbitTracks is enabled by default.
|
|
19
|
+
def self.enabled?
|
|
20
|
+
!!RabbitTracks.config.enabled
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Returns who is responsible for any changes that occur.
|
|
24
|
+
def self.whodidit
|
|
25
|
+
rabbit_tracks_store[:whodidit]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Sets who is responsible for any changes that occur.
|
|
29
|
+
# In a controller it automatically get the value from `current_user` action.
|
|
30
|
+
def self.whodidit=(value)
|
|
31
|
+
rabbit_tracks_store[:whodidit] = value
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Thread-safe hash to hold RabbitTracks's data.
|
|
35
|
+
def self.rabbit_tracks_store
|
|
36
|
+
Thread.current[:rabbit_tracks] ||= {}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Returns RabbitTracks's configuration object.
|
|
40
|
+
def self.config
|
|
41
|
+
@config ||= RabbitTracks::Config.instance
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
|
+
require 'rabbit_tracks/version'
|
|
6
|
+
|
|
7
|
+
Gem::Specification.new do |spec|
|
|
8
|
+
spec.name = 'rabbit_tracks'
|
|
9
|
+
spec.version = RabbitTracks::VERSION
|
|
10
|
+
spec.authors = ['Peter Zhang']
|
|
11
|
+
spec.email = ['peterpengnz@gmail.com']
|
|
12
|
+
|
|
13
|
+
spec.summary = 'Rabbit Tracks is a gem keeps all model changes you care about.'
|
|
14
|
+
spec.description = 'Rabbit Tracks keeps a record of who made which change at what time.
|
|
15
|
+
You can skip any database columns as well.
|
|
16
|
+
For example: `updated_at`, `created_at` and `password` etc.'
|
|
17
|
+
spec.homepage = 'https://github.com/peterpengnz/rabbit_tracks'
|
|
18
|
+
spec.license = 'MIT'
|
|
19
|
+
spec.required_ruby_version = '>= 2.7'
|
|
20
|
+
|
|
21
|
+
if spec.respond_to?(:metadata)
|
|
22
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org/'
|
|
23
|
+
|
|
24
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
25
|
+
else
|
|
26
|
+
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
|
27
|
+
'public gem pushes.'
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Specify which files should be added to the gem when it is released.
|
|
31
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
32
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
33
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
34
|
+
end
|
|
35
|
+
spec.bindir = 'exe'
|
|
36
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
37
|
+
spec.require_paths = ['lib']
|
|
38
|
+
|
|
39
|
+
spec.add_development_dependency 'bundler', '~> 2.7.2'
|
|
40
|
+
spec.add_development_dependency 'generator_spec', '~> 0.10.0'
|
|
41
|
+
spec.add_development_dependency 'rake', '~> 13.3.0'
|
|
42
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
43
|
+
spec.add_development_dependency 'rubocop', '~> 1.82.1'
|
|
44
|
+
spec.add_development_dependency 'sqlite3', '~> 2.9.0'
|
|
45
|
+
|
|
46
|
+
spec.add_dependency 'activerecord-import', '~> 2.2.0'
|
|
47
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rabbit_tracks
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Peter Zhang
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: bundler
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 2.7.2
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 2.7.2
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: generator_spec
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 0.10.0
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: 0.10.0
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rake
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: 13.3.0
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: 13.3.0
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: rspec
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '3.0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '3.0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: rubocop
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: 1.82.1
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: 1.82.1
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: sqlite3
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: 2.9.0
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: 2.9.0
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: activerecord-import
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: 2.2.0
|
|
103
|
+
type: :runtime
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: 2.2.0
|
|
110
|
+
description: |-
|
|
111
|
+
Rabbit Tracks keeps a record of who made which change at what time.
|
|
112
|
+
You can skip any database columns as well.
|
|
113
|
+
For example: `updated_at`, `created_at` and `password` etc.
|
|
114
|
+
email:
|
|
115
|
+
- peterpengnz@gmail.com
|
|
116
|
+
executables: []
|
|
117
|
+
extensions: []
|
|
118
|
+
extra_rdoc_files: []
|
|
119
|
+
files:
|
|
120
|
+
- ".DS_Store"
|
|
121
|
+
- ".github/workflows/gem-push.yml"
|
|
122
|
+
- ".gitignore"
|
|
123
|
+
- Gemfile
|
|
124
|
+
- Gemfile.lock
|
|
125
|
+
- LICENSE.txt
|
|
126
|
+
- README.md
|
|
127
|
+
- Rakefile
|
|
128
|
+
- lib/.DS_Store
|
|
129
|
+
- lib/generators/rabbit_tracks/install.rb
|
|
130
|
+
- lib/generators/rabbit_tracks/templates/create_rabbit_tracks.rb
|
|
131
|
+
- lib/rabbit_tracks.rb
|
|
132
|
+
- lib/rabbit_tracks/config.rb
|
|
133
|
+
- lib/rabbit_tracks/controller.rb
|
|
134
|
+
- lib/rabbit_tracks/has_rabbit_tracks.rb
|
|
135
|
+
- lib/rabbit_tracks/rabbit_track.rb
|
|
136
|
+
- lib/rabbit_tracks/railtie.rb
|
|
137
|
+
- lib/rabbit_tracks/version.rb
|
|
138
|
+
- rabbit_tracks.gemspec
|
|
139
|
+
homepage: https://github.com/peterpengnz/rabbit_tracks
|
|
140
|
+
licenses:
|
|
141
|
+
- MIT
|
|
142
|
+
metadata:
|
|
143
|
+
allowed_push_host: https://rubygems.org/
|
|
144
|
+
homepage_uri: https://github.com/peterpengnz/rabbit_tracks
|
|
145
|
+
rdoc_options: []
|
|
146
|
+
require_paths:
|
|
147
|
+
- lib
|
|
148
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - ">="
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: '2.7'
|
|
153
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
154
|
+
requirements:
|
|
155
|
+
- - ">="
|
|
156
|
+
- !ruby/object:Gem::Version
|
|
157
|
+
version: '0'
|
|
158
|
+
requirements: []
|
|
159
|
+
rubygems_version: 3.6.9
|
|
160
|
+
specification_version: 4
|
|
161
|
+
summary: Rabbit Tracks is a gem keeps all model changes you care about.
|
|
162
|
+
test_files: []
|