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.
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