paper_trail 8.1.2 → 9.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/paper_trail/install_generator.rb +2 -0
  3. data/lib/paper_trail.rb +130 -78
  4. data/lib/paper_trail/attribute_serializers/attribute_serializer_factory.rb +3 -1
  5. data/lib/paper_trail/attribute_serializers/cast_attribute_serializer.rb +2 -0
  6. data/lib/paper_trail/attribute_serializers/object_attribute.rb +2 -0
  7. data/lib/paper_trail/attribute_serializers/object_changes_attribute.rb +2 -0
  8. data/lib/paper_trail/cleaner.rb +2 -0
  9. data/lib/paper_trail/config.rb +33 -8
  10. data/lib/paper_trail/frameworks/active_record.rb +2 -0
  11. data/lib/paper_trail/frameworks/active_record/models/paper_trail/version.rb +2 -0
  12. data/lib/paper_trail/frameworks/active_record/models/paper_trail/version_association.rb +2 -0
  13. data/lib/paper_trail/frameworks/cucumber.rb +5 -3
  14. data/lib/paper_trail/frameworks/rails.rb +2 -0
  15. data/lib/paper_trail/frameworks/rails/controller.rb +28 -16
  16. data/lib/paper_trail/frameworks/rails/engine.rb +2 -0
  17. data/lib/paper_trail/frameworks/rspec.rb +5 -3
  18. data/lib/paper_trail/frameworks/rspec/helpers.rb +2 -0
  19. data/lib/paper_trail/has_paper_trail.rb +2 -1
  20. data/lib/paper_trail/model_config.rb +76 -14
  21. data/lib/paper_trail/queries/versions/where_object.rb +2 -0
  22. data/lib/paper_trail/queries/versions/where_object_changes.rb +3 -1
  23. data/lib/paper_trail/record_history.rb +2 -0
  24. data/lib/paper_trail/record_trail.rb +188 -48
  25. data/lib/paper_trail/reifier.rb +4 -2
  26. data/lib/paper_trail/reifiers/belongs_to.rb +2 -0
  27. data/lib/paper_trail/reifiers/has_and_belongs_to_many.rb +2 -0
  28. data/lib/paper_trail/reifiers/has_many.rb +2 -0
  29. data/lib/paper_trail/reifiers/has_many_through.rb +2 -0
  30. data/lib/paper_trail/reifiers/has_one.rb +52 -4
  31. data/lib/paper_trail/request.rb +183 -0
  32. data/lib/paper_trail/serializers/json.rb +2 -2
  33. data/lib/paper_trail/serializers/yaml.rb +10 -14
  34. data/lib/paper_trail/type_serializers/postgres_array_serializer.rb +2 -0
  35. data/lib/paper_trail/version_association_concern.rb +1 -1
  36. data/lib/paper_trail/version_concern.rb +2 -6
  37. data/lib/paper_trail/version_number.rb +5 -3
  38. metadata +8 -21
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "paper_trail/attribute_serializers/object_attribute"
2
4
  require "paper_trail/reifiers/belongs_to"
3
5
  require "paper_trail/reifiers/has_and_belongs_to_many"
@@ -54,7 +56,7 @@ module PaperTrail
54
56
  # @api private
55
57
  def each_enabled_association(associations)
56
58
  associations.each do |assoc|
57
- next unless assoc.klass.paper_trail.enabled?
59
+ next unless ::PaperTrail.request.enabled_for_model?(assoc.klass)
58
60
  yield assoc
59
61
  end
60
62
  end
@@ -192,7 +194,7 @@ module PaperTrail
192
194
  # @api private
193
195
  def reify_habtm_associations(transaction_id, model, options = {})
194
196
  model.class.reflect_on_all_associations(:has_and_belongs_to_many).each do |assoc|
195
- pt_enabled = assoc.klass.paper_trail.enabled?
197
+ pt_enabled = ::PaperTrail.request.enabled_for_model?(assoc.klass)
196
198
  next unless model.class.paper_trail_save_join_tables.include?(assoc.name) || pt_enabled
197
199
  Reifiers::HasAndBelongsToMany.reify(pt_enabled, assoc, model, options, transaction_id)
198
200
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PaperTrail
2
4
  module Reifiers
3
5
  # Reify a single `belongs_to` association of `model`.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PaperTrail
2
4
  module Reifiers
3
5
  # Reify a single HABTM association of `model`.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PaperTrail
2
4
  module Reifiers
3
5
  # Reify a single, direct (not `through`) `has_many` association of `model`.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PaperTrail
2
4
  module Reifiers
3
5
  # Reify a single HMT association of `model`.
@@ -1,12 +1,46 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PaperTrail
2
4
  module Reifiers
3
5
  # Reify a single `has_one` association of `model`.
4
6
  # @api private
5
7
  module HasOne
8
+ # A more helpful error message, instead of the AssociationTypeMismatch
9
+ # you would get if, eg. we were to try to assign a Bicycle to the :car
10
+ # association (before, if there were multiple records we would just take
11
+ # the first and hope for the best).
12
+ # @api private
13
+ class FoundMoreThanOne < RuntimeError
14
+ MESSAGE_FMT = <<~STR
15
+ Unable to reify has_one association. Expected to find one %s,
16
+ but found %d.
17
+
18
+ This is a known issue, and a good example of why association tracking
19
+ is an experimental feature that should not be used in production.
20
+
21
+ That said, this is a rare error. In spec/models/person_spec.rb we
22
+ reproduce it by having two STI models with the same foreign_key (Car
23
+ and Bicycle are both Vehicles and the FK for both is owner_id)
24
+
25
+ If you'd like to help fix this error, please read
26
+ https://github.com/airblade/paper_trail/issues/594
27
+ and see spec/models/person_spec.rb
28
+ STR
29
+
30
+ def initialize(base_class_name, num_records_found)
31
+ @base_class_name = base_class_name.to_s
32
+ @num_records_found = num_records_found.to_i
33
+ end
34
+
35
+ def message
36
+ format(MESSAGE_FMT, @base_class_name, @num_records_found)
37
+ end
38
+ end
39
+
6
40
  class << self
7
41
  # @api private
8
42
  def reify(assoc, model, options, transaction_id)
9
- version = load_version_for_has_one(assoc, model, transaction_id, options[:version_at])
43
+ version = load_version(assoc, model, transaction_id, options[:version_at])
10
44
  return unless version
11
45
  if version.event == "create"
12
46
  create_event(assoc, model, options)
@@ -31,15 +65,29 @@ module PaperTrail
31
65
  # Given a has-one association `assoc` on `model`, return the version
32
66
  # record from the point in time identified by `transaction_id` or `version_at`.
33
67
  # @api private
34
- def load_version_for_has_one(assoc, model, transaction_id, version_at)
68
+ def load_version(assoc, model, transaction_id, version_at)
69
+ base_class_name = assoc.klass.base_class.name
70
+ versions = load_versions(assoc, model, transaction_id, version_at, base_class_name)
71
+ case versions.length
72
+ when 0
73
+ nil
74
+ when 1
75
+ versions.first
76
+ else
77
+ raise FoundMoreThanOne.new(base_class_name, versions.length)
78
+ end
79
+ end
80
+
81
+ # @api private
82
+ def load_versions(assoc, model, transaction_id, version_at, base_class_name)
35
83
  version_table_name = model.class.paper_trail.version_class.table_name
36
84
  model.class.paper_trail.version_class.joins(:version_associations).
37
85
  where("version_associations.foreign_key_name = ?", assoc.foreign_key).
38
86
  where("version_associations.foreign_key_id = ?", model.id).
39
- where("#{version_table_name}.item_type = ?", assoc.klass.base_class.name).
87
+ where("#{version_table_name}.item_type = ?", base_class_name).
40
88
  where("created_at >= ? OR transaction_id = ?", version_at, transaction_id).
41
89
  order("#{version_table_name}.id ASC").
42
- first
90
+ load
43
91
  end
44
92
 
45
93
  # @api private
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "request_store"
4
+
5
+ module PaperTrail
6
+ # Manages variables that affect the current HTTP request, such as `whodunnit`.
7
+ #
8
+ # Please do not use `PaperTrail::Request` directly, use `PaperTrail.request`.
9
+ # Currently, `Request` is a `Module`, but in the future it is quite possible
10
+ # we may make it a `Class`. If we make such a choice, we will not provide any
11
+ # warning and will not treat it as a breaking change. You've been warned :)
12
+ #
13
+ # @api private
14
+ module Request
15
+ class InvalidOption < RuntimeError
16
+ end
17
+
18
+ class << self
19
+ # @api private
20
+ def clear_transaction_id
21
+ self.transaction_id = nil
22
+ end
23
+
24
+ # Sets any data from the controller that you want PaperTrail to store.
25
+ # See also `PaperTrail::Rails::Controller#info_for_paper_trail`.
26
+ #
27
+ # PaperTrail.request.controller_info = { ip: request_user_ip }
28
+ # PaperTrail.request.controller_info # => { ip: '127.0.0.1' }
29
+ #
30
+ # @api public
31
+ def controller_info=(value)
32
+ store[:controller_info] = value
33
+ end
34
+
35
+ # Returns the data from the controller that you want PaperTrail to store.
36
+ # See also `PaperTrail::Rails::Controller#info_for_paper_trail`.
37
+ #
38
+ # PaperTrail.request.controller_info = { ip: request_user_ip }
39
+ # PaperTrail.request.controller_info # => { ip: '127.0.0.1' }
40
+ #
41
+ # @api public
42
+ def controller_info
43
+ store[:controller_info]
44
+ end
45
+
46
+ # Switches PaperTrail off for the given model.
47
+ # @api public
48
+ def disable_model(model_class)
49
+ enabled_for_model(model_class, false)
50
+ end
51
+
52
+ # Switches PaperTrail on for the given model.
53
+ # @api public
54
+ def enable_model(model_class)
55
+ enabled_for_model(model_class, true)
56
+ end
57
+
58
+ # Sets whether PaperTrail is enabled or disabled for the current request.
59
+ # @api public
60
+ def enabled=(value)
61
+ store[:enabled] = value
62
+ end
63
+
64
+ # Returns `true` if PaperTrail is enabled for the request, `false` otherwise.
65
+ # See `PaperTrail::Rails::Controller#paper_trail_enabled_for_controller`.
66
+ # @api public
67
+ def enabled?
68
+ !!store[:enabled]
69
+ end
70
+
71
+ # Sets whether PaperTrail is enabled or disabled for this model in the
72
+ # current request.
73
+ # @api public
74
+ def enabled_for_model(model, value)
75
+ store[:"enabled_for_#{model}"] = value
76
+ end
77
+
78
+ # Returns `true` if PaperTrail is enabled for this model in the current
79
+ # request, `false` otherwise.
80
+ # @api public
81
+ def enabled_for_model?(model)
82
+ model.include?(::PaperTrail::Model::InstanceMethods) &&
83
+ !!store.fetch(:"enabled_for_#{model}", true)
84
+ end
85
+
86
+ # @api private
87
+ def merge(options)
88
+ options.to_h.each do |k, v|
89
+ store[k] = v
90
+ end
91
+ end
92
+
93
+ # @api private
94
+ def set(options)
95
+ store.clear
96
+ merge(options)
97
+ end
98
+
99
+ # Returns a deep copy of the internal hash from our RequestStore. Keys are
100
+ # all symbols. Values are mostly primitives, but whodunnit can be a Proc.
101
+ # We cannot use Marshal.dump here because it doesn't support Proc. It is
102
+ # unclear exactly how `deep_dup` handles a Proc, but it doesn't complain.
103
+ # @api private
104
+ def to_h
105
+ store.deep_dup
106
+ end
107
+
108
+ # @api private
109
+ def transaction_id
110
+ store[:transaction_id]
111
+ end
112
+
113
+ # @api private
114
+ def transaction_id=(id)
115
+ store[:transaction_id] = id
116
+ end
117
+
118
+ # Temporarily set `options` and execute a block.
119
+ # @api private
120
+ def with(options)
121
+ return unless block_given?
122
+ validate_public_options(options)
123
+ before = to_h
124
+ merge(options)
125
+ yield
126
+ ensure
127
+ set(before)
128
+ end
129
+
130
+ # Sets who is responsible for any changes that occur during request. You
131
+ # would normally use this in a migration or on the console, when working
132
+ # with models directly.
133
+ #
134
+ # `value` is usually a string, the name of a person, but you can set
135
+ # anything that responds to `to_s`. You can also set a Proc, which will
136
+ # not be evaluated until `whodunnit` is called later, usually right before
137
+ # inserting a `Version` record.
138
+ #
139
+ # @api public
140
+ def whodunnit=(value)
141
+ store[:whodunnit] = value
142
+ end
143
+
144
+ # Returns who is reponsible for any changes that occur during request.
145
+ #
146
+ # @api public
147
+ def whodunnit
148
+ who = store[:whodunnit]
149
+ who.respond_to?(:call) ? who.call : who
150
+ end
151
+
152
+ private
153
+
154
+ # Returns a Hash, initializing with default values if necessary.
155
+ # @api private
156
+ def store
157
+ RequestStore.store[:paper_trail] ||= {
158
+ enabled: true
159
+ }
160
+ end
161
+
162
+ # Provide a helpful error message if someone has a typo in one of their
163
+ # option keys. We don't validate option values here. That's traditionally
164
+ # been handled with casting (`to_s`, `!!`) in the accessor method.
165
+ # @api private
166
+ def validate_public_options(options)
167
+ options.each do |k, _v|
168
+ case k
169
+ when :controller_info,
170
+ /enabled_for_/,
171
+ :enabled,
172
+ :whodunnit
173
+ next
174
+ when :transaction_id
175
+ raise InvalidOption, "Cannot set private option: #{k}"
176
+ else
177
+ raise InvalidOption, "Invalid option: #{k}"
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
@@ -1,4 +1,4 @@
1
- require "active_support/json"
1
+ # frozen_string_literal: true
2
2
 
3
3
  module PaperTrail
4
4
  module Serializers
@@ -34,7 +34,7 @@ module PaperTrail
34
34
 
35
35
  def where_object_changes_condition(*)
36
36
  raise <<-STR.squish.freeze
37
- where_object_changes no longer supports reading json from a text
37
+ where_object_changes no longer supports reading JSON from a text
38
38
  column. The old implementation was inaccurate, returning more records
39
39
  than you wanted. This feature was deprecated in 7.1.0 and removed in
40
40
  8.0.0. The json and jsonb datatypes are still supported. See the
@@ -1,16 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "yaml"
2
4
 
3
5
  module PaperTrail
4
6
  module Serializers
5
7
  # The default serializer for, e.g. `versions.object`.
6
8
  module YAML
7
- E_WHERE_OBJ_CHANGES = <<-STR.squish.freeze
8
- where_object_changes has a known issue. When reading YAML from a text
9
- column, it may return more records than expected. Instead of a warning,
10
- this method may raise an error in the future. Please join the discussion
11
- at https://github.com/airblade/paper_trail/pull/997
12
- STR
13
-
14
9
  extend self # makes all instance methods become module methods as well
15
10
 
16
11
  def load(string)
@@ -29,13 +24,14 @@ module PaperTrail
29
24
 
30
25
  # Returns a SQL LIKE condition to be used to match the given field and
31
26
  # value in the serialized `object_changes`.
32
- def where_object_changes_condition(arel_field, field, value)
33
- ::ActiveSupport::Deprecation.warn(E_WHERE_OBJ_CHANGES)
34
-
35
- # Need to check first (before) and secondary (after) fields
36
- m1 = "%\n#{field}:\n- #{value}\n%"
37
- m2 = "%\n#{field}:\n-%\n- #{value}\n%"
38
- arel_field.matches(m1).or(arel_field.matches(m2))
27
+ def where_object_changes_condition(*)
28
+ raise <<-STR.squish.freeze
29
+ where_object_changes no longer supports reading YAML from a text
30
+ column. The old implementation was inaccurate, returning more records
31
+ than you wanted. This feature was deprecated in 8.1.0 and removed in
32
+ 9.0.0. The json and jsonb datatypes are still supported. See
33
+ discussion at https://github.com/airblade/paper_trail/pull/997
34
+ STR
39
35
  end
40
36
  end
41
37
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PaperTrail
2
4
  module TypeSerializers
3
5
  # Provides an alternative method of serialization
@@ -1,4 +1,4 @@
1
- require "active_support/concern"
1
+ # frozen_string_literal: true
2
2
 
3
3
  module PaperTrail
4
4
  # Functionality for `PaperTrail::VersionAssociation`. Exists in a module
@@ -1,4 +1,5 @@
1
- require "active_support/concern"
1
+ # frozen_string_literal: true
2
+
2
3
  require "paper_trail/attribute_serializers/object_changes_attribute"
3
4
  require "paper_trail/queries/versions/where_object"
4
5
  require "paper_trail/queries/versions/where_object_changes"
@@ -230,11 +231,6 @@ module PaperTrail
230
231
  @paper_trail_originator ||= previous.try(:whodunnit)
231
232
  end
232
233
 
233
- def originator
234
- ::ActiveSupport::Deprecation.warn "Use paper_trail_originator instead of originator."
235
- paper_trail_originator
236
- end
237
-
238
234
  # Returns who changed the item from the state it had in this version. This
239
235
  # is an alias for `whodunnit`.
240
236
  def terminator
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PaperTrail
2
4
  # The version number of the paper_trail gem. Not to be confused with
3
5
  # `PaperTrail::Version`. Ruby constants are case-sensitive, apparently,
@@ -5,9 +7,9 @@ module PaperTrail
5
7
  # because of this confusion, but it's not worth the breaking change.
6
8
  # People are encouraged to use `PaperTrail.gem_version` instead.
7
9
  module VERSION
8
- MAJOR = 8
9
- MINOR = 1
10
- TINY = 2
10
+ MAJOR = 9
11
+ MINOR = 0
12
+ TINY = 0
11
13
  PRE = nil
12
14
 
13
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".").freeze
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: 8.1.2
4
+ version: 9.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Stewart
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2017-12-22 00:00:00.000000000 Z
13
+ date: 2018-03-27 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -21,7 +21,7 @@ dependencies:
21
21
  version: '4.2'
22
22
  - - "<"
23
23
  - !ruby/object:Gem::Version
24
- version: '5.2'
24
+ version: '5.3'
25
25
  type: :runtime
26
26
  prerelease: false
27
27
  version_requirements: !ruby/object:Gem::Requirement
@@ -31,7 +31,7 @@ dependencies:
31
31
  version: '4.2'
32
32
  - - "<"
33
33
  - !ruby/object:Gem::Version
34
- version: '5.2'
34
+ version: '5.3'
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: request_store
37
37
  requirement: !ruby/object:Gem::Requirement
@@ -74,20 +74,6 @@ dependencies:
74
74
  - - "~>"
75
75
  - !ruby/object:Gem::Version
76
76
  version: '9.1'
77
- - !ruby/object:Gem::Dependency
78
- name: database_cleaner
79
- requirement: !ruby/object:Gem::Requirement
80
- requirements:
81
- - - "~>"
82
- - !ruby/object:Gem::Version
83
- version: '1.6'
84
- type: :development
85
- prerelease: false
86
- version_requirements: !ruby/object:Gem::Requirement
87
- requirements:
88
- - - "~>"
89
- - !ruby/object:Gem::Version
90
- version: '1.6'
91
77
  - !ruby/object:Gem::Dependency
92
78
  name: ffaker
93
79
  requirement: !ruby/object:Gem::Requirement
@@ -252,7 +238,7 @@ description: |
252
238
  Track changes to your models, for auditing or versioning. See how a model looked
253
239
  at any stage in its lifecycle, revert it to any version, or restore it after it
254
240
  has been destroyed.
255
- email: batkinz@gmail.com
241
+ email: jared@jaredbeck.com
256
242
  executables: []
257
243
  extensions: []
258
244
  extra_rdoc_files: []
@@ -292,6 +278,7 @@ files:
292
278
  - lib/paper_trail/reifiers/has_many.rb
293
279
  - lib/paper_trail/reifiers/has_many_through.rb
294
280
  - lib/paper_trail/reifiers/has_one.rb
281
+ - lib/paper_trail/request.rb
295
282
  - lib/paper_trail/serializers/json.rb
296
283
  - lib/paper_trail/serializers/yaml.rb
297
284
  - lib/paper_trail/type_serializers/postgres_array_serializer.rb
@@ -310,7 +297,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
310
297
  requirements:
311
298
  - - ">="
312
299
  - !ruby/object:Gem::Version
313
- version: 2.2.0
300
+ version: 2.3.0
314
301
  required_rubygems_version: !ruby/object:Gem::Requirement
315
302
  requirements:
316
303
  - - ">="
@@ -318,7 +305,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
318
305
  version: 1.3.6
319
306
  requirements: []
320
307
  rubyforge_project:
321
- rubygems_version: 2.7.2
308
+ rubygems_version: 2.7.6
322
309
  signing_key:
323
310
  specification_version: 4
324
311
  summary: Track changes to your models.