paper_trail 3.0.2 → 3.0.5

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