paper_trail 2.7.0 → 2.7.1

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