paper_trail 2.7.0 → 2.7.1

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.
data/CHANGELOG.md CHANGED
@@ -1,10 +1,27 @@
1
+ ## 2.7.1
2
+
3
+ - [#206](https://github.com/airblade/paper_trail/issues/206) - Fixed Ruby 1.8.7 compatibility for tracking `object_changes`.
4
+ - [#200](https://github.com/airblade/paper_trail/issues/200) - Fixed `next_version` method so that it returns the live model
5
+ when called on latest reified version of a model prior to the live model.
6
+ - [#197](https://github.com/airblade/paper_trail/issues/197) - PaperTrail now falls back on using YAML for serialization of
7
+ serialized model attributes for storage in the `object` and `object_changes` columns in the `Version` table. This fixes
8
+ compatibility for `Rails 3.0.x` for projects that employ the `serialize` declaration on a model.
9
+ - [#194](https://github.com/airblade/paper_trail/issues/194) - A JSON serializer is now included in the gem.
10
+ - [#192](https://github.com/airblade/paper_trail/pull/192) - `object_changes` should store serialized representation of serialized
11
+ attributes for `create` actions (in addition to `update` actions, which had already been patched by
12
+ [#180](https://github.com/airblade/paper_trail/pull/180)).
13
+ - [#190](https://github.com/airblade/paper_trail/pull/190) - Fixed compatibility with
14
+ [SerializedAttributes](https://github.com/technoweenie/serialized_attributes) gem.
15
+ - [#189](https://github.com/airblade/paper_trail/pull/189) - Provided support for a `configure` block initializer.
16
+ - Added `setter` method for the `serializer` config option.
17
+
1
18
  ## 2.7.0
2
19
 
3
- - [#164](https://github.com/airblade/paper_trail/pull/164) - Allow for custom serializer for storage of object attributes.
4
- - [#180](https://github.com/airblade/paper_trail/pull/180) - Store serialized representation of serialized attributes
5
- on the `object_changes` column in the `Version` table.
6
20
  - [#183](https://github.com/airblade/paper_trail/pull/183) - Fully qualify the `Version` class to help prevent
7
21
  namespace resolution errors within other gems / plugins.
22
+ - [#180](https://github.com/airblade/paper_trail/pull/180) - Store serialized representation of serialized attributes
23
+ on the `object` and `object_changes` columns in the `Version` table.
24
+ - [#164](https://github.com/airblade/paper_trail/pull/164) - Allow usage of custom serializer for storage of object attributes.
8
25
 
9
26
  ## 2.6.4
10
27
 
data/README.md CHANGED
@@ -749,7 +749,10 @@ By default, PaperTrail stores your changes as a YAML dump. You can override this
749
749
  >> PaperTrail.serializer = MyCustomSerializer
750
750
  ```
751
751
 
752
- The serializer needs to be a class that responds to a `load` and `dump` method.
752
+ A valid serializer is a `module` (or `class`) that defines a `load` and `dump` method. These serializers are included in the gem for your convenience:
753
+
754
+ * [Yaml](https://github.com/airblade/paper_trail/blob/master/lib/paper_trail/serializers/yaml.rb) - Default
755
+ * [Json](https://github.com/airblade/paper_trail/blob/master/lib/paper_trail/serializers/json.rb)
753
756
 
754
757
  ## Deleting Old Versions
755
758
 
@@ -860,6 +863,7 @@ Many thanks to:
860
863
  * [Yves Senn](https://github.com/senny)
861
864
  * [Tyler Rick](https://github.com/TylerRick)
862
865
  * [Bradley Priest](https://github.com/bradleypriest)
866
+ * [David Butler](https://github.com/dwbutler)
863
867
 
864
868
 
865
869
  ## Inspirations
data/lib/paper_trail.rb CHANGED
@@ -1,10 +1,10 @@
1
- require 'yaml'
2
-
3
1
  require 'paper_trail/config'
4
2
  require 'paper_trail/controller'
5
3
  require 'paper_trail/has_paper_trail'
6
4
  require 'paper_trail/version'
5
+
7
6
  require 'paper_trail/serializers/yaml'
7
+ require 'paper_trail/serializers/json'
8
8
 
9
9
  # PaperTrail's module methods can be called in both models and controllers.
10
10
  module PaperTrail
@@ -69,6 +69,11 @@ module PaperTrail
69
69
  paper_trail_store[:controller_info] = value
70
70
  end
71
71
 
72
+ # Getter and Setter for PaperTrail Serializer
73
+ def self.serializer=(value)
74
+ PaperTrail.config.serializer = value
75
+ end
76
+
72
77
  def self.serializer
73
78
  PaperTrail.config.serializer
74
79
  end
@@ -88,6 +93,10 @@ module PaperTrail
88
93
  @@config ||= PaperTrail::Config.instance
89
94
  end
90
95
 
96
+ def self.configure
97
+ yield config
98
+ end
99
+
91
100
  end
92
101
 
93
102
 
@@ -82,17 +82,19 @@ module PaperTrail
82
82
  end
83
83
 
84
84
  # Used for Version#object attribute
85
- def serialize_attributes(attributes)
85
+ def serialize_attributes_for_paper_trail(attributes)
86
86
  serialized_attributes.each do |key, coder|
87
87
  if attributes.key?(key)
88
+ coder = PaperTrail::Serializers::Yaml unless coder.respond_to?(:dump) # Fall back to YAML if `coder` has no `dump` method
88
89
  attributes[key] = coder.dump(attributes[key])
89
90
  end
90
91
  end
91
92
  end
92
93
 
93
- def unserialize_attributes(attributes)
94
+ def unserialize_attributes_for_paper_trail(attributes)
94
95
  serialized_attributes.each do |key, coder|
95
96
  if attributes.key?(key)
97
+ coder = PaperTrail::Serializers::Yaml unless coder.respond_to?(:dump)
96
98
  attributes[key] = coder.load(attributes[key])
97
99
  end
98
100
  end
@@ -102,6 +104,7 @@ module PaperTrail
102
104
  def serialize_attribute_changes(changes)
103
105
  serialized_attributes.each do |key, coder|
104
106
  if changes.key?(key)
107
+ coder = PaperTrail::Serializers::Yaml unless coder.respond_to?(:dump) # Fall back to YAML if `coder` has no `dump` method
105
108
  old_value, new_value = changes[key]
106
109
  changes[key] = [coder.dump(old_value),
107
110
  coder.dump(new_value)]
@@ -112,6 +115,7 @@ module PaperTrail
112
115
  def unserialize_attribute_changes(changes)
113
116
  serialized_attributes.each do |key, coder|
114
117
  if changes.key?(key)
118
+ coder = PaperTrail::Serializers::Yaml unless coder.respond_to?(:dump)
115
119
  old_value, new_value = changes[key]
116
120
  changes[key] = [coder.load(old_value),
117
121
  coder.load(new_value)]
@@ -151,15 +155,17 @@ module PaperTrail
151
155
  # Returns the object (not a Version) as it was most recently.
152
156
  def previous_version
153
157
  preceding_version = source_version ? source_version.previous : send(self.class.versions_association_name).last
154
- preceding_version.try :reify
158
+ preceding_version.reify if preceding_version
155
159
  end
156
160
 
157
161
  # Returns the object (not a Version) as it became next.
162
+ # NOTE: if self (the item) was not reified from a version, i.e. it is the
163
+ # "live" item, we return nil. Perhaps we should return self instead?
158
164
  def next_version
159
- # NOTE: if self (the item) was not reified from a version, i.e. it is the
160
- # "live" item, we return nil. Perhaps we should return self instead?
161
- subsequent_version = source_version ? source_version.next : nil
162
- subsequent_version.reify if subsequent_version
165
+ subsequent_version = source_version.next
166
+ subsequent_version ? subsequent_version.reify : self.class.find(self.id)
167
+ rescue
168
+ nil
163
169
  end
164
170
 
165
171
  # Executes the given method or block without creating a new version.
@@ -189,7 +195,7 @@ module PaperTrail
189
195
  }
190
196
 
191
197
  if changed_notably? and version_class.column_names.include?('object_changes')
192
- data[:object_changes] = changes_for_paper_trail.to_yaml
198
+ data[:object_changes] = PaperTrail.serializer.dump(changes_for_paper_trail)
193
199
  end
194
200
 
195
201
  send(self.class.versions_association_name).create merge_metadata(data)
@@ -211,8 +217,8 @@ module PaperTrail
211
217
  end
212
218
 
213
219
  def changes_for_paper_trail
214
- self.changes.keep_if do |key, value|
215
- notably_changed.include?(key)
220
+ self.changes.delete_if do |key, value|
221
+ !notably_changed.include?(key)
216
222
  end.tap do |changes|
217
223
  self.class.serialize_attribute_changes(changes) # Use serialized value for attributes when necessary
218
224
  end
@@ -264,7 +270,7 @@ module PaperTrail
264
270
 
265
271
  def object_to_string(object)
266
272
  _attrs = object.attributes.except(*self.class.paper_trail_options[:skip]).tap do |attributes|
267
- self.class.serialize_attributes attributes
273
+ self.class.serialize_attributes_for_paper_trail attributes
268
274
  end
269
275
  PaperTrail.serializer.dump(_attrs)
270
276
  end
@@ -0,0 +1,17 @@
1
+ require 'active_support/json'
2
+
3
+ module PaperTrail
4
+ module Serializers
5
+ module Json
6
+ extend self # makes all instance methods become module methods as well
7
+
8
+ def load(string)
9
+ ActiveSupport::JSON.decode string
10
+ end
11
+
12
+ def dump(object)
13
+ ActiveSupport::JSON.encode object
14
+ end
15
+ end
16
+ end
17
+ end
@@ -2,13 +2,15 @@ require 'yaml'
2
2
 
3
3
  module PaperTrail
4
4
  module Serializers
5
- class Yaml
6
- def self.load(string)
5
+ module Yaml
6
+ extend self # makes all instance methods become module methods as well
7
+
8
+ def load(string)
7
9
  YAML.load string
8
10
  end
9
11
 
10
- def self.dump(hash)
11
- YAML.dump hash
12
+ def dump(object)
13
+ YAML.dump object
12
14
  end
13
15
  end
14
16
  end
@@ -79,10 +79,10 @@ class Version < ActiveRecord::Base
79
79
  model = klass.new
80
80
  end
81
81
 
82
- model.class.unserialize_attributes attrs
82
+ model.class.unserialize_attributes_for_paper_trail attrs
83
83
  attrs.each do |k, v|
84
84
  if model.respond_to?("#{k}=")
85
- model.send :write_attribute, k.to_sym, v
85
+ model[k.to_sym] = v
86
86
  else
87
87
  logger.warn "Attribute #{k} does not exist on #{item_type} (Version id: #{id})."
88
88
  end
@@ -102,15 +102,13 @@ class Version < ActiveRecord::Base
102
102
  # Returns what changed in this version of the item. Cf. `ActiveModel::Dirty#changes`.
103
103
  # Returns nil if your `versions` table does not have an `object_changes` text column.
104
104
  def changeset
105
- if self.class.column_names.include? 'object_changes'
106
- if changes = object_changes
107
- HashWithIndifferentAccess[PaperTrail.serializer.load(changes)].tap do |changes|
108
- item_type.constantize.unserialize_attribute_changes(changes)
109
- end
110
- else
111
- {}
112
- end
105
+ return nil unless self.class.column_names.include? 'object_changes'
106
+
107
+ HashWithIndifferentAccess.new(PaperTrail.serializer.load(object_changes)).tap do |changes|
108
+ item_type.constantize.unserialize_attribute_changes(changes)
113
109
  end
110
+ rescue
111
+ {}
114
112
  end
115
113
 
116
114
  # Returns who put the item into the state stored in this version.
@@ -1,3 +1,3 @@
1
1
  module PaperTrail
2
- VERSION = '2.7.0'
2
+ VERSION = '2.7.1'
3
3
  end
data/paper_trail.gemspec CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
19
19
  s.add_dependency 'activerecord', '~> 3.0'
20
20
 
21
21
  s.add_development_dependency 'rake'
22
- s.add_development_dependency 'shoulda', '~> 3.3'
23
- s.add_development_dependency 'sqlite3', '~> 1.2'
24
- s.add_development_dependency 'capybara', '~> 2.0'
22
+ s.add_development_dependency 'shoulda', '~> 3.3'
23
+ s.add_development_dependency 'sqlite3', '~> 1.2'
24
+ s.add_development_dependency 'ffaker', '>= 1.15'
25
25
  end
@@ -0,0 +1,3 @@
1
+ class ProtectedWidget < Widget
2
+ attr_accessible :name, :a_text
3
+ end
@@ -5,7 +5,7 @@ require "active_record/railtie"
5
5
  require "action_controller/railtie"
6
6
  require "action_view/railtie"
7
7
 
8
- Bundler.require
8
+ Bundler.require(:default, Rails.env) if defined?(Bundler)
9
9
  require 'paper_trail'
10
10
 
11
11
  module Dummy
@@ -32,13 +32,33 @@ module Dummy
32
32
  # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
33
33
  # config.i18n.default_locale = :de
34
34
 
35
- # JavaScript files you want as :defaults (application.js is always included).
36
- # config.action_view.javascript_expansions[:defaults] = %w(jquery rails)
37
-
38
35
  # Configure the default encoding used in templates for Ruby 1.9.
39
36
  config.encoding = "utf-8"
40
37
 
41
38
  # Configure sensitive parameters which will be filtered from the log file.
42
39
  config.filter_parameters += [:password]
40
+
41
+ # Enable escaping HTML in JSON.
42
+ config.active_support.escape_html_entities_in_json = true
43
+
44
+ # Use SQL instead of Active Record's schema dumper when creating the database.
45
+ # This is necessary if your schema can't be completely dumped by the schema dumper,
46
+ # like if you have constraints or database-specific column types
47
+ # config.active_record.schema_format = :sql
48
+
49
+ # Enforce whitelist mode for mass assignment.
50
+ # This will create an empty whitelist of attributes available for mass-assignment for all models
51
+ # in your app. As such, your models will need to explicitly whitelist or blacklist accessible
52
+ # parameters by using an attr_accessible or attr_protected declaration.
53
+ #
54
+ # This is uncommented in new rails apps by default but for our testing purposes its more convenient
55
+ # to leave it commented out.
56
+ # config.active_record.whitelist_attributes = true
57
+
58
+ # Enable the asset pipeline
59
+ config.assets.enabled = false
60
+
61
+ # Version of your assets, change this if you want to expire all your assets
62
+ # config.assets.version = '1.0'
43
63
  end
44
64
  end
@@ -2,8 +2,8 @@ Dummy::Application.configure do
2
2
  # Settings specified here will take precedence over those in config/application.rb
3
3
 
4
4
  # In the development environment your application's code is reloaded on
5
- # every request. This slows down response time but is perfect for development
6
- # since you don't have to restart the webserver when you make code changes.
5
+ # every request. This slows down response time but is perfect for development
6
+ # since you don't have to restart the web server when you make code changes.
7
7
  config.cache_classes = false
8
8
 
9
9
  # Log error messages when you accidentally call methods on nil.
@@ -11,16 +11,28 @@ Dummy::Application.configure do
11
11
 
12
12
  # Show full error reports and disable caching
13
13
  config.consider_all_requests_local = true
14
- config.action_view.debug_rjs = true
15
14
  config.action_controller.perform_caching = false
16
15
 
17
16
  # Don't care if the mailer can't send
18
- config.action_mailer.raise_delivery_errors = false
17
+ # config.action_mailer.raise_delivery_errors = false
19
18
 
20
19
  # Print deprecation notices to the Rails logger
21
20
  config.active_support.deprecation = :log
22
21
 
23
22
  # Only use best-standards-support built into browsers
24
23
  config.action_dispatch.best_standards_support = :builtin
24
+
25
+ # Raise exception on mass assignment protection for Active Record models
26
+ config.active_record.mass_assignment_sanitizer = :strict
27
+
28
+ # Log the query plan for queries taking more than this (works
29
+ # with SQLite, MySQL, and PostgreSQL)
30
+ config.active_record.auto_explain_threshold_in_seconds = 0.5
31
+
32
+ # Do not compress assets
33
+ config.assets.compress = false
34
+
35
+ # Expands the lines which load the assets
36
+ config.assets.debug = true
25
37
  end
26
38
 
@@ -1,7 +1,6 @@
1
1
  Dummy::Application.configure do
2
2
  # Settings specified here will take precedence over those in config/application.rb
3
3
 
4
- # The production environment is meant for finished, "live" apps.
5
4
  # Code is not reloaded between requests
6
5
  config.cache_classes = true
7
6
 
@@ -9,31 +8,46 @@ Dummy::Application.configure do
9
8
  config.consider_all_requests_local = false
10
9
  config.action_controller.perform_caching = true
11
10
 
12
- # Specifies the header that your server uses for sending files
13
- config.action_dispatch.x_sendfile_header = "X-Sendfile"
11
+ # Disable Rails's static asset server (Apache or nginx will already do this)
12
+ config.serve_static_assets = false
13
+
14
+ # Compress JavaScripts and CSS
15
+ config.assets.compress = true
16
+
17
+ # Don't fallback to assets pipeline if a precompiled asset is missed
18
+ config.assets.compile = false
19
+
20
+ # Generate digests for assets URLs
21
+ config.assets.digest = true
22
+
23
+ # Defaults to nil and saved in location specified by config.assets.prefix
24
+ # config.assets.manifest = YOUR_PATH
14
25
 
15
- # For nginx:
16
- # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'
26
+ # Specifies the header that your server uses for sending files
27
+ # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
28
+ # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
17
29
 
18
- # If you have no front-end server that supports something like X-Sendfile,
19
- # just comment this out and Rails will serve the files
30
+ # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
31
+ # config.force_ssl = true
20
32
 
21
33
  # See everything in the log (default is :info)
22
34
  # config.log_level = :debug
23
35
 
36
+ # Prepend all log lines with the following tags
37
+ # config.log_tags = [ :subdomain, :uuid ]
38
+
24
39
  # Use a different logger for distributed setups
25
- # config.logger = SyslogLogger.new
40
+ # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
26
41
 
27
42
  # Use a different cache store in production
28
43
  # config.cache_store = :mem_cache_store
29
44
 
30
- # Disable Rails's static asset server
31
- # In production, Apache or nginx will already do this
32
- config.serve_static_assets = false
33
-
34
- # Enable serving of images, stylesheets, and javascripts from an asset server
45
+ # Enable serving of images, stylesheets, and JavaScripts from an asset server
35
46
  # config.action_controller.asset_host = "http://assets.example.com"
36
47
 
48
+ # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
49
+ # config.assets.precompile += %w( search.js )
50
+
37
51
  # Disable delivery errors, bad email addresses will be ignored
38
52
  # config.action_mailer.raise_delivery_errors = false
39
53
 
@@ -46,4 +60,8 @@ Dummy::Application.configure do
46
60
 
47
61
  # Send deprecation notices to registered listeners
48
62
  config.active_support.deprecation = :notify
63
+
64
+ # Log the query plan for queries taking more than this (works
65
+ # with SQLite, MySQL, and PostgreSQL)
66
+ # config.active_record.auto_explain_threshold_in_seconds = 0.5
49
67
  end
@@ -2,12 +2,16 @@ Dummy::Application.configure do
2
2
  # Settings specified here will take precedence over those in config/application.rb
3
3
 
4
4
  # The test environment is used exclusively to run your application's
5
- # test suite. You never need to work with it otherwise. Remember that
5
+ # test suite. You never need to work with it otherwise. Remember that
6
6
  # your test database is "scratch space" for the test suite and is wiped
7
- # and recreated between test runs. Don't rely on the data there!
7
+ # and recreated between test runs. Don't rely on the data there!
8
8
  config.cache_classes = true
9
9
 
10
- # Log error messages when you accidentally call methods on nil.
10
+ # Configure static asset server for tests with Cache-Control for performance
11
+ config.serve_static_assets = true
12
+ config.static_cache_control = "public, max-age=3600"
13
+
14
+ # Log error messages when you accidentally call methods on nil
11
15
  config.whiny_nils = true
12
16
 
13
17
  # Show full error reports and disable caching
@@ -18,17 +22,15 @@ Dummy::Application.configure do
18
22
  config.action_dispatch.show_exceptions = false
19
23
 
20
24
  # Disable request forgery protection in test environment
21
- config.action_controller.allow_forgery_protection = false
25
+ config.action_controller.allow_forgery_protection = false
22
26
 
23
27
  # Tell Action Mailer not to deliver emails to the real world.
24
28
  # The :test delivery method accumulates sent emails in the
25
29
  # ActionMailer::Base.deliveries array.
26
- config.action_mailer.delivery_method = :test if config.respond_to?(:action_mailer)
30
+ # config.action_mailer.delivery_method = :test
27
31
 
28
- # Use SQL instead of Active Record's schema dumper when creating the test database.
29
- # This is necessary if your schema can't be completely dumped by the schema dumper,
30
- # like if you have constraints or database-specific column types
31
- # config.active_record.schema_format = :sql
32
+ # Raise exception on mass assignment protection for Active Record models
33
+ config.active_record.mass_assignment_sanitizer = :strict
32
34
 
33
35
  # Print deprecation notices to the stderr
34
36
  config.active_support.deprecation = :stderr
@@ -0,0 +1,3 @@
1
+ class Version < ActiveRecord::Base
2
+ attr_accessible :created_at, :updated_at, :answer, :action, :question, :article_id, :ip, :user_agent, :title
3
+ end
data/test/test_helper.rb CHANGED
@@ -11,11 +11,7 @@ require "rails/test_help"
11
11
  Rails.backtrace_cleaner.remove_silencers!
12
12
 
13
13
  require 'shoulda'
14
-
15
- # Configure capybara for integration testing
16
- require "capybara/rails"
17
- Capybara.default_driver = :rack_test
18
- Capybara.default_selector = :css
14
+ require 'ffaker'
19
15
 
20
16
  # Run any available migration
21
17
  ActiveRecord::Migrator.migrate File.expand_path("../dummy/db/migrate/", __FILE__)
@@ -42,8 +38,3 @@ def change_schema
42
38
  end
43
39
  ActiveRecord::Migration.verbose = true
44
40
  end
45
-
46
- class Version < ActiveRecord::Base
47
- attr_accessible :created_at, :updated_at,
48
- :answer, :action, :question, :article_id, :ip, :user_agent, :title
49
- end
@@ -4,6 +4,7 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
4
4
 
5
5
  context 'A record with defined "only" and "ignore" attributes' do
6
6
  setup { @article = Article.create }
7
+ should 'creation should change the number of versions' do assert_equal(1, Version.count) end
7
8
 
8
9
  context 'which updates an ignored column' do
9
10
  setup { @article.update_attributes :title => 'My first title' }
@@ -14,6 +15,10 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
14
15
  setup { @article.update_attributes :title => 'My first title', :content => 'Some text here.' }
15
16
  should 'change the number of versions' do assert_equal(2, Version.count) end
16
17
 
18
+ should "show the new version in the model's `versions` association" do
19
+ assert_equal(2, @article.versions.size)
20
+ end
21
+
17
22
  should 'have stored only non-ignored attributes' do
18
23
  assert_equal ({'content' => [nil, 'Some text here.']}), @article.versions.last.changeset
19
24
  end
@@ -22,6 +27,10 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
22
27
  context 'which updates a selected column' do
23
28
  setup { @article.update_attributes :content => 'Some text here.' }
24
29
  should 'change the number of versions' do assert_equal(2, Version.count) end
30
+
31
+ should "show the new version in the model's `versions` association" do
32
+ assert_equal(2, @article.versions.size)
33
+ end
25
34
  end
26
35
 
27
36
  context 'which updates a non-ignored and non-selected column' do
@@ -38,6 +47,10 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
38
47
  setup { @article.update_attributes :file_upload => 'Your data goes here', :content => 'Some text here.' }
39
48
  should 'change the number of versions' do assert_equal(2, Version.count) end
40
49
 
50
+ should "show the new version in the model's `versions` association" do
51
+ assert_equal(2, @article.versions.size)
52
+ end
53
+
41
54
  should 'have stored only non-skipped attributes' do
42
55
  assert_equal ({'content' => [nil, 'Some text here.']}), @article.versions.last.changeset
43
56
  end
@@ -57,6 +70,15 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
57
70
  end
58
71
  end
59
72
  end
73
+
74
+ context 'which gets destroyed' do
75
+ setup { @article.destroy }
76
+ should 'change the number of versions' do assert_equal(2, Version.count) end
77
+
78
+ should "show the new version in the model's `versions` association" do
79
+ assert_equal(2, @article.versions.size)
80
+ end
81
+ end
60
82
  end
61
83
 
62
84
  context 'A record with defined "ignore" attribute' do
@@ -111,11 +133,19 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
111
133
  context 'after update' do
112
134
  setup { @translation.update_attributes :content => 'Content' }
113
135
  should 'change the number of versions' do assert_equal(2, Version.count) end
136
+
137
+ should "show the new version in the model's `versions` association" do
138
+ assert_equal(2, @translation.versions.size)
139
+ end
114
140
  end
115
141
 
116
142
  context 'after destroy' do
117
143
  setup { @translation.destroy }
118
144
  should 'change the number of versions' do assert_equal(2, Version.count) end
145
+
146
+ should "show the new version in the model's `versions` association" do
147
+ assert_equal(2, @translation.versions.size)
148
+ end
119
149
  end
120
150
  end
121
151
  end
@@ -765,10 +795,10 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
765
795
  end
766
796
 
767
797
  should 'have a previous version' do
768
- assert_equal @widget.versions.last.reify, @widget.previous_version
798
+ assert_equal @widget.versions.last.reify.name, @widget.previous_version.name
769
799
  end
770
800
 
771
- should 'have a next version' do
801
+ should 'not have a next version' do
772
802
  assert_nil @widget.next_version
773
803
  end
774
804
  end
@@ -776,21 +806,20 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
776
806
 
777
807
  context 'A reified item' do
778
808
  setup do
779
- widget = Widget.create :name => 'Bob'
780
- %w( Tom Dick Jane ).each { |name| widget.update_attributes :name => name }
781
- @versions = widget.versions
782
- @second_widget = @versions[1].reify # first widget is null
783
- @last_widget = @versions.last.reify
809
+ @widget = Widget.create :name => 'Bob'
810
+ %w(Tom Dick Jane).each { |name| @widget.update_attributes :name => name }
811
+ @second_widget = @widget.versions[1].reify # first widget is `nil`
812
+ @last_widget = @widget.versions.last.reify
784
813
  end
785
814
 
786
815
  should 'have a previous version' do
787
- assert_nil @second_widget.previous_version
788
- assert_equal @versions[-2].reify, @last_widget.previous_version
816
+ assert_nil @second_widget.previous_version # `create` events return `nil` for `reify`
817
+ assert_equal @widget.versions[-2].reify.name, @last_widget.previous_version.name
789
818
  end
790
819
 
791
820
  should 'have a next version' do
792
- assert_equal @versions[2].reify, @second_widget.next_version
793
- assert_nil @last_widget.next_version
821
+ assert_equal @widget.versions[2].reify.name, @second_widget.next_version.name
822
+ assert_equal @last_widget.next_version.name, @widget.name
794
823
  end
795
824
  end
796
825
 
@@ -1076,7 +1105,11 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
1076
1105
  assert_equal 2, @doc.paper_trail_versions.length
1077
1106
  end
1078
1107
 
1079
- should 'respond to previous_version as normal' do
1108
+ should 'respond to `next_version` as normal' do
1109
+ assert_equal @doc.paper_trail_versions.last.reify.next_version.name, @doc.name
1110
+ end
1111
+
1112
+ should 'respond to `previous_version` as normal' do
1080
1113
  @doc.update_attributes :name => 'Doc 2'
1081
1114
  assert_equal 3, @doc.paper_trail_versions.length
1082
1115
  assert_equal 'Doc 1', @doc.previous_version.name
@@ -0,0 +1,41 @@
1
+ require 'test_helper'
2
+
3
+ class ProtectedAttrsTest < ActiveSupport::TestCase
4
+ subject { ProtectedWidget.new }
5
+
6
+ accessible_attrs = ProtectedWidget.accessible_attributes.to_a
7
+ accessible_attrs.each do |attr_name|
8
+ should allow_mass_assignment_of(attr_name.to_sym)
9
+ end
10
+ ProtectedWidget.column_names.reject { |column_name| accessible_attrs.include?(column_name) }.each do |attr_name|
11
+ should_not allow_mass_assignment_of(attr_name.to_sym)
12
+ end
13
+
14
+ context 'A model with `attr_accessible` created' do
15
+ setup do
16
+ @widget = ProtectedWidget.create! :name => 'Henry'
17
+ @initial_attributes = @widget.attributes
18
+ end
19
+
20
+ should 'be `nil` in its previous version' do
21
+ assert_nil @widget.previous_version
22
+ end
23
+
24
+ context 'which is then updated' do
25
+ setup do
26
+ @widget.assign_attributes(:name => 'Jeff', :a_text => 'Short statement')
27
+ @widget.an_integer = 42
28
+ @widget.save!
29
+ end
30
+
31
+ should 'not be `nil` in its previous version' do
32
+ assert_not_nil @widget.previous_version
33
+ end
34
+
35
+ should 'the previous version should contain right attributes' do
36
+ assert_equal @widget.previous_version.attributes, @initial_attributes
37
+ end
38
+ end
39
+
40
+ end
41
+ end
@@ -1,16 +1,5 @@
1
1
  require 'test_helper'
2
2
 
3
- class CustomSerializer
4
- require 'json'
5
- def self.dump(object_hash)
6
- JSON.dump object_hash
7
- end
8
-
9
- def self.load(string)
10
- JSON.parse string
11
- end
12
- end
13
-
14
3
  class SerializerTest < ActiveSupport::TestCase
15
4
 
16
5
  context 'YAML Serializer' do
@@ -20,6 +9,7 @@ class SerializerTest < ActiveSupport::TestCase
20
9
  END
21
10
 
22
11
  @fluxor = Fluxor.create :name => 'Some text.'
12
+ @original_fluxor_attributes = @fluxor.send(:item_before_change).attributes # this is exactly what PaperTrail serializes
23
13
  @fluxor.update_attributes :name => 'Some more text.'
24
14
  end
25
15
 
@@ -29,24 +19,28 @@ class SerializerTest < ActiveSupport::TestCase
29
19
  assert_nil @fluxor.versions[0].reify
30
20
  assert_equal 'Some text.', @fluxor.versions[1].reify.name
31
21
 
32
-
33
22
  # Check values are stored as YAML.
34
- hash = {"widget_id" => nil,"name" =>"Some text.","id" =>1}
35
- assert_equal YAML.dump(hash), @fluxor.versions[1].object
36
- assert_equal hash, YAML.load(@fluxor.versions[1].object)
37
-
23
+ assert_equal @original_fluxor_attributes, YAML.load(@fluxor.versions[1].object)
24
+ # This test can't consistently pass in Ruby1.8 because hashes do no preserve order, which means the order of the
25
+ # attributes in the YAML can't be ensured.
26
+ if RUBY_VERSION.to_f >= 1.9
27
+ assert_equal YAML.dump(@original_fluxor_attributes), @fluxor.versions[1].object
28
+ end
38
29
  end
39
30
  end
40
31
 
41
32
  context 'Custom Serializer' do
42
33
  setup do
43
- PaperTrail.config.serializer = CustomSerializer
34
+ PaperTrail.configure do |config|
35
+ config.serializer = PaperTrail::Serializers::Json
36
+ end
44
37
 
45
38
  Fluxor.instance_eval <<-END
46
39
  has_paper_trail
47
40
  END
48
41
 
49
42
  @fluxor = Fluxor.create :name => 'Some text.'
43
+ @original_fluxor_attributes = @fluxor.send(:item_before_change).attributes # this is exactly what PaperTrail serializes
50
44
  @fluxor.update_attributes :name => 'Some more text.'
51
45
  end
52
46
 
@@ -54,17 +48,26 @@ class SerializerTest < ActiveSupport::TestCase
54
48
  PaperTrail.config.serializer = PaperTrail::Serializers::Yaml
55
49
  end
56
50
 
57
- should 'work with custom serializer' do
51
+ should 'reify with custom serializer' do
58
52
  # Normal behaviour
59
53
  assert_equal 2, @fluxor.versions.length
60
54
  assert_nil @fluxor.versions[0].reify
61
55
  assert_equal 'Some text.', @fluxor.versions[1].reify.name
62
56
 
63
57
  # Check values are stored as JSON.
64
- hash = {"widget_id" => nil,"name" =>"Some text.","id" =>1}
65
- assert_equal JSON.dump(hash), @fluxor.versions[1].object
66
- assert_equal hash, JSON.parse(@fluxor.versions[1].object)
58
+ assert_equal @original_fluxor_attributes, ActiveSupport::JSON.decode(@fluxor.versions[1].object)
59
+ # This test can't consistently pass in Ruby1.8 because hashes do no preserve order, which means the order of the
60
+ # attributes in the JSON can't be ensured.
61
+ if RUBY_VERSION.to_f >= 1.9
62
+ assert_equal ActiveSupport::JSON.encode(@original_fluxor_attributes), @fluxor.versions[1].object
63
+ end
64
+ end
67
65
 
66
+ should 'store object_changes' do
67
+ initial_changeset = {"name" => [nil, "Some text."], "id" => [nil, 1]}
68
+ second_changeset = {"name"=>["Some text.", "Some more text."]}
69
+ assert_equal initial_changeset, @fluxor.versions[0].changeset
70
+ assert_equal second_changeset, @fluxor.versions[1].changeset
68
71
  end
69
72
  end
70
73
 
@@ -0,0 +1,40 @@
1
+ require 'test_helper'
2
+
3
+ class JsonTest < ActiveSupport::TestCase
4
+
5
+ setup do
6
+ # Setup a hash with random values
7
+ @hash = {}
8
+ (1..4).each do |i|
9
+ @hash["key#{i}"] = Faker::Lorem.word
10
+ end
11
+ @hash_as_json = @hash.to_json
12
+ # Setup an array of random words
13
+ @array = []
14
+ (rand(5) + 4).times { @array << Faker::Lorem.word }
15
+ @array_as_json = @array.to_json
16
+ end
17
+
18
+ context '`load` class method' do
19
+ should 'exist' do
20
+ assert PaperTrail::Serializers::Json.respond_to?(:load)
21
+ end
22
+
23
+ should '`deserialize` JSON to Ruby' do
24
+ assert_equal @hash, PaperTrail::Serializers::Json.load(@hash_as_json)
25
+ assert_equal @array, PaperTrail::Serializers::Json.load(@array_as_json)
26
+ end
27
+ end
28
+
29
+ context '`dump` class method' do
30
+ should 'exist' do
31
+ assert PaperTrail::Serializers::Json.respond_to?(:dump)
32
+ end
33
+
34
+ should '`serialize` Ruby to JSON' do
35
+ assert_equal @hash_as_json, PaperTrail::Serializers::Json.dump(@hash)
36
+ assert_equal @array_as_json, PaperTrail::Serializers::Json.dump(@array)
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,48 @@
1
+ require 'test_helper'
2
+
3
+ module CustomJsonSerializer
4
+ extend PaperTrail::Serializers::Json
5
+
6
+ def self.load(string)
7
+ parsed_value = super(string)
8
+ parsed_value.is_a?(Hash) ? parsed_value.reject { |k,v| k.blank? || v.blank? } : parsed_value
9
+ end
10
+
11
+ def self.dump(object)
12
+ object.is_a?(Hash) ? super(object.reject { |k,v| v.nil? }) : super
13
+ end
14
+ end
15
+
16
+ class MixinJsonTest < ActiveSupport::TestCase
17
+
18
+ setup do
19
+ # Setup a hash with random values, ensuring some values are nil
20
+ @hash = {}
21
+ (1..4).each do |i|
22
+ @hash["key#{i}"] = [Faker::Lorem.word, nil].sample
23
+ end
24
+ @hash['tkey'] = nil
25
+ @hash[''] = 'foo'
26
+ @hash_as_json = @hash.to_json
27
+ end
28
+
29
+ context '`load` class method' do
30
+ should 'exist' do
31
+ assert CustomJsonSerializer.respond_to?(:load)
32
+ end
33
+
34
+ should '`deserialize` JSON to Ruby, removing pairs with `blank` keys or values' do
35
+ assert_equal @hash.reject { |k,v| k.blank? || v.blank? }, CustomJsonSerializer.load(@hash_as_json)
36
+ end
37
+ end
38
+
39
+ context '`dump` class method' do
40
+ should 'exist' do
41
+ assert CustomJsonSerializer.respond_to?(:dump)
42
+ end
43
+
44
+ should '`serialize` Ruby to JSON, removing pairs with `nil` values' do
45
+ assert_equal @hash.reject { |k,v| v.nil? }.to_json, CustomJsonSerializer.dump(@hash)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,49 @@
1
+ require 'test_helper'
2
+
3
+ module CustomYamlSerializer
4
+ extend PaperTrail::Serializers::Yaml
5
+
6
+ def self.load(string)
7
+ parsed_value = super(string)
8
+ parsed_value.is_a?(Hash) ? parsed_value.reject { |k,v| k.blank? || v.blank? } : parsed_value
9
+ end
10
+
11
+ def self.dump(object)
12
+ object.is_a?(Hash) ? super(object.reject { |k,v| v.nil? }) : super
13
+ end
14
+ end
15
+
16
+ class MixinYamlTest < ActiveSupport::TestCase
17
+
18
+ setup do
19
+ # Setup a hash with random values, ensuring some values are nil
20
+ @hash = {}
21
+ (1..4).each do |i|
22
+ @hash["key#{i}"] = [Faker::Lorem.word, nil].sample
23
+ end
24
+ @hash['tkey'] = nil
25
+ @hash[''] = 'foo'
26
+ @hash_as_yaml = @hash.to_yaml
27
+ end
28
+
29
+ context '`load` class method' do
30
+ should 'exist' do
31
+ assert CustomYamlSerializer.respond_to?(:load)
32
+ end
33
+
34
+ should '`deserialize` YAML to Ruby, removing pairs with `blank` keys or values' do
35
+ assert_equal @hash.reject { |k,v| k.blank? || v.blank? }, CustomYamlSerializer.load(@hash_as_yaml)
36
+ end
37
+ end
38
+
39
+ context '`dump` class method' do
40
+ should 'exist' do
41
+ assert CustomYamlSerializer.respond_to?(:dump)
42
+ end
43
+
44
+ should '`serialize` Ruby to YAML, removing pairs with `nil` values' do
45
+ assert_equal @hash.reject { |k,v| v.nil? }.to_yaml, CustomYamlSerializer.dump(@hash)
46
+ end
47
+ end
48
+
49
+ end
@@ -0,0 +1,40 @@
1
+ require 'test_helper'
2
+
3
+ class YamlTest < ActiveSupport::TestCase
4
+
5
+ setup do
6
+ # Setup a hash with random values
7
+ @hash = {}
8
+ (1..4).each do |i|
9
+ @hash["key#{i}".to_sym] = Faker::Lorem.word
10
+ end
11
+ @hash_as_yaml = @hash.to_yaml
12
+ # Setup an array of random words
13
+ @array = []
14
+ (rand(5) + 4).times { @array << Faker::Lorem.word }
15
+ @array_as_yaml = @array.to_yaml
16
+ end
17
+
18
+ context '`load` class method' do
19
+ should 'exist' do
20
+ assert PaperTrail::Serializers::Yaml.respond_to?(:load)
21
+ end
22
+
23
+ should '`deserialize` YAML to Ruby' do
24
+ assert_equal @hash, PaperTrail::Serializers::Yaml.load(@hash_as_yaml)
25
+ assert_equal @array, PaperTrail::Serializers::Yaml.load(@array_as_yaml)
26
+ end
27
+ end
28
+
29
+ context '`dump` class method' do
30
+ should 'exist' do
31
+ assert PaperTrail::Serializers::Yaml.respond_to?(:dump)
32
+ end
33
+
34
+ should '`serialize` Ruby to YAML' do
35
+ assert_equal @hash_as_yaml, PaperTrail::Serializers::Yaml.dump(@hash)
36
+ assert_equal @array_as_yaml, PaperTrail::Serializers::Yaml.dump(@array)
37
+ end
38
+ end
39
+
40
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paper_trail
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.0
4
+ version: 2.7.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-28 00:00:00.000000000 Z
12
+ date: 2013-02-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: railties
@@ -92,21 +92,21 @@ dependencies:
92
92
  - !ruby/object:Gem::Version
93
93
  version: '1.2'
94
94
  - !ruby/object:Gem::Dependency
95
- name: capybara
95
+ name: ffaker
96
96
  requirement: !ruby/object:Gem::Requirement
97
97
  none: false
98
98
  requirements:
99
- - - ~>
99
+ - - ! '>='
100
100
  - !ruby/object:Gem::Version
101
- version: '2.0'
101
+ version: '1.15'
102
102
  type: :development
103
103
  prerelease: false
104
104
  version_requirements: !ruby/object:Gem::Requirement
105
105
  none: false
106
106
  requirements:
107
- - - ~>
107
+ - - ! '>='
108
108
  - !ruby/object:Gem::Version
109
- version: '2.0'
109
+ version: '1.15'
110
110
  description: Track changes to your models' data. Good for auditing or versioning.
111
111
  email: boss@airbladesoftware.com
112
112
  executables: []
@@ -128,6 +128,7 @@ files:
128
128
  - lib/paper_trail/config.rb
129
129
  - lib/paper_trail/controller.rb
130
130
  - lib/paper_trail/has_paper_trail.rb
131
+ - lib/paper_trail/serializers/json.rb
131
132
  - lib/paper_trail/serializers/yaml.rb
132
133
  - lib/paper_trail/version.rb
133
134
  - lib/paper_trail/version_number.rb
@@ -150,6 +151,7 @@ files:
150
151
  - test/dummy/app/models/legacy_widget.rb
151
152
  - test/dummy/app/models/person.rb
152
153
  - test/dummy/app/models/post.rb
154
+ - test/dummy/app/models/protected_widget.rb
153
155
  - test/dummy/app/models/song.rb
154
156
  - test/dummy/app/models/translation.rb
155
157
  - test/dummy/app/models/widget.rb
@@ -167,6 +169,7 @@ files:
167
169
  - test/dummy/config/initializers/backtrace_silencers.rb
168
170
  - test/dummy/config/initializers/inflections.rb
169
171
  - test/dummy/config/initializers/mime_types.rb
172
+ - test/dummy/config/initializers/paper_trail.rb
170
173
  - test/dummy/config/initializers/secret_token.rb
171
174
  - test/dummy/config/initializers/session_store.rb
172
175
  - test/dummy/config/locales/en.yml
@@ -187,13 +190,16 @@ files:
187
190
  - test/dummy/script/rails
188
191
  - test/functional/controller_test.rb
189
192
  - test/functional/thread_safety_test.rb
190
- - test/integration/navigation_test.rb
191
193
  - test/paper_trail_test.rb
192
- - test/support/integration_case.rb
193
194
  - test/test_helper.rb
194
195
  - test/unit/inheritance_column_test.rb
195
196
  - test/unit/model_test.rb
197
+ - test/unit/protected_attrs_test.rb
196
198
  - test/unit/serializer_test.rb
199
+ - test/unit/serializers/json_test.rb
200
+ - test/unit/serializers/mixin_json_test.rb
201
+ - test/unit/serializers/mixin_yaml_test.rb
202
+ - test/unit/serializers/yaml_test.rb
197
203
  - test/unit/timestamp_test.rb
198
204
  - test/unit/version_test.rb
199
205
  homepage: http://github.com/airblade/paper_trail
@@ -239,6 +245,7 @@ test_files:
239
245
  - test/dummy/app/models/legacy_widget.rb
240
246
  - test/dummy/app/models/person.rb
241
247
  - test/dummy/app/models/post.rb
248
+ - test/dummy/app/models/protected_widget.rb
242
249
  - test/dummy/app/models/song.rb
243
250
  - test/dummy/app/models/translation.rb
244
251
  - test/dummy/app/models/widget.rb
@@ -256,6 +263,7 @@ test_files:
256
263
  - test/dummy/config/initializers/backtrace_silencers.rb
257
264
  - test/dummy/config/initializers/inflections.rb
258
265
  - test/dummy/config/initializers/mime_types.rb
266
+ - test/dummy/config/initializers/paper_trail.rb
259
267
  - test/dummy/config/initializers/secret_token.rb
260
268
  - test/dummy/config/initializers/session_store.rb
261
269
  - test/dummy/config/locales/en.yml
@@ -276,12 +284,15 @@ test_files:
276
284
  - test/dummy/script/rails
277
285
  - test/functional/controller_test.rb
278
286
  - test/functional/thread_safety_test.rb
279
- - test/integration/navigation_test.rb
280
287
  - test/paper_trail_test.rb
281
- - test/support/integration_case.rb
282
288
  - test/test_helper.rb
283
289
  - test/unit/inheritance_column_test.rb
284
290
  - test/unit/model_test.rb
291
+ - test/unit/protected_attrs_test.rb
285
292
  - test/unit/serializer_test.rb
293
+ - test/unit/serializers/json_test.rb
294
+ - test/unit/serializers/mixin_json_test.rb
295
+ - test/unit/serializers/mixin_yaml_test.rb
296
+ - test/unit/serializers/yaml_test.rb
286
297
  - test/unit/timestamp_test.rb
287
298
  - test/unit/version_test.rb
@@ -1,7 +0,0 @@
1
- require 'test_helper'
2
-
3
- class NavigationTest < ActiveSupport::IntegrationCase
4
- test 'Sanity test' do
5
- assert_kind_of Dummy::Application, Rails.application
6
- end
7
- end
@@ -1,5 +0,0 @@
1
- # Define a bare test case to use with Capybara
2
- class ActiveSupport::IntegrationCase < ActiveSupport::TestCase
3
- include Capybara::DSL
4
- include Rails.application.routes.url_helpers
5
- end