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 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
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
+
7
+ gemspec
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
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
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,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RabbitTracks
4
+ class Config
5
+ include Singleton
6
+ attr_accessor :enabled
7
+
8
+ def initialize
9
+ # Indicates whether RabbitTracks is on or off.
10
+ @enabled = true
11
+ end
12
+ end
13
+ 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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RabbitTracks
4
+ VERSION = '1.0.0'
5
+ 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: []