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.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -0
  3. data/.rspec +1 -2
  4. data/.travis.yml +14 -5
  5. data/CHANGELOG.md +215 -8
  6. data/CONTRIBUTING.md +84 -0
  7. data/README.md +922 -502
  8. data/Rakefile +2 -2
  9. data/doc/bug_report_template.rb +65 -0
  10. data/gemfiles/ar3.gemfile +61 -0
  11. data/lib/generators/paper_trail/install_generator.rb +22 -3
  12. data/lib/generators/paper_trail/templates/add_object_changes_to_versions.rb +6 -1
  13. data/lib/generators/paper_trail/templates/add_transaction_id_column_to_versions.rb +11 -0
  14. data/lib/generators/paper_trail/templates/create_version_associations.rb +17 -0
  15. data/lib/generators/paper_trail/templates/create_versions.rb +22 -1
  16. data/lib/paper_trail.rb +52 -22
  17. data/lib/paper_trail/attributes_serialization.rb +89 -0
  18. data/lib/paper_trail/cleaner.rb +32 -15
  19. data/lib/paper_trail/config.rb +35 -2
  20. data/lib/paper_trail/frameworks/active_record.rb +4 -5
  21. data/lib/paper_trail/frameworks/active_record/models/paper_trail/version_association.rb +7 -0
  22. data/lib/paper_trail/frameworks/rails.rb +1 -0
  23. data/lib/paper_trail/frameworks/rails/controller.rb +27 -11
  24. data/lib/paper_trail/frameworks/rspec.rb +5 -0
  25. data/lib/paper_trail/frameworks/sinatra.rb +3 -1
  26. data/lib/paper_trail/has_paper_trail.rb +304 -148
  27. data/lib/paper_trail/record_history.rb +59 -0
  28. data/lib/paper_trail/reifier.rb +270 -0
  29. data/lib/paper_trail/serializers/json.rb +13 -2
  30. data/lib/paper_trail/serializers/yaml.rb +16 -2
  31. data/lib/paper_trail/version_association_concern.rb +15 -0
  32. data/lib/paper_trail/version_concern.rb +160 -122
  33. data/lib/paper_trail/version_number.rb +3 -3
  34. data/paper_trail.gemspec +22 -9
  35. data/spec/generators/install_generator_spec.rb +4 -4
  36. data/spec/models/animal_spec.rb +36 -0
  37. data/spec/models/boolit_spec.rb +48 -0
  38. data/spec/models/callback_modifier_spec.rb +96 -0
  39. data/spec/models/fluxor_spec.rb +19 -0
  40. data/spec/models/gadget_spec.rb +14 -12
  41. data/spec/models/joined_version_spec.rb +9 -9
  42. data/spec/models/json_version_spec.rb +103 -0
  43. data/spec/models/kitchen/banana_spec.rb +14 -0
  44. data/spec/models/not_on_update_spec.rb +19 -0
  45. data/spec/models/post_with_status_spec.rb +3 -3
  46. data/spec/models/skipper_spec.rb +46 -0
  47. data/spec/models/thing_spec.rb +11 -0
  48. data/spec/models/version_spec.rb +195 -44
  49. data/spec/models/widget_spec.rb +136 -76
  50. data/spec/modules/paper_trail_spec.rb +27 -0
  51. data/spec/modules/version_concern_spec.rb +8 -8
  52. data/spec/modules/version_number_spec.rb +16 -16
  53. data/spec/paper_trail/config_spec.rb +52 -0
  54. data/spec/paper_trail_spec.rb +17 -17
  55. data/spec/rails_helper.rb +34 -0
  56. data/spec/requests/articles_spec.rb +10 -14
  57. data/spec/spec_helper.rb +81 -34
  58. data/spec/support/alt_db_init.rb +1 -1
  59. data/test/dummy/app/controllers/application_controller.rb +1 -1
  60. data/test/dummy/app/controllers/articles_controller.rb +4 -1
  61. data/test/dummy/app/models/animal.rb +2 -0
  62. data/test/dummy/app/models/book.rb +4 -0
  63. data/test/dummy/app/models/boolit.rb +4 -0
  64. data/test/dummy/app/models/callback_modifier.rb +45 -0
  65. data/test/dummy/app/models/chapter.rb +9 -0
  66. data/test/dummy/app/models/citation.rb +5 -0
  67. data/test/dummy/app/models/customer.rb +4 -0
  68. data/test/dummy/app/models/editor.rb +4 -0
  69. data/test/dummy/app/models/editorship.rb +5 -0
  70. data/test/dummy/app/models/fruit.rb +5 -0
  71. data/test/dummy/app/models/kitchen/banana.rb +5 -0
  72. data/test/dummy/app/models/line_item.rb +4 -0
  73. data/test/dummy/app/models/not_on_update.rb +4 -0
  74. data/test/dummy/app/models/order.rb +5 -0
  75. data/test/dummy/app/models/paragraph.rb +5 -0
  76. data/test/dummy/app/models/person.rb +13 -3
  77. data/test/dummy/app/models/post.rb +0 -1
  78. data/test/dummy/app/models/quotation.rb +5 -0
  79. data/test/dummy/app/models/section.rb +6 -0
  80. data/test/dummy/app/models/skipper.rb +6 -0
  81. data/test/dummy/app/models/song.rb +20 -0
  82. data/test/dummy/app/models/thing.rb +3 -0
  83. data/test/dummy/app/models/whatchamajigger.rb +4 -0
  84. data/test/dummy/app/models/widget.rb +5 -0
  85. data/test/dummy/app/versions/json_version.rb +3 -0
  86. data/test/dummy/app/versions/kitchen/banana_version.rb +5 -0
  87. data/test/dummy/config/application.rb +6 -0
  88. data/test/dummy/config/database.postgres.yml +1 -1
  89. data/test/dummy/config/environments/test.rb +5 -1
  90. data/test/dummy/config/initializers/paper_trail.rb +6 -1
  91. data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +143 -3
  92. data/test/dummy/db/schema.rb +169 -25
  93. data/test/functional/controller_test.rb +4 -2
  94. data/test/functional/modular_sinatra_test.rb +1 -1
  95. data/test/functional/sinatra_test.rb +1 -1
  96. data/test/paper_trail_test.rb +7 -0
  97. data/test/test_helper.rb +38 -2
  98. data/test/time_travel_helper.rb +15 -0
  99. data/test/unit/associations_test.rb +726 -0
  100. data/test/unit/inheritance_column_test.rb +6 -6
  101. data/test/unit/model_test.rb +109 -125
  102. data/test/unit/protected_attrs_test.rb +4 -3
  103. data/test/unit/serializer_test.rb +6 -6
  104. data/test/unit/serializers/json_test.rb +17 -4
  105. data/test/unit/serializers/yaml_test.rb +5 -1
  106. data/test/unit/version_test.rb +87 -69
  107. metadata +172 -75
  108. data/gemfiles/3.0.gemfile +0 -42
  109. data/test/dummy/public/404.html +0 -26
  110. data/test/dummy/public/422.html +0 -26
  111. data/test/dummy/public/500.html +0 -26
  112. data/test/dummy/public/favicon.ico +0 -0
  113. data/test/dummy/public/javascripts/application.js +0 -2
  114. data/test/dummy/public/javascripts/controls.js +0 -965
  115. data/test/dummy/public/javascripts/dragdrop.js +0 -974
  116. data/test/dummy/public/javascripts/effects.js +0 -1123
  117. data/test/dummy/public/javascripts/prototype.js +0 -6001
  118. data/test/dummy/public/javascripts/rails.js +0 -175
  119. 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.to_f >= 1.9
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 PaperTrail specs for the RSpec helper.'
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, :desc => "Store changeset (diff) with each version"
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
- migration_template 'create_versions.rb', 'db/migrate/create_versions.rb'
15
- migration_template 'add_object_changes_to_versions.rb', 'db/migrate/add_object_changes_to_versions.rb' if options.with_changes?
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
- # Require core library
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 current request.
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 request, `false` otherwise.
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
- # You would normally use this in a migration or on the console,
59
- # when working with models directly. In a controller it is set
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
- # to store. By default this is set automatically by a before filter.
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 || !!defined?(ProtectedAttributes)
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
- # Initializing with needed default values.
123
+ # Thread-safe hash to hold PaperTrail's data. Initializing with needed
124
+ # default values.
101
125
  def self.paper_trail_store
102
- Thread.current[:paper_trail] ||= { :request_enabled_for_controller => true }
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
- @@config ||= PaperTrail::Config.instance
131
+ @config ||= PaperTrail::Config.instance
132
+ yield @config if block_given?
133
+ @config
108
134
  end
109
135
 
110
- def self.configure
111
- yield config
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 `Version` class gets loaded in
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; end # will rescue if `ProtectedAttributes` gem is not available
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
- require 'paper_trail/frameworks/rails' if defined? Rails
131
- require 'paper_trail/frameworks/rspec' if defined? RSpec::Core
132
- require 'paper_trail/frameworks/cucumber' if defined? World
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