paper_trail 3.0.6 → 4.2.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 +4 -4
- data/.gitignore +5 -0
- data/.rspec +1 -2
- data/.travis.yml +14 -5
- data/CHANGELOG.md +215 -8
- data/CONTRIBUTING.md +84 -0
- data/README.md +922 -502
- data/Rakefile +2 -2
- data/doc/bug_report_template.rb +65 -0
- data/gemfiles/ar3.gemfile +61 -0
- data/lib/generators/paper_trail/install_generator.rb +22 -3
- data/lib/generators/paper_trail/templates/add_object_changes_to_versions.rb +6 -1
- data/lib/generators/paper_trail/templates/add_transaction_id_column_to_versions.rb +11 -0
- data/lib/generators/paper_trail/templates/create_version_associations.rb +17 -0
- data/lib/generators/paper_trail/templates/create_versions.rb +22 -1
- data/lib/paper_trail.rb +52 -22
- data/lib/paper_trail/attributes_serialization.rb +89 -0
- data/lib/paper_trail/cleaner.rb +32 -15
- data/lib/paper_trail/config.rb +35 -2
- data/lib/paper_trail/frameworks/active_record.rb +4 -5
- data/lib/paper_trail/frameworks/active_record/models/paper_trail/version_association.rb +7 -0
- data/lib/paper_trail/frameworks/rails.rb +1 -0
- data/lib/paper_trail/frameworks/rails/controller.rb +27 -11
- data/lib/paper_trail/frameworks/rspec.rb +5 -0
- data/lib/paper_trail/frameworks/sinatra.rb +3 -1
- data/lib/paper_trail/has_paper_trail.rb +304 -148
- data/lib/paper_trail/record_history.rb +59 -0
- data/lib/paper_trail/reifier.rb +270 -0
- data/lib/paper_trail/serializers/json.rb +13 -2
- data/lib/paper_trail/serializers/yaml.rb +16 -2
- data/lib/paper_trail/version_association_concern.rb +15 -0
- data/lib/paper_trail/version_concern.rb +160 -122
- data/lib/paper_trail/version_number.rb +3 -3
- data/paper_trail.gemspec +22 -9
- data/spec/generators/install_generator_spec.rb +4 -4
- data/spec/models/animal_spec.rb +36 -0
- data/spec/models/boolit_spec.rb +48 -0
- data/spec/models/callback_modifier_spec.rb +96 -0
- data/spec/models/fluxor_spec.rb +19 -0
- data/spec/models/gadget_spec.rb +14 -12
- data/spec/models/joined_version_spec.rb +9 -9
- data/spec/models/json_version_spec.rb +103 -0
- data/spec/models/kitchen/banana_spec.rb +14 -0
- data/spec/models/not_on_update_spec.rb +19 -0
- data/spec/models/post_with_status_spec.rb +3 -3
- data/spec/models/skipper_spec.rb +46 -0
- data/spec/models/thing_spec.rb +11 -0
- data/spec/models/version_spec.rb +195 -44
- data/spec/models/widget_spec.rb +136 -76
- data/spec/modules/paper_trail_spec.rb +27 -0
- data/spec/modules/version_concern_spec.rb +8 -8
- data/spec/modules/version_number_spec.rb +16 -16
- data/spec/paper_trail/config_spec.rb +52 -0
- data/spec/paper_trail_spec.rb +17 -17
- data/spec/rails_helper.rb +34 -0
- data/spec/requests/articles_spec.rb +10 -14
- data/spec/spec_helper.rb +81 -34
- data/spec/support/alt_db_init.rb +1 -1
- data/test/dummy/app/controllers/application_controller.rb +1 -1
- data/test/dummy/app/controllers/articles_controller.rb +4 -1
- data/test/dummy/app/models/animal.rb +2 -0
- data/test/dummy/app/models/book.rb +4 -0
- data/test/dummy/app/models/boolit.rb +4 -0
- data/test/dummy/app/models/callback_modifier.rb +45 -0
- data/test/dummy/app/models/chapter.rb +9 -0
- data/test/dummy/app/models/citation.rb +5 -0
- data/test/dummy/app/models/customer.rb +4 -0
- data/test/dummy/app/models/editor.rb +4 -0
- data/test/dummy/app/models/editorship.rb +5 -0
- data/test/dummy/app/models/fruit.rb +5 -0
- data/test/dummy/app/models/kitchen/banana.rb +5 -0
- data/test/dummy/app/models/line_item.rb +4 -0
- data/test/dummy/app/models/not_on_update.rb +4 -0
- data/test/dummy/app/models/order.rb +5 -0
- data/test/dummy/app/models/paragraph.rb +5 -0
- data/test/dummy/app/models/person.rb +13 -3
- data/test/dummy/app/models/post.rb +0 -1
- data/test/dummy/app/models/quotation.rb +5 -0
- data/test/dummy/app/models/section.rb +6 -0
- data/test/dummy/app/models/skipper.rb +6 -0
- data/test/dummy/app/models/song.rb +20 -0
- data/test/dummy/app/models/thing.rb +3 -0
- data/test/dummy/app/models/whatchamajigger.rb +4 -0
- data/test/dummy/app/models/widget.rb +5 -0
- data/test/dummy/app/versions/json_version.rb +3 -0
- data/test/dummy/app/versions/kitchen/banana_version.rb +5 -0
- data/test/dummy/config/application.rb +6 -0
- data/test/dummy/config/database.postgres.yml +1 -1
- data/test/dummy/config/environments/test.rb +5 -1
- data/test/dummy/config/initializers/paper_trail.rb +6 -1
- data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +143 -3
- data/test/dummy/db/schema.rb +169 -25
- data/test/functional/controller_test.rb +4 -2
- data/test/functional/modular_sinatra_test.rb +1 -1
- data/test/functional/sinatra_test.rb +1 -1
- data/test/paper_trail_test.rb +7 -0
- data/test/test_helper.rb +38 -2
- data/test/time_travel_helper.rb +15 -0
- data/test/unit/associations_test.rb +726 -0
- data/test/unit/inheritance_column_test.rb +6 -6
- data/test/unit/model_test.rb +109 -125
- data/test/unit/protected_attrs_test.rb +4 -3
- data/test/unit/serializer_test.rb +6 -6
- data/test/unit/serializers/json_test.rb +17 -4
- data/test/unit/serializers/yaml_test.rb +5 -1
- data/test/unit/version_test.rb +87 -69
- metadata +172 -75
- data/gemfiles/3.0.gemfile +0 -42
- data/test/dummy/public/404.html +0 -26
- data/test/dummy/public/422.html +0 -26
- data/test/dummy/public/500.html +0 -26
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/public/javascripts/application.js +0 -2
- data/test/dummy/public/javascripts/controls.js +0 -965
- data/test/dummy/public/javascripts/dragdrop.js +0 -974
- data/test/dummy/public/javascripts/effects.js +0 -1123
- data/test/dummy/public/javascripts/prototype.js +0 -6001
- data/test/dummy/public/javascripts/rails.js +0 -175
- data/test/dummy/public/stylesheets/.gitkeep +0 -0
data/Rakefile
CHANGED
|
@@ -4,7 +4,7 @@ Bundler::GemHelper.install_tasks
|
|
|
4
4
|
desc 'Set a relevant database.yml for testing'
|
|
5
5
|
task :prepare do
|
|
6
6
|
ENV["DB"] ||= "sqlite"
|
|
7
|
-
if RUBY_VERSION
|
|
7
|
+
if RUBY_VERSION >= '1.9'
|
|
8
8
|
FileUtils.cp "test/dummy/config/database.#{ENV["DB"]}.yml", "test/dummy/config/database.yml"
|
|
9
9
|
else
|
|
10
10
|
require 'ftools'
|
|
@@ -23,7 +23,7 @@ Rake::TestTask.new(:test) do |t|
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
require 'rspec/core/rake_task'
|
|
26
|
-
desc 'Run
|
|
26
|
+
desc 'Run tests on PaperTrail with RSpec'
|
|
27
27
|
RSpec::Core::RakeTask.new(:spec)
|
|
28
28
|
|
|
29
29
|
desc 'Default: run all available test suites'
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Use this template to report PaperTrail bugs.
|
|
2
|
+
# It is based on the ActiveRecord template.
|
|
3
|
+
# https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_gem.rb
|
|
4
|
+
begin
|
|
5
|
+
require 'bundler/inline'
|
|
6
|
+
rescue LoadError => e
|
|
7
|
+
$stderr.puts 'Bundler version 1.10 or later is required. Please update your Bundler'
|
|
8
|
+
raise e
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
gemfile(true) do
|
|
12
|
+
ruby '2.2.3'
|
|
13
|
+
source 'https://rubygems.org'
|
|
14
|
+
gem 'activerecord', '4.2.0'
|
|
15
|
+
gem 'minitest', '5.8.3'
|
|
16
|
+
gem 'paper_trail', '4.0.0', require: false
|
|
17
|
+
gem 'sqlite3'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
require 'active_record'
|
|
21
|
+
require 'minitest/autorun'
|
|
22
|
+
require 'logger'
|
|
23
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
|
|
24
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
|
25
|
+
require 'paper_trail'
|
|
26
|
+
|
|
27
|
+
ActiveRecord::Schema.define do
|
|
28
|
+
create_table :users, force: true do |t|
|
|
29
|
+
t.text :first_name, null: false
|
|
30
|
+
t.timestamps null: false
|
|
31
|
+
end
|
|
32
|
+
create_table :versions do |t|
|
|
33
|
+
t.string :item_type, null: false
|
|
34
|
+
t.integer :item_id, null: false
|
|
35
|
+
t.string :event, null: false
|
|
36
|
+
t.string :whodunnit
|
|
37
|
+
t.text :object, limit: 1_073_741_823
|
|
38
|
+
t.text :object_changes, limit: 1_073_741_823
|
|
39
|
+
t.integer :transaction_id
|
|
40
|
+
t.datetime :created_at
|
|
41
|
+
end
|
|
42
|
+
add_index :versions, [:item_type, :item_id]
|
|
43
|
+
add_index :versions, [:transaction_id]
|
|
44
|
+
|
|
45
|
+
create_table :version_associations do |t|
|
|
46
|
+
t.integer :version_id
|
|
47
|
+
t.string :foreign_key_name, null: false
|
|
48
|
+
t.integer :foreign_key_id
|
|
49
|
+
end
|
|
50
|
+
add_index :version_associations, [:version_id]
|
|
51
|
+
add_index :version_associations, [:foreign_key_name, :foreign_key_id],
|
|
52
|
+
name: 'index_version_associations_on_foreign_key'
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class User < ActiveRecord::Base
|
|
56
|
+
has_paper_trail
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class BugTest < ActiveSupport::TestCase
|
|
60
|
+
def test_1
|
|
61
|
+
assert_difference(-> { PaperTrail::Version.count }, +1) {
|
|
62
|
+
User.create(first_name: "Jane")
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
source 'https://rubygems.org'
|
|
2
|
+
|
|
3
|
+
gem 'activerecord', '3.2.22.2'
|
|
4
|
+
gem 'request_store', '~> 1.1.0'
|
|
5
|
+
|
|
6
|
+
# i18n 0.7 requires ruby >= 1.9.3, but we still support 1.8.7
|
|
7
|
+
gem 'i18n', '< 0.7'
|
|
8
|
+
|
|
9
|
+
# actionpack 3 depends on rack-cache ~> 1.2, but bundler seems to ignore that.
|
|
10
|
+
# Also rack-cache 1.3 dropped support for ruby 1.8.7. The simplest thing for now
|
|
11
|
+
# was to specify rack-cache ~> 1.2 here, though it should be unnecessary given
|
|
12
|
+
# the actionpack 3 dependency.
|
|
13
|
+
gem 'rack-cache', '~> 1.2.0'
|
|
14
|
+
|
|
15
|
+
group :development, :test do
|
|
16
|
+
gem 'rake', '~> 10.1.1'
|
|
17
|
+
gem 'shoulda', '~> 3.5'
|
|
18
|
+
gem 'ffaker', '<= 1.31.0'
|
|
19
|
+
|
|
20
|
+
# Testing of Sinatra
|
|
21
|
+
gem 'sinatra', '~> 1.1.4'
|
|
22
|
+
|
|
23
|
+
# RSpec testing
|
|
24
|
+
gem 'rspec-rails', '~> 3.4.2'
|
|
25
|
+
gem 'generator_spec'
|
|
26
|
+
|
|
27
|
+
# To do proper transactional testing with ActiveSupport::TestCase on MySQL
|
|
28
|
+
gem 'database_cleaner', '~> 1.2.0'
|
|
29
|
+
|
|
30
|
+
# Allow time travel in testing. timecop is only supported after 1.9.2 but does a better cleanup at 'return'
|
|
31
|
+
if RUBY_VERSION < "1.9.2"
|
|
32
|
+
gem 'delorean'
|
|
33
|
+
else
|
|
34
|
+
gem 'timecop'
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
platforms :ruby do
|
|
38
|
+
gem 'sqlite3', '~> 1.2'
|
|
39
|
+
|
|
40
|
+
# We would prefer to only constrain mysql2 to '~> 0.3',
|
|
41
|
+
# but a rails bug (https://github.com/rails/rails/issues/21544)
|
|
42
|
+
# requires us to constrain to '~> 0.3.20' for now.
|
|
43
|
+
gem 'mysql2', '~> 0.3.20'
|
|
44
|
+
|
|
45
|
+
gem 'pg', '~> 0.17.1'
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
platforms :jruby, :ruby_18 do
|
|
49
|
+
# shoulda-matchers > 2.0 is not compatible with Ruby18.
|
|
50
|
+
# Since we can't specify difference between JRuby 18/19, we need to use shoulda-matchers 1.5 for all JRuby testing.
|
|
51
|
+
gem 'shoulda-matchers', '~> 1.5'
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
platforms :jruby do
|
|
55
|
+
# Use jRuby's sqlite3 adapter for jRuby
|
|
56
|
+
gem 'activerecord-jdbcsqlite3-adapter', '~> 1.3'
|
|
57
|
+
gem 'activerecord-jdbcpostgresql-adapter', '~> 1.3'
|
|
58
|
+
gem 'activerecord-jdbcmysql-adapter', '~> 1.3'
|
|
59
|
+
gem 'activerecord-jdbc-adapter', '1.3.15'
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -6,17 +6,36 @@ module PaperTrail
|
|
|
6
6
|
include ::Rails::Generators::Migration
|
|
7
7
|
|
|
8
8
|
source_root File.expand_path('../templates', __FILE__)
|
|
9
|
-
class_option :with_changes, :type => :boolean, :default => false,
|
|
9
|
+
class_option :with_changes, :type => :boolean, :default => false,
|
|
10
|
+
:desc => "Store changeset (diff) with each version"
|
|
11
|
+
class_option :with_associations, :type => :boolean, :default => false,
|
|
12
|
+
:desc => "Store transactional IDs to support association restoration"
|
|
10
13
|
|
|
11
14
|
desc 'Generates (but does not run) a migration to add a versions table.'
|
|
12
15
|
|
|
13
16
|
def create_migration_file
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
add_paper_trail_migration('create_versions')
|
|
18
|
+
add_paper_trail_migration('add_object_changes_to_versions') if options.with_changes?
|
|
19
|
+
if options.with_associations?
|
|
20
|
+
add_paper_trail_migration('create_version_associations')
|
|
21
|
+
add_paper_trail_migration('add_transaction_id_column_to_versions')
|
|
22
|
+
end
|
|
16
23
|
end
|
|
17
24
|
|
|
18
25
|
def self.next_migration_number(dirname)
|
|
19
26
|
::ActiveRecord::Generators::Base.next_migration_number(dirname)
|
|
20
27
|
end
|
|
28
|
+
|
|
29
|
+
protected
|
|
30
|
+
def add_paper_trail_migration(template)
|
|
31
|
+
migration_dir = File.expand_path('db/migrate')
|
|
32
|
+
|
|
33
|
+
unless self.class.migration_exists?(migration_dir, template)
|
|
34
|
+
migration_template "#{template}.rb", "db/migrate/#{template}.rb"
|
|
35
|
+
else
|
|
36
|
+
warn("ALERT: Migration already exists named '#{template}'." +
|
|
37
|
+
" Please check your migrations directory before re-running")
|
|
38
|
+
end
|
|
39
|
+
end
|
|
21
40
|
end
|
|
22
41
|
end
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
class AddObjectChangesToVersions < ActiveRecord::Migration
|
|
2
|
+
|
|
3
|
+
# The largest text column available in all supported RDBMS.
|
|
4
|
+
# See `create_versions.rb` for details.
|
|
5
|
+
TEXT_BYTES = 1_073_741_823
|
|
6
|
+
|
|
2
7
|
def change
|
|
3
|
-
add_column :versions, :object_changes, :text
|
|
8
|
+
add_column :versions, :object_changes, :text, :limit => TEXT_BYTES
|
|
4
9
|
end
|
|
5
10
|
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class AddTransactionIdColumnToVersions < ActiveRecord::Migration
|
|
2
|
+
def self.up
|
|
3
|
+
add_column :versions, :transaction_id, :integer
|
|
4
|
+
add_index :versions, [:transaction_id]
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def self.down
|
|
8
|
+
remove_index :versions, [:transaction_id]
|
|
9
|
+
remove_column :versions, :transaction_id
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class CreateVersionAssociations < ActiveRecord::Migration
|
|
2
|
+
def self.up
|
|
3
|
+
create_table :version_associations do |t|
|
|
4
|
+
t.integer :version_id
|
|
5
|
+
t.string :foreign_key_name, :null => false
|
|
6
|
+
t.integer :foreign_key_id
|
|
7
|
+
end
|
|
8
|
+
add_index :version_associations, [:version_id]
|
|
9
|
+
add_index :version_associations, [:foreign_key_name, :foreign_key_id], :name => 'index_version_associations_on_foreign_key'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.down
|
|
13
|
+
remove_index :version_associations, [:version_id]
|
|
14
|
+
remove_index :version_associations, :name => 'index_version_associations_on_foreign_key'
|
|
15
|
+
drop_table :version_associations
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -1,11 +1,32 @@
|
|
|
1
1
|
class CreateVersions < ActiveRecord::Migration
|
|
2
|
+
|
|
3
|
+
# The largest text column available in all supported RDBMS is
|
|
4
|
+
# 1024^3 - 1 bytes, roughly one gibibyte. We specify a size
|
|
5
|
+
# so that MySQL will use `longtext` instead of `text`. Otherwise,
|
|
6
|
+
# when serializing very large objects, `text` might not be big enough.
|
|
7
|
+
TEXT_BYTES = 1_073_741_823
|
|
8
|
+
|
|
2
9
|
def change
|
|
3
10
|
create_table :versions do |t|
|
|
4
11
|
t.string :item_type, :null => false
|
|
5
12
|
t.integer :item_id, :null => false
|
|
6
13
|
t.string :event, :null => false
|
|
7
14
|
t.string :whodunnit
|
|
8
|
-
t.text :object
|
|
15
|
+
t.text :object, :limit => TEXT_BYTES
|
|
16
|
+
|
|
17
|
+
# Known issue in MySQL: fractional second precision
|
|
18
|
+
# -------------------------------------------------
|
|
19
|
+
#
|
|
20
|
+
# MySQL timestamp columns do not support fractional seconds unless
|
|
21
|
+
# defined with "fractional seconds precision". MySQL users should manually
|
|
22
|
+
# add fractional seconds precision to this migration, specifically, to
|
|
23
|
+
# the `created_at` column.
|
|
24
|
+
# (https://dev.mysql.com/doc/refman/5.6/en/fractional-seconds.html)
|
|
25
|
+
#
|
|
26
|
+
# MySQL users should also upgrade to rails 4.2, which is the first
|
|
27
|
+
# version of ActiveRecord with support for fractional seconds in MySQL.
|
|
28
|
+
# (https://github.com/rails/rails/pull/14359)
|
|
29
|
+
#
|
|
9
30
|
t.datetime :created_at
|
|
10
31
|
end
|
|
11
32
|
add_index :versions, [:item_type, :item_id]
|
data/lib/paper_trail.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
require 'request_store'
|
|
2
|
+
|
|
3
|
+
# Require files in lib/paper_trail, but not its subdirectories.
|
|
2
4
|
Dir[File.join(File.dirname(__FILE__), 'paper_trail', '*.rb')].each do |file|
|
|
3
5
|
require File.join('paper_trail', File.basename(file, '.rb'))
|
|
4
6
|
end
|
|
@@ -22,6 +24,14 @@ module PaperTrail
|
|
|
22
24
|
!!PaperTrail.config.enabled
|
|
23
25
|
end
|
|
24
26
|
|
|
27
|
+
def self.serialized_attributes?
|
|
28
|
+
ActiveSupport::Deprecation.warn(
|
|
29
|
+
"PaperTrail.serialized_attributes? is deprecated without replacement " +
|
|
30
|
+
"and always returns false."
|
|
31
|
+
)
|
|
32
|
+
false
|
|
33
|
+
end
|
|
34
|
+
|
|
25
35
|
# Sets whether PaperTrail is enabled or disabled for the current request.
|
|
26
36
|
def self.enabled_for_controller=(value)
|
|
27
37
|
paper_trail_store[:request_enabled_for_controller] = value
|
|
@@ -34,12 +44,14 @@ module PaperTrail
|
|
|
34
44
|
!!paper_trail_store[:request_enabled_for_controller]
|
|
35
45
|
end
|
|
36
46
|
|
|
37
|
-
# Sets whether PaperTrail is enabled or disabled for this model in the
|
|
47
|
+
# Sets whether PaperTrail is enabled or disabled for this model in the
|
|
48
|
+
# current request.
|
|
38
49
|
def self.enabled_for_model(model, value)
|
|
39
50
|
paper_trail_store[:"enabled_for_#{model}"] = value
|
|
40
51
|
end
|
|
41
52
|
|
|
42
|
-
# Returns `true` if PaperTrail is enabled for this model in the current
|
|
53
|
+
# Returns `true` if PaperTrail is enabled for this model in the current
|
|
54
|
+
# request, `false` otherwise.
|
|
43
55
|
def self.enabled_for_model?(model)
|
|
44
56
|
!!paper_trail_store.fetch(:"enabled_for_#{model}", true)
|
|
45
57
|
end
|
|
@@ -54,10 +66,9 @@ module PaperTrail
|
|
|
54
66
|
PaperTrail.config.timestamp_field
|
|
55
67
|
end
|
|
56
68
|
|
|
57
|
-
# Sets who is responsible for any changes that occur.
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
# automatically to the `current_user`.
|
|
69
|
+
# Sets who is responsible for any changes that occur. You would normally use
|
|
70
|
+
# this in a migration or on the console, when working with models directly.
|
|
71
|
+
# In a controller it is set automatically to the `current_user`.
|
|
61
72
|
def self.whodunnit=(value)
|
|
62
73
|
paper_trail_store[:whodunnit] = value
|
|
63
74
|
end
|
|
@@ -67,8 +78,8 @@ module PaperTrail
|
|
|
67
78
|
paper_trail_store[:whodunnit]
|
|
68
79
|
end
|
|
69
80
|
|
|
70
|
-
# Sets any information from the controller that you want PaperTrail
|
|
71
|
-
#
|
|
81
|
+
# Sets any information from the controller that you want PaperTrail to
|
|
82
|
+
# store. By default this is set automatically by a before filter.
|
|
72
83
|
def self.controller_info=(value)
|
|
73
84
|
paper_trail_store[:controller_info] = value
|
|
74
85
|
end
|
|
@@ -91,33 +102,51 @@ module PaperTrail
|
|
|
91
102
|
end
|
|
92
103
|
|
|
93
104
|
def self.active_record_protected_attributes?
|
|
94
|
-
@active_record_protected_attributes ||= ::ActiveRecord::VERSION::MAJOR < 4 ||
|
|
105
|
+
@active_record_protected_attributes ||= ::ActiveRecord::VERSION::MAJOR < 4 ||
|
|
106
|
+
!!defined?(ProtectedAttributes)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def self.transaction?
|
|
110
|
+
::ActiveRecord::Base.connection.open_transactions > 0
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def self.transaction_id
|
|
114
|
+
paper_trail_store[:transaction_id]
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def self.transaction_id=(id)
|
|
118
|
+
paper_trail_store[:transaction_id] = id
|
|
95
119
|
end
|
|
96
120
|
|
|
97
121
|
private
|
|
98
122
|
|
|
99
|
-
# Thread-safe hash to hold PaperTrail's data.
|
|
100
|
-
#
|
|
123
|
+
# Thread-safe hash to hold PaperTrail's data. Initializing with needed
|
|
124
|
+
# default values.
|
|
101
125
|
def self.paper_trail_store
|
|
102
|
-
|
|
126
|
+
RequestStore.store[:paper_trail] ||= { :request_enabled_for_controller => true }
|
|
103
127
|
end
|
|
104
128
|
|
|
105
129
|
# Returns PaperTrail's configuration object.
|
|
106
130
|
def self.config
|
|
107
|
-
|
|
131
|
+
@config ||= PaperTrail::Config.instance
|
|
132
|
+
yield @config if block_given?
|
|
133
|
+
@config
|
|
108
134
|
end
|
|
109
135
|
|
|
110
|
-
|
|
111
|
-
|
|
136
|
+
class << self
|
|
137
|
+
alias_method :configure, :config
|
|
112
138
|
end
|
|
113
139
|
end
|
|
114
140
|
|
|
115
|
-
# Ensure `ProtectedAttributes` gem gets required if it is available before the
|
|
141
|
+
# Ensure `ProtectedAttributes` gem gets required if it is available before the
|
|
142
|
+
# `Version` class gets loaded in.
|
|
116
143
|
unless PaperTrail.active_record_protected_attributes?
|
|
117
144
|
PaperTrail.send(:remove_instance_variable, :@active_record_protected_attributes)
|
|
118
145
|
begin
|
|
119
146
|
require 'protected_attributes'
|
|
120
|
-
rescue LoadError
|
|
147
|
+
rescue LoadError
|
|
148
|
+
# In case `ProtectedAttributes` gem is not available.
|
|
149
|
+
end
|
|
121
150
|
end
|
|
122
151
|
|
|
123
152
|
ActiveSupport.on_load(:active_record) do
|
|
@@ -125,8 +154,9 @@ ActiveSupport.on_load(:active_record) do
|
|
|
125
154
|
end
|
|
126
155
|
|
|
127
156
|
# Require frameworks
|
|
128
|
-
require 'paper_trail/frameworks/active_record'
|
|
129
157
|
require 'paper_trail/frameworks/sinatra'
|
|
130
|
-
|
|
131
|
-
require 'paper_trail/frameworks/
|
|
132
|
-
|
|
158
|
+
if defined?(::Rails) && ActiveRecord::VERSION::STRING >= '3.2'
|
|
159
|
+
require 'paper_trail/frameworks/rails'
|
|
160
|
+
else
|
|
161
|
+
require 'paper_trail/frameworks/active_record'
|
|
162
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
module PaperTrail
|
|
2
|
+
module AttributesSerialization
|
|
3
|
+
class NoOpAttribute
|
|
4
|
+
def type_cast_for_database(value)
|
|
5
|
+
value
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def type_cast_from_database(data)
|
|
9
|
+
data
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
NO_OP_ATTRIBUTE = NoOpAttribute.new
|
|
13
|
+
|
|
14
|
+
class SerializedAttribute
|
|
15
|
+
def initialize(coder)
|
|
16
|
+
@coder = coder.respond_to?(:dump) ? coder : PaperTrail.serializer
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def type_cast_for_database(value)
|
|
20
|
+
@coder.dump(value)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def type_cast_from_database(data)
|
|
24
|
+
@coder.load(data)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
SERIALIZE, DESERIALIZE =
|
|
29
|
+
if ::ActiveRecord::VERSION::MAJOR >= 5
|
|
30
|
+
[:serialize, :deserialize]
|
|
31
|
+
else
|
|
32
|
+
[:type_cast_for_database, :type_cast_from_database]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
if ::ActiveRecord::VERSION::STRING < '4.2'
|
|
36
|
+
# Backport Rails 4.2 and later's `type_for_attribute` to build
|
|
37
|
+
# on a common interface.
|
|
38
|
+
def type_for_attribute(attr_name)
|
|
39
|
+
serialized_attribute_types[attr_name.to_s] || NO_OP_ATTRIBUTE
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def serialized_attribute_types
|
|
43
|
+
@attribute_types ||= Hash[serialized_attributes.map do |attr_name, coder|
|
|
44
|
+
[attr_name, SerializedAttribute.new(coder)]
|
|
45
|
+
end]
|
|
46
|
+
end
|
|
47
|
+
private :serialized_attribute_types
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Used for `Version#object` attribute.
|
|
51
|
+
def serialize_attributes_for_paper_trail!(attributes)
|
|
52
|
+
alter_attributes_for_paper_trail!(SERIALIZE, attributes)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def unserialize_attributes_for_paper_trail!(attributes)
|
|
56
|
+
alter_attributes_for_paper_trail!(DESERIALIZE, attributes)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def alter_attributes_for_paper_trail!(serializer, attributes)
|
|
60
|
+
# Don't serialize before values before inserting into columns of type
|
|
61
|
+
# `JSON` on `PostgreSQL` databases.
|
|
62
|
+
return attributes if paper_trail_version_class.object_col_is_json?
|
|
63
|
+
|
|
64
|
+
attributes.each do |key, value|
|
|
65
|
+
attributes[key] = type_for_attribute(key).send(serializer, value)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Used for Version#object_changes attribute.
|
|
70
|
+
def serialize_attribute_changes_for_paper_trail!(changes)
|
|
71
|
+
alter_attribute_changes_for_paper_trail!(SERIALIZE, changes)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def unserialize_attribute_changes_for_paper_trail!(changes)
|
|
75
|
+
alter_attribute_changes_for_paper_trail!(DESERIALIZE, changes)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def alter_attribute_changes_for_paper_trail!(serializer, changes)
|
|
79
|
+
# Don't serialize before values before inserting into columns of type
|
|
80
|
+
# `JSON` on `PostgreSQL` databases.
|
|
81
|
+
return changes if paper_trail_version_class.object_changes_col_is_json?
|
|
82
|
+
|
|
83
|
+
changes.clone.each do |key, change|
|
|
84
|
+
type = type_for_attribute(key)
|
|
85
|
+
changes[key] = Array(change).map { |value| type.send(serializer, value) }
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|