paper_trail 3.0.6 → 4.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|