paper_trail 3.0.2 → 3.0.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a4b0bcf1de1e90e178545aca16201e7f3bf5de2a
4
- data.tar.gz: 01b443dae88b5d51479bbca1e788b723f2c37c89
3
+ metadata.gz: 3ee3ec4b4dd8dfad13cbe5e9b46ccfa30f3318a3
4
+ data.tar.gz: 20559b46236a0318c4b7e2eb940bfbd79600cfad
5
5
  SHA512:
6
- metadata.gz: f4ca7aa698867e094708d43673e77a29a681674ab3ddceefda21df783167d7f5876e0ea067945978e58615b25fdd7229f27a1630100d881c9da99feb01ee873a
7
- data.tar.gz: f01d87966c55fe86b9894d015cf906ef833fe70e67187a920aa20676694ab5a1c64a79182917fbdb6b5a0cd8ea66d457f3d2370862f6ce764af0573530bbb112
6
+ metadata.gz: 6cf548b9641d5994bf67b3e886d4ed60fed0d6d16e9134e6374ca8ecaaa7ff3662fd4d97684e2bd1b59cffa710f1e19a58c0d4679433483dcd471fdc4ef201ad
7
+ data.tar.gz: 3ae713d8799f7ce5b95b5787134eeaf3aada506b1c71a89d3e25e3703c1fbc5a809f183e0a62c521c65f473312261e5049843d01a54166243ddac7a994120332
@@ -1,3 +1,26 @@
1
+ ## 3.0.5
2
+
3
+ - [#401](https://github.com/airblade/paper_trail/issues/401) / [#406](https://github.com/airblade/paper_trail/issues/406)
4
+ `PaperTrail::Version` class is not loaded via a `Rails::Engine`, even when the gem is used with in Rails. This feature has
5
+ will be re-introduced in version `3.1.0`.
6
+
7
+ ## 3.0.3
8
+ *This version was yanked from RubyGems and has been replaced by version `3.0.5`, which is identical but does not eager load
9
+ in the `PaperTrail::Version` class through a `Rails::Engine` when the gem is used on Rails since it was causing issues for some users.*
10
+
11
+ - [#386](https://github.com/airblade/paper_trail/issues/386) - Fix eager loading of `versions` association with custom class name
12
+ in `ActiveRecord` 4.1.
13
+ - [#384](https://github.com/airblade/paper_trail/issues/384) - Fix `VersionConcern#originator` instance method.
14
+ - [#383](https://github.com/airblade/paper_trail/pull/383) - Make gem compatible with `ActiveRecord::Enum` (available in `ActiveRecord` 4.1+).
15
+ - [#380](https://github.com/airblade/paper_trail/pull/380) / [#377](https://github.com/airblade/paper_trail/issues/377) -
16
+ Add `VersionConcern#where_object` instance method; acts as a helper for querying against the `object` column in versions table.
17
+ - [#373](https://github.com/airblade/paper_trail/pull/373) - Fix default sort order for the `versions` association in `ActiveRecord` 4.1.
18
+ - [#372](https://github.com/airblade/paper_trail/pull/372) - Use [Arel](https://github.com/rails/arel) for SQL construction.
19
+ - [#365](https://github.com/airblade/paper_trail/issues/365) - `VersionConcern#version_at` should return `nil` when receiving a timestamp
20
+ that occured after the object was destroyed.
21
+ - Expand `PaperTrail::VERSION` into a module, mimicking the form used by Rails to give it some additional modularity & versatility.
22
+ - Fixed `VersionConcern#index` instance method so that it conforms to using the primary key for ordering when possible.
23
+
1
24
  ## 3.0.2
2
25
 
3
26
  - [#357](https://github.com/airblade/paper_trail/issues/357) - If a `Version` instance is reified and then persisted at that state,
data/README.md CHANGED
@@ -40,9 +40,9 @@ The Rails 2.3 code is on the [`rails2`](https://github.com/airblade/paper_trail/
40
40
 
41
41
  ### Rails 3 & 4
42
42
 
43
- 1. Add `PaperTrail` to your `Gemfile`.
43
+ 1. Add PaperTrail to your `Gemfile`.
44
44
 
45
- `gem 'paper_trail', '~> 3.0.2'`
45
+ `gem 'paper_trail', '~> 3.0.5'`
46
46
 
47
47
  2. Generate a migration which will add a `versions` table to your database.
48
48
 
@@ -56,15 +56,15 @@ The Rails 2.3 code is on the [`rails2`](https://github.com/airblade/paper_trail/
56
56
 
57
57
  ### Sinatra
58
58
 
59
- In order to configure `PaperTrail` for usage with [Sinatra](http://www.sinatrarb.com),
60
- your `Sinatra` app must be using `ActiveRecord` 3 or `ActiveRecord` 4. It is also recommended to use the
59
+ In order to configure PaperTrail for usage with [Sinatra](http://www.sinatrarb.com),
60
+ your `Sinatra` app must be using `ActiveRecord` 3 or 4. It is also recommended to use the
61
61
  [Sinatra ActiveRecord Extension](https://github.com/janko-m/sinatra-activerecord) or something similar for managing
62
62
  your applications `ActiveRecord` connection in a manner similar to the way `Rails` does. If using the aforementioned
63
- `Sinatra ActiveRecord Extension`, steps for setting up your app with `PaperTrail` will look something like this:
63
+ `Sinatra ActiveRecord Extension`, steps for setting up your app with PaperTrail will look something like this:
64
64
 
65
- 1. Add `PaperTrail` to your `Gemfile`.
65
+ 1. Add PaperTrail to your `Gemfile`.
66
66
 
67
- `gem 'paper_trail', '~> 3.0.2'`
67
+ `gem 'paper_trail', '~> 3.0.5'`
68
68
 
69
69
  2. Generate a migration to add a `versions` table to your database.
70
70
 
@@ -813,11 +813,14 @@ If you disable PaperTrail in your test environment but want to enable it for spe
813
813
  # in test/test_helper.rb
814
814
  def with_versioning
815
815
  was_enabled = PaperTrail.enabled?
816
+ was_enabled_for_controller = PaperTrail.enabled_for_controller?
816
817
  PaperTrail.enabled = true
818
+ PaperTrail.enabled_for_controller = true
817
819
  begin
818
820
  yield
819
821
  ensure
820
822
  PaperTrail.enabled = was_enabled
823
+ PaperTrail.enabled_for_controller = was_enabled_for_controller
821
824
  end
822
825
  end
823
826
  ```
@@ -1004,9 +1007,9 @@ Spork.prefork do
1004
1007
  end
1005
1008
  ```
1006
1009
 
1007
- ### Zeus
1010
+ ### Zeus or Spring
1008
1011
 
1009
- If you wish to use the `RSpec` or `Cucumber` heleprs with [Zeus](https://github.com/burke/zeus), you will need to
1012
+ If you wish to use the `RSpec` or `Cucumber` helpers with [Zeus](https://github.com/burke/zeus) or [Spring](https://github.com/rails/spring), you will need to
1010
1013
  manually require the helper(s) in your test helper, like so:
1011
1014
 
1012
1015
  ```ruby
@@ -1032,6 +1035,7 @@ export DB=sqlite # this is default
1032
1035
 
1033
1036
  ## Articles
1034
1037
 
1038
+ * [Versioning with PaperTrail](http://www.sitepoint.com/versioning-papertrail), [Ilya Bodrov](http://www.sitepoint.com/author/ibodrov), 10th April 2014
1035
1039
  * [Using PaperTrail to track stack traces](http://rubyrailsexpert.com/?p=36), T James Corcoran's blog, 1st October 2013.
1036
1040
  * [RailsCast #255 - Undo with PaperTrail](http://railscasts.com/episodes/255-undo-with-paper-trail), 28th February 2011.
1037
1041
  * [Keep a Paper Trail with PaperTrail](http://www.linux-mag.com/id/7528), Linux Magazine, 16th September 2009.
@@ -1046,6 +1050,7 @@ Please use GitHub's [issue tracker](http://github.com/airblade/paper_trail/issue
1046
1050
 
1047
1051
  Many thanks to:
1048
1052
 
1053
+ * [Dmitry Polushkin](https://github.com/dmitry)
1049
1054
  * [Russell Osborne](https://github.com/rposborne)
1050
1055
  * [Zachery Hostens](http://github.com/zacheryph)
1051
1056
  * [Jeremy Weiskotten](http://github.com/jeremyw)
@@ -19,7 +19,7 @@ group :development, :test do
19
19
  gem 'generator_spec'
20
20
 
21
21
  # To do proper transactional testing with ActiveSupport::TestCase on MySQL
22
- gem 'database_cleaner', '~> 1.2'
22
+ gem 'database_cleaner', '~> 1.2.0'
23
23
 
24
24
  platforms :ruby do
25
25
  gem 'sqlite3', '~> 1.2'
@@ -1,10 +1,12 @@
1
- require 'paper_trail/config'
2
- require 'paper_trail/has_paper_trail'
3
- require 'paper_trail/cleaner'
4
- require 'paper_trail/version_number'
1
+ # Require core library
2
+ Dir[File.join(File.dirname(__FILE__), 'paper_trail', '*.rb')].each do |file|
3
+ require File.join('paper_trail', File.basename(file, '.rb'))
4
+ end
5
5
 
6
6
  # Require serializers
7
- Dir[File.join(File.dirname(__FILE__), 'paper_trail', 'serializers', '*.rb')].each { |file| require file }
7
+ Dir[File.join(File.dirname(__FILE__), 'paper_trail', 'serializers', '*.rb')].each do |file|
8
+ require File.join('paper_trail', 'serializers', File.basename(file, '.rb'))
9
+ end
8
10
 
9
11
  module PaperTrail
10
12
  extend PaperTrail::Cleaner
@@ -118,21 +120,13 @@ unless PaperTrail.active_record_protected_attributes?
118
120
  rescue LoadError; end # will rescue if `ProtectedAttributes` gem is not available
119
121
  end
120
122
 
121
- require 'paper_trail/version'
122
-
123
123
  ActiveSupport.on_load(:active_record) do
124
124
  include PaperTrail::Model
125
125
  end
126
126
 
127
127
  # Require frameworks
128
+ require 'paper_trail/frameworks/active_record'
128
129
  require 'paper_trail/frameworks/sinatra'
129
- require 'paper_trail/frameworks/rspec' if defined? RSpec
130
+ require 'paper_trail/frameworks/rails' if defined? Rails
131
+ require 'paper_trail/frameworks/rspec' if defined? RSpec::Core
130
132
  require 'paper_trail/frameworks/cucumber' if defined? World
131
-
132
- if defined?(ActionController)
133
- require 'paper_trail/frameworks/rails'
134
-
135
- ActiveSupport.on_load(:action_controller) do
136
- include PaperTrail::Rails::Controller
137
- end
138
- end
@@ -0,0 +1,5 @@
1
+ # This file only needs to be loaded if the gem is being used outside of Rails, since otherwise
2
+ # the model(s) will get loaded in via the `Rails::Engine`
3
+ Dir[File.join(File.dirname(__FILE__), 'active_record', 'models', 'paper_trail', '*.rb')].each do |file|
4
+ require "paper_trail/frameworks/active_record/models/paper_trail/#{File.basename(file, '.rb')}"
5
+ end
@@ -1,4 +1,4 @@
1
- require File.expand_path('../version_concern', __FILE__)
1
+ require 'paper_trail/version_concern'
2
2
 
3
3
  module PaperTrail
4
4
  class Version < ::ActiveRecord::Base
@@ -1,74 +1,6 @@
1
+ require 'paper_trail/frameworks/rails/controller'
2
+
1
3
  module PaperTrail
2
4
  module Rails
3
- module Controller
4
-
5
- def self.included(base)
6
- base.before_filter :set_paper_trail_enabled_for_controller
7
- base.before_filter :set_paper_trail_whodunnit, :set_paper_trail_controller_info
8
- end
9
-
10
- protected
11
-
12
- # Returns the user who is responsible for any changes that occur.
13
- # By default this calls `current_user` and returns the result.
14
- #
15
- # Override this method in your controller to call a different
16
- # method, e.g. `current_person`, or anything you like.
17
- def user_for_paper_trail
18
- return unless defined?(current_user)
19
- ActiveSupport::VERSION::MAJOR >= 4 ? current_user.try!(:id) : current_user.try(:id)
20
- rescue NoMethodError
21
- current_user
22
- end
23
-
24
- # Returns any information about the controller or request that you
25
- # want PaperTrail to store alongside any changes that occur. By
26
- # default this returns an empty hash.
27
- #
28
- # Override this method in your controller to return a hash of any
29
- # information you need. The hash's keys must correspond to columns
30
- # in your `versions` table, so don't forget to add any new columns
31
- # you need.
32
- #
33
- # For example:
34
- #
35
- # {:ip => request.remote_ip, :user_agent => request.user_agent}
36
- #
37
- # The columns `ip` and `user_agent` must exist in your `versions` # table.
38
- #
39
- # Use the `:meta` option to `PaperTrail::Model::ClassMethods.has_paper_trail`
40
- # to store any extra model-level data you need.
41
- def info_for_paper_trail
42
- {}
43
- end
44
-
45
- # Returns `true` (default) or `false` depending on whether PaperTrail should
46
- # be active for the current request.
47
- #
48
- # Override this method in your controller to specify when PaperTrail should
49
- # be off.
50
- def paper_trail_enabled_for_controller
51
- ::PaperTrail.enabled?
52
- end
53
-
54
- private
55
-
56
- # Tells PaperTrail whether versions should be saved in the current request.
57
- def set_paper_trail_enabled_for_controller
58
- ::PaperTrail.enabled_for_controller = paper_trail_enabled_for_controller
59
- end
60
-
61
- # Tells PaperTrail who is responsible for any changes that occur.
62
- def set_paper_trail_whodunnit
63
- ::PaperTrail.whodunnit = user_for_paper_trail if ::PaperTrail.enabled_for_controller?
64
- end
65
-
66
- # Tells PaperTrail any information from the controller you want
67
- # to store alongside any changes that occur.
68
- def set_paper_trail_controller_info
69
- ::PaperTrail.controller_info = info_for_paper_trail if ::PaperTrail.enabled_for_controller?
70
- end
71
-
72
- end
73
5
  end
74
6
  end
@@ -0,0 +1,78 @@
1
+ module PaperTrail
2
+ module Rails
3
+ module Controller
4
+
5
+ def self.included(base)
6
+ base.before_filter :set_paper_trail_enabled_for_controller
7
+ base.before_filter :set_paper_trail_whodunnit, :set_paper_trail_controller_info
8
+ end
9
+
10
+ protected
11
+
12
+ # Returns the user who is responsible for any changes that occur.
13
+ # By default this calls `current_user` and returns the result.
14
+ #
15
+ # Override this method in your controller to call a different
16
+ # method, e.g. `current_person`, or anything you like.
17
+ def user_for_paper_trail
18
+ return unless defined?(current_user)
19
+ ActiveSupport::VERSION::MAJOR >= 4 ? current_user.try!(:id) : current_user.try(:id)
20
+ rescue NoMethodError
21
+ current_user
22
+ end
23
+
24
+ # Returns any information about the controller or request that you
25
+ # want PaperTrail to store alongside any changes that occur. By
26
+ # default this returns an empty hash.
27
+ #
28
+ # Override this method in your controller to return a hash of any
29
+ # information you need. The hash's keys must correspond to columns
30
+ # in your `versions` table, so don't forget to add any new columns
31
+ # you need.
32
+ #
33
+ # For example:
34
+ #
35
+ # {:ip => request.remote_ip, :user_agent => request.user_agent}
36
+ #
37
+ # The columns `ip` and `user_agent` must exist in your `versions` # table.
38
+ #
39
+ # Use the `:meta` option to `PaperTrail::Model::ClassMethods.has_paper_trail`
40
+ # to store any extra model-level data you need.
41
+ def info_for_paper_trail
42
+ {}
43
+ end
44
+
45
+ # Returns `true` (default) or `false` depending on whether PaperTrail should
46
+ # be active for the current request.
47
+ #
48
+ # Override this method in your controller to specify when PaperTrail should
49
+ # be off.
50
+ def paper_trail_enabled_for_controller
51
+ ::PaperTrail.enabled?
52
+ end
53
+
54
+ private
55
+
56
+ # Tells PaperTrail whether versions should be saved in the current request.
57
+ def set_paper_trail_enabled_for_controller
58
+ ::PaperTrail.enabled_for_controller = paper_trail_enabled_for_controller
59
+ end
60
+
61
+ # Tells PaperTrail who is responsible for any changes that occur.
62
+ def set_paper_trail_whodunnit
63
+ ::PaperTrail.whodunnit = user_for_paper_trail if ::PaperTrail.enabled_for_controller?
64
+ end
65
+
66
+ # Tells PaperTrail any information from the controller you want
67
+ # to store alongside any changes that occur.
68
+ def set_paper_trail_controller_info
69
+ ::PaperTrail.controller_info = info_for_paper_trail if ::PaperTrail.enabled_for_controller?
70
+ end
71
+
72
+ end
73
+ end
74
+
75
+ if defined?(::ActionController)
76
+ ::ActiveSupport.on_load(:action_controller) { include PaperTrail::Rails::Controller }
77
+ end
78
+ end
@@ -0,0 +1,7 @@
1
+ module PaperTrail
2
+ module Rails
3
+ class Engine < ::Rails::Engine
4
+ paths['app/models'] << 'lib/paper_trail/frameworks/active_record/models'
5
+ end
6
+ end
7
+ end
@@ -61,7 +61,7 @@ module PaperTrail
61
61
 
62
62
  if ::ActiveRecord::VERSION::MAJOR >= 4 # `has_many` syntax for specifying order uses a lambda in Rails 4
63
63
  has_many self.versions_association_name,
64
- lambda { |model| order(model.version_class_name.constantize.timestamp_sort_order) },
64
+ lambda { order(model.timestamp_sort_order) },
65
65
  :class_name => self.version_class_name, :as => :item
66
66
  else
67
67
  has_many self.versions_association_name,
@@ -174,7 +174,7 @@ module PaperTrail
174
174
 
175
175
  # Returns who put the object into its current state.
176
176
  def originator
177
- @originator ||= self.class.paper_trail_version_class.with_item_keys(self.class.base_class.name, id).last.try :whodunnit
177
+ (source_version || send(self.class.versions_association_name).last).try(:whodunnit)
178
178
  end
179
179
 
180
180
  # Returns the object (not a Version) as it was at the given timestamp.
@@ -182,7 +182,8 @@ module PaperTrail
182
182
  # Because a version stores how its object looked *before* the change,
183
183
  # we need to look for the first version created *after* the timestamp.
184
184
  v = send(self.class.versions_association_name).subsequent(timestamp, true).first
185
- v ? v.reify(reify_options) : self
185
+ return v.reify(reify_options) if v
186
+ self unless self.destroyed?
186
187
  end
187
188
 
188
189
  # Returns the objects (not Versions) as they were between the given times.
@@ -290,7 +291,7 @@ module PaperTrail
290
291
  end.tap { |changes| self.class.serialize_attribute_changes(changes) }
291
292
  end
292
293
 
293
- # Invoked via `after_update` callback for when a previous version is reified and then saved
294
+ # Invoked via`after_update` callback for when a previous version is reified and then saved
294
295
  def clear_version_instance!
295
296
  send("#{self.class.version_association_name}=", nil)
296
297
  end
@@ -342,9 +343,13 @@ module PaperTrail
342
343
  all_timestamp_attributes.each do |column|
343
344
  previous[column] = send(column) if self.class.column_names.include?(column.to_s) and not send(column).nil?
344
345
  end
346
+ enums = previous.respond_to?(:defined_enums) ? previous.defined_enums : {}
345
347
  previous.tap do |prev|
346
348
  prev.id = id # `dup` clears the `id` so we add that back
347
- changed_attributes.select { |k,v| self.class.column_names.include?(k) }.each { |attr, before| prev[attr] = before }
349
+ changed_attributes.select { |k,v| self.class.column_names.include?(k) }.each do |attr, before|
350
+ before = enums[attr][before] if enums[attr]
351
+ prev[attr] = before
352
+ end
348
353
  end
349
354
  end
350
355
 
@@ -12,6 +12,25 @@ module PaperTrail
12
12
  def dump(object)
13
13
  ActiveSupport::JSON.encode object
14
14
  end
15
+
16
+ # Returns a SQL condition to be used to match the given field and value in
17
+ # the serialized object.
18
+ def where_object_condition(arel_field, field, value)
19
+ # Convert to JSON to handle strings and nulls correctly.
20
+ json_value = value.to_json
21
+
22
+ # If the value is a number, we need to ensure that we find the next
23
+ # character too, which is either `,` or `}`, to ensure that searching
24
+ # for the value 12 doesn't yield false positives when the value is
25
+ # 123.
26
+ if value.is_a? Numeric
27
+ arel_field.matches("%\"#{field}\":#{json_value},%").
28
+ or(
29
+ arel_field.matches("%\"#{field}\":#{json_value}}%"))
30
+ else
31
+ arel_field.matches("%\"#{field}\":#{json_value}%")
32
+ end
33
+ end
15
34
  end
16
35
  end
17
36
  end
@@ -12,6 +12,12 @@ module PaperTrail
12
12
  def dump(object)
13
13
  ::YAML.dump object
14
14
  end
15
+
16
+ # Returns a SQL condition to be used to match the given field and value in
17
+ # the serialized object.
18
+ def where_object_condition(arel_field, field, value)
19
+ arel_field.matches("%\n#{field}: #{value}\n%")
20
+ end
15
21
  end
16
22
  end
17
23
  end
@@ -37,35 +37,50 @@ module PaperTrail
37
37
  # `timestamp_arg` receives `true`
38
38
  def subsequent(obj, timestamp_arg = false)
39
39
  if timestamp_arg != true && self.primary_key_is_int?
40
- return where("#{table_name}.#{self.primary_key} > ?", obj).order("#{table_name}.#{self.primary_key} ASC")
40
+ return where(arel_table[primary_key].gt(obj.id)).order(arel_table[primary_key].asc)
41
41
  end
42
42
 
43
43
  obj = obj.send(PaperTrail.timestamp_field) if obj.is_a?(self)
44
- where("#{table_name}.#{PaperTrail.timestamp_field} > ?", obj).order(self.timestamp_sort_order)
44
+ where(arel_table[PaperTrail.timestamp_field].gt(obj)).order(self.timestamp_sort_order)
45
45
  end
46
46
 
47
47
  def preceding(obj, timestamp_arg = false)
48
48
  if timestamp_arg != true && self.primary_key_is_int?
49
- return where("#{table_name}.#{self.primary_key} < ?", obj).order("#{table_name}.#{self.primary_key} DESC")
49
+ return where(arel_table[primary_key].lt(obj.id)).order(arel_table[primary_key].desc)
50
50
  end
51
51
 
52
52
  obj = obj.send(PaperTrail.timestamp_field) if obj.is_a?(self)
53
- where("#{table_name}.#{PaperTrail.timestamp_field} < ?", obj).order(self.timestamp_sort_order('DESC'))
53
+ where(arel_table[PaperTrail.timestamp_field].lt(obj)).order(self.timestamp_sort_order('desc'))
54
54
  end
55
55
 
56
56
 
57
57
  def between(start_time, end_time)
58
- where("#{table_name}.#{PaperTrail.timestamp_field} > ? AND #{table_name}.#{PaperTrail.timestamp_field} < ?",
59
- start_time, end_time).order(self.timestamp_sort_order)
58
+ where(
59
+ arel_table[PaperTrail.timestamp_field].gt(start_time).
60
+ and(arel_table[PaperTrail.timestamp_field].lt(end_time))
61
+ ).order(self.timestamp_sort_order)
60
62
  end
61
63
 
62
64
  # defaults to using the primary key as the secondary sort order if possible
63
- def timestamp_sort_order(order = 'ASC')
64
- if self.primary_key_is_int?
65
- "#{table_name}.#{PaperTrail.timestamp_field} #{order}, #{table_name}.#{self.primary_key} #{order}"
66
- else
67
- "#{table_name}.#{PaperTrail.timestamp_field} #{order}"
65
+ def timestamp_sort_order(direction = 'asc')
66
+ [arel_table[PaperTrail.timestamp_field].send(direction.downcase)].tap do |array|
67
+ array << arel_table[primary_key].send(direction.downcase) if self.primary_key_is_int?
68
+ end
69
+ end
70
+
71
+ # Performs an attribute search on the serialized object by invoking the
72
+ # identically-named method in the serializer being used.
73
+ def where_object(args = {})
74
+ raise ArgumentError, 'expected to receive a Hash' unless args.is_a?(Hash)
75
+ arel_field = arel_table[:object]
76
+
77
+ where_conditions = args.map do |field, value|
78
+ PaperTrail.serializer.where_object_condition(arel_field, field, value)
79
+ end.reduce do |condition1, condition2|
80
+ condition1.and(condition2)
68
81
  end
82
+
83
+ where(where_conditions)
69
84
  end
70
85
 
71
86
  def primary_key_is_int?
@@ -189,10 +204,14 @@ module PaperTrail
189
204
  end
190
205
 
191
206
  def index
192
- table_name = self.class.table_name
193
- @index ||= sibling_versions.
194
- select(["#{table_name}.#{PaperTrail.timestamp_field}", "#{table_name}.#{self.class.primary_key}"]).
195
- order("#{table_name}.#{PaperTrail.timestamp_field} ASC").index(self)
207
+ table = self.class.arel_table unless @index
208
+ @index ||=
209
+ if self.class.primary_key_is_int?
210
+ sibling_versions.select(table[self.class.primary_key]).order(table[self.class.primary_key].asc).index(self)
211
+ else
212
+ sibling_versions.select([table[PaperTrail.timestamp_field], table[self.class.primary_key]]).
213
+ order(self.class.timestamp_sort_order).index(self)
214
+ end
196
215
  end
197
216
 
198
217
  private
@@ -1,3 +1,18 @@
1
1
  module PaperTrail
2
- VERSION = '3.0.2'
2
+ module VERSION
3
+ MAJOR = 3
4
+ MINOR = 0
5
+ TINY = 5
6
+ PRE = nil
7
+
8
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
9
+
10
+ def self.to_s
11
+ STRING
12
+ end
13
+ end
14
+
15
+ def self.version
16
+ VERSION::STRING
17
+ end
3
18
  end
@@ -3,7 +3,7 @@ require 'paper_trail/version_number'
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = 'paper_trail'
6
- s.version = PaperTrail::VERSION
6
+ s.version = PaperTrail.version
7
7
  s.platform = Gem::Platform::RUBY
8
8
  s.summary = "Track changes to your models' data. Good for auditing or versioning."
9
9
  s.description = s.summary
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ # This model is in the test suite soley for the purpose of testing ActiveRecord::Enum,
4
+ # which is available in ActiveRecord4+ only
5
+ describe PostWithStatus do
6
+ if defined?(ActiveRecord::Enum)
7
+ with_versioning do
8
+ let(:post) { PostWithStatus.create!(:status => 'draft') }
9
+
10
+ it "should stash the enum value properly in versions" do
11
+ post.published!
12
+ post.archived!
13
+ post.previous_version.published?.should == true
14
+ end
15
+ end
16
+ end
17
+ end
@@ -40,5 +40,49 @@ describe PaperTrail::Version do
40
40
  end
41
41
  end
42
42
  end
43
+
44
+ describe "Class" do
45
+ describe :where_object do
46
+ it { PaperTrail::Version.should respond_to(:where_object) }
47
+
48
+ context "invalid arguments" do
49
+ it "should raise an error" do
50
+ expect { PaperTrail::Version.where_object(:foo) }.to raise_error(ArgumentError)
51
+ expect { PaperTrail::Version.where_object([]) }.to raise_error(ArgumentError)
52
+ end
53
+ end
54
+
55
+ context "valid arguments", :versioning => true do
56
+ let(:widget) { Widget.new }
57
+ let(:name) { Faker::Name.first_name }
58
+ let(:int) { rand(10) + 1 }
59
+
60
+ before do
61
+ widget.update_attributes!(:name => name, :an_integer => int)
62
+ widget.update_attributes!(:name => 'foobar', :an_integer => 100)
63
+ widget.update_attributes!(:name => Faker::Name.last_name, :an_integer => 15)
64
+ end
65
+
66
+ context "`serializer == YAML`" do
67
+ specify { PaperTrail.serializer == PaperTrail::Serializers::YAML }
68
+
69
+ it "should be able to locate versions according to their `object` contents" do
70
+ PaperTrail::Version.where_object(:name => name).should == [widget.versions[1]]
71
+ PaperTrail::Version.where_object(:an_integer => 100).should == [widget.versions[2]]
72
+ end
73
+ end
74
+
75
+ context "`serializer == JSON`" do
76
+ before { PaperTrail.serializer = PaperTrail::Serializers::JSON }
77
+ specify { PaperTrail.serializer == PaperTrail::Serializers::JSON }
78
+
79
+ it "should be able to locate versions according to their `object` contents" do
80
+ PaperTrail::Version.where_object(:name => name).should == [widget.versions[1]]
81
+ PaperTrail::Version.where_object(:an_integer => 100).should == [widget.versions[2]]
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
43
87
  end
44
88
  end
@@ -61,8 +61,65 @@ describe Widget do
61
61
  end
62
62
  end
63
63
 
64
+ describe "Association", :versioning => true do
65
+ describe "sort order" do
66
+ it "should sort by the timestamp order from the `VersionConcern`" do
67
+ widget.versions.to_sql.should ==
68
+ widget.versions.reorder(PaperTrail::Version.timestamp_sort_order).to_sql
69
+ end
70
+ end
71
+ end
72
+
64
73
  describe "Methods" do
65
74
  describe "Instance", :versioning => true do
75
+ describe :originator do
76
+ it { should respond_to(:originator) }
77
+
78
+ describe "return value" do
79
+ let(:orig_name) { Faker::Name.name }
80
+ let(:new_name) { Faker::Name.name }
81
+ before { PaperTrail.whodunnit = orig_name }
82
+
83
+ context "accessed from live model instance" do
84
+ specify { widget.should be_live }
85
+
86
+ it "should return the originator for the model at a given state" do
87
+ widget.originator.should == orig_name
88
+ widget.whodunnit(new_name) { |w| w.update_attributes(:name => 'Elizabeth') }
89
+ widget.originator.should == new_name
90
+ end
91
+ end
92
+
93
+ context "accessed from a reified model instance" do
94
+ before do
95
+ widget.update_attributes(:name => 'Andy')
96
+ PaperTrail.whodunnit = new_name
97
+ widget.update_attributes(:name => 'Elizabeth')
98
+ end
99
+ let(:reified_widget) { widget.versions[1].reify }
100
+
101
+ it "should return the appropriate originator" do
102
+ reified_widget.originator.should == orig_name
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ describe :version_at do
109
+ it { should respond_to(:version_at) }
110
+
111
+ context "Timestamp argument is AFTER object has been destroyed" do
112
+ before do
113
+ widget.update_attribute(:name, 'foobar')
114
+ widget.destroy
115
+ end
116
+
117
+ it "should return `nil`" do
118
+ widget.version_at(Time.now).should be_nil
119
+ end
120
+ end
121
+ end
122
+
66
123
  describe :whodunnit do
67
124
  it { should respond_to(:whodunnit) }
68
125
 
@@ -127,9 +184,9 @@ describe Widget do
127
184
  it { should respond_to(:paper_trail_off!) }
128
185
 
129
186
  it 'should set the `paper_trail_enabled_for_model?` to `false`' do
130
- subject.paper_trail_enabled_for_model?.should be_true
187
+ subject.paper_trail_enabled_for_model?.should == true
131
188
  subject.paper_trail_off!
132
- subject.paper_trail_enabled_for_model?.should be_false
189
+ subject.paper_trail_enabled_for_model?.should == false
133
190
  end
134
191
  end
135
192
 
@@ -154,9 +211,9 @@ describe Widget do
154
211
  it { should respond_to(:paper_trail_on!) }
155
212
 
156
213
  it 'should set the `paper_trail_enabled_for_model?` to `true`' do
157
- subject.paper_trail_enabled_for_model?.should be_false
214
+ subject.paper_trail_enabled_for_model?.should == false
158
215
  subject.paper_trail_on!
159
- subject.paper_trail_enabled_for_model?.should be_true
216
+ subject.paper_trail_enabled_for_model?.should == true
160
217
  end
161
218
  end
162
219
 
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'PaperTrail::VERSION' do
4
+
5
+ describe "Constants" do
6
+ subject { PaperTrail::VERSION }
7
+
8
+ describe :MAJOR do
9
+ it { should be_const_defined(:MAJOR) }
10
+ it { subject::MAJOR.should be_a(Integer) }
11
+ end
12
+ describe :MINOR do
13
+ it { should be_const_defined(:MINOR) }
14
+ it { subject::MINOR.should be_a(Integer) }
15
+ end
16
+ describe :TINY do
17
+ it { should be_const_defined(:TINY) }
18
+ it { subject::TINY.should be_a(Integer) }
19
+ end
20
+ describe :PRE do
21
+ it { should be_const_defined(:PRE) }
22
+ if PaperTrail::VERSION::PRE
23
+ it { subject::PRE.should be_instance_of(String) }
24
+ end
25
+ end
26
+ describe :STRING do
27
+ it { should be_const_defined(:STRING) }
28
+ it { subject::STRING.should be_instance_of(String) }
29
+
30
+ it "should join the numbers into a period separated string" do
31
+ subject::STRING.should ==
32
+ [subject::MAJOR, subject::MINOR, subject::TINY, subject::PRE].compact.join('.')
33
+ end
34
+ end
35
+ end
36
+
37
+ end
38
+
39
+ describe PaperTrail do
40
+ describe :version do
41
+ it { should respond_to(:version) }
42
+ its(:version) { should == PaperTrail::VERSION::STRING }
43
+ end
44
+ end
@@ -44,5 +44,5 @@ RSpec.configure do |config|
44
44
  # order dependency and want to debug it, you can fix the order by providing
45
45
  # the seed, which is printed after each run.
46
46
  # --seed 1234
47
- # config.order ||= 'random'
47
+ # config.order = 'random'
48
48
  end
@@ -0,0 +1,8 @@
1
+ class PostWithStatus < ActiveRecord::Base
2
+ has_paper_trail
3
+
4
+ # ActiveRecord::Enum is only supported in AR4.1+
5
+ if defined?(ActiveRecord::Enum)
6
+ enum :status => { :draft => 0, :published => 1, :archived => 2 }
7
+ end
8
+ end
@@ -92,6 +92,10 @@ class SetUpTestTables < ActiveRecord::Migration
92
92
  t.string :content
93
93
  end
94
94
 
95
+ create_table :post_with_statuses, :force => true do |t|
96
+ t.integer :status
97
+ end
98
+
95
99
  create_table :animals, :force => true do |t|
96
100
  t.string :name
97
101
  t.string :species # single table inheritance column
@@ -100,7 +104,7 @@ class SetUpTestTables < ActiveRecord::Migration
100
104
  create_table :documents, :force => true do |t|
101
105
  t.string :name
102
106
  end
103
-
107
+
104
108
  create_table :legacy_widgets, :force => true do |t|
105
109
  t.string :name
106
110
  t.integer :version
@@ -1376,7 +1376,6 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
1376
1376
  end
1377
1377
  end
1378
1378
 
1379
-
1380
1379
  private
1381
1380
 
1382
1381
  # Updates `model`'s last version so it looks like the version was
@@ -37,4 +37,39 @@ class JSONTest < ActiveSupport::TestCase
37
37
  end
38
38
  end
39
39
 
40
+ context '`where_object` class method' do
41
+ context "when value is a string" do
42
+ should 'construct correct WHERE query' do
43
+ matches = PaperTrail::Serializers::JSON.where_object_condition(
44
+ PaperTrail::Version.arel_table[:object], :arg1, "Val 1")
45
+
46
+ assert matches.instance_of?(Arel::Nodes::Matches)
47
+ assert_equal matches.right, "%\"arg1\":\"Val 1\"%"
48
+ end
49
+ end
50
+
51
+ context "when value is `null`" do
52
+ should 'construct correct WHERE query' do
53
+ matches = PaperTrail::Serializers::JSON.where_object_condition(
54
+ PaperTrail::Version.arel_table[:object], :arg1, nil)
55
+
56
+ assert matches.instance_of?(Arel::Nodes::Matches)
57
+ assert_equal matches.right, "%\"arg1\":null%"
58
+ end
59
+ end
60
+
61
+ context "when value is a number" do
62
+ should 'construct correct WHERE query' do
63
+ grouping = PaperTrail::Serializers::JSON.where_object_condition(
64
+ PaperTrail::Version.arel_table[:object], :arg1, -3.5)
65
+
66
+ assert grouping.instance_of?(Arel::Nodes::Grouping)
67
+ matches = grouping.select { |v| v.instance_of?(Arel::Nodes::Matches) }
68
+ # Numeric arguments need to ensure that they match for only the number, not the beginning
69
+ # of a #, so it uses an Grouping matcher (See notes on `PaperTrail::Serializers::JSON`)
70
+ assert_equal matches.first.right, "%\"arg1\":-3.5,%"
71
+ assert_equal matches.last.right, "%\"arg1\":-3.5}%"
72
+ end
73
+ end
74
+ end
40
75
  end
@@ -37,4 +37,12 @@ class YamlTest < ActiveSupport::TestCase
37
37
  end
38
38
  end
39
39
 
40
+ context '`where_object` class method' do
41
+ should 'construct correct WHERE query' do
42
+ matches = PaperTrail::Serializers::YAML.where_object_condition(
43
+ PaperTrail::Version.arel_table[:object], :arg1, "Val 1")
44
+ assert matches.instance_of?(Arel::Nodes::Matches)
45
+ assert_equal matches.right, "%\narg1: Val 1\n%"
46
+ end
47
+ end
40
48
  end
@@ -68,7 +68,7 @@ class PaperTrail::VersionTest < ActiveSupport::TestCase
68
68
  should "return all versions that were created before the Timestamp" do
69
69
  value = PaperTrail::Version.subsequent(1.hour.ago, true)
70
70
  assert_equal value, @animal.versions.to_a
71
- assert_not_nil value.to_sql.match(/ORDER BY versions.created_at ASC/)
71
+ assert_not_nil value.to_sql.match(/ORDER BY #{PaperTrail::Version.arel_table[:created_at].asc.to_sql}/)
72
72
  end
73
73
  end
74
74
 
@@ -87,7 +87,7 @@ class PaperTrail::VersionTest < ActiveSupport::TestCase
87
87
  should "return all versions that were created before the Timestamp" do
88
88
  value = PaperTrail::Version.preceding(5.seconds.from_now, true)
89
89
  assert_equal value, @animal.versions.reverse
90
- assert_not_nil value.to_sql.match(/ORDER BY versions.created_at DESC/)
90
+ assert_not_nil value.to_sql.match(/ORDER BY #{PaperTrail::Version.arel_table[:created_at].desc.to_sql}/)
91
91
  end
92
92
  end
93
93
 
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: 3.0.2
4
+ version: 3.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Stewart
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-05-12 00:00:00.000000000 Z
12
+ date: 2014-08-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -247,23 +247,28 @@ files:
247
247
  - lib/paper_trail.rb
248
248
  - lib/paper_trail/cleaner.rb
249
249
  - lib/paper_trail/config.rb
250
+ - lib/paper_trail/frameworks/active_record.rb
251
+ - lib/paper_trail/frameworks/active_record/models/paper_trail/version.rb
250
252
  - lib/paper_trail/frameworks/cucumber.rb
251
253
  - lib/paper_trail/frameworks/rails.rb
254
+ - lib/paper_trail/frameworks/rails/controller.rb
255
+ - lib/paper_trail/frameworks/rails/engine.rb
252
256
  - lib/paper_trail/frameworks/rspec.rb
253
257
  - lib/paper_trail/frameworks/rspec/helpers.rb
254
258
  - lib/paper_trail/frameworks/sinatra.rb
255
259
  - lib/paper_trail/has_paper_trail.rb
256
260
  - lib/paper_trail/serializers/json.rb
257
261
  - lib/paper_trail/serializers/yaml.rb
258
- - lib/paper_trail/version.rb
259
262
  - lib/paper_trail/version_concern.rb
260
263
  - lib/paper_trail/version_number.rb
261
264
  - paper_trail.gemspec
262
265
  - spec/generators/install_generator_spec.rb
263
266
  - spec/models/joined_version_spec.rb
267
+ - spec/models/post_with_status_spec.rb
264
268
  - spec/models/version_spec.rb
265
269
  - spec/models/widget_spec.rb
266
270
  - spec/modules/version_concern_spec.rb
271
+ - spec/modules/version_number_spec.rb
267
272
  - spec/paper_trail_spec.rb
268
273
  - spec/requests/articles_spec.rb
269
274
  - spec/spec_helper.rb
@@ -288,6 +293,7 @@ files:
288
293
  - test/dummy/app/models/legacy_widget.rb
289
294
  - test/dummy/app/models/person.rb
290
295
  - test/dummy/app/models/post.rb
296
+ - test/dummy/app/models/post_with_status.rb
291
297
  - test/dummy/app/models/protected_widget.rb
292
298
  - test/dummy/app/models/song.rb
293
299
  - test/dummy/app/models/translation.rb
@@ -366,16 +372,18 @@ required_rubygems_version: !ruby/object:Gem::Requirement
366
372
  version: 1.3.6
367
373
  requirements: []
368
374
  rubyforge_project:
369
- rubygems_version: 2.2.1
375
+ rubygems_version: 2.2.2
370
376
  signing_key:
371
377
  specification_version: 4
372
378
  summary: Track changes to your models' data. Good for auditing or versioning.
373
379
  test_files:
374
380
  - spec/generators/install_generator_spec.rb
375
381
  - spec/models/joined_version_spec.rb
382
+ - spec/models/post_with_status_spec.rb
376
383
  - spec/models/version_spec.rb
377
384
  - spec/models/widget_spec.rb
378
385
  - spec/modules/version_concern_spec.rb
386
+ - spec/modules/version_number_spec.rb
379
387
  - spec/paper_trail_spec.rb
380
388
  - spec/requests/articles_spec.rb
381
389
  - spec/spec_helper.rb
@@ -400,6 +408,7 @@ test_files:
400
408
  - test/dummy/app/models/legacy_widget.rb
401
409
  - test/dummy/app/models/person.rb
402
410
  - test/dummy/app/models/post.rb
411
+ - test/dummy/app/models/post_with_status.rb
403
412
  - test/dummy/app/models/protected_widget.rb
404
413
  - test/dummy/app/models/song.rb
405
414
  - test/dummy/app/models/translation.rb