paper_trail 5.0.1 → 5.1.0
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 +4 -4
- data/.github/CONTRIBUTING.md +2 -1
- data/.github/ISSUE_TEMPLATE.md +1 -1
- data/.rubocop_todo.yml +1 -1
- data/Appraisals +6 -10
- data/CHANGELOG.md +33 -0
- data/README.md +133 -94
- data/doc/warning_about_not_setting_whodunnit.md +32 -0
- data/lib/paper_trail.rb +0 -1
- data/lib/paper_trail/attribute_serializers/README.md +10 -0
- data/lib/paper_trail/attribute_serializers/cast_attribute_serializer.rb +58 -0
- data/lib/paper_trail/attribute_serializers/legacy_active_record_shim.rb +48 -0
- data/lib/paper_trail/attribute_serializers/object_attribute.rb +39 -0
- data/lib/paper_trail/attribute_serializers/object_changes_attribute.rb +42 -0
- data/lib/paper_trail/frameworks/rails/controller.rb +4 -4
- data/lib/paper_trail/has_paper_trail.rb +41 -22
- data/lib/paper_trail/reifier.rb +4 -3
- data/lib/paper_trail/serializers/json.rb +4 -4
- data/lib/paper_trail/serializers/yaml.rb +4 -4
- data/lib/paper_trail/version_concern.rb +10 -2
- data/lib/paper_trail/version_number.rb +2 -2
- data/paper_trail.gemspec +1 -1
- data/spec/models/widget_spec.rb +2 -1
- data/spec/modules/version_number_spec.rb +2 -1
- data/test/dummy/app/models/foo_habtm.rb +1 -0
- data/test/unit/associations_test.rb +24 -0
- data/test/unit/serializers/json_test.rb +11 -3
- data/test/unit/serializers/yaml_test.rb +4 -1
- metadata +10 -5
- data/lib/paper_trail/attributes_serialization.rb +0 -161
@@ -1,4 +1,5 @@
|
|
1
1
|
require "active_support/concern"
|
2
|
+
require "paper_trail/attribute_serializers/object_changes_attribute"
|
2
3
|
|
3
4
|
module PaperTrail
|
4
5
|
# Originally, PaperTrail did not provide this module, and all of this
|
@@ -109,8 +110,10 @@ module PaperTrail
|
|
109
110
|
end
|
110
111
|
end
|
111
112
|
|
113
|
+
# Query the `versions.objects` column using the SQL LIKE operator.
|
112
114
|
# Performs an attribute search on the serialized object by invoking the
|
113
115
|
# identically-named method in the serializer being used.
|
116
|
+
# @api public
|
114
117
|
def where_object(args = {})
|
115
118
|
raise ArgumentError, "expected to receive a Hash" unless args.is_a?(Hash)
|
116
119
|
|
@@ -135,6 +138,9 @@ module PaperTrail
|
|
135
138
|
end
|
136
139
|
end
|
137
140
|
|
141
|
+
# Query the `versions.object_changes` column by attributes, using the
|
142
|
+
# SQL LIKE operator.
|
143
|
+
# @api public
|
138
144
|
def where_object_changes(args = {})
|
139
145
|
raise ArgumentError, "expected to receive a Hash" unless args.is_a?(Hash)
|
140
146
|
|
@@ -279,7 +285,7 @@ module PaperTrail
|
|
279
285
|
# First, deserialize the `object_changes` column.
|
280
286
|
changes = HashWithIndifferentAccess.new(object_changes_deserialized)
|
281
287
|
|
282
|
-
# The next step is, perhaps unfortunately, called "
|
288
|
+
# The next step is, perhaps unfortunately, called "de-serialization",
|
283
289
|
# and appears to be responsible for custom attribute serializers. For an
|
284
290
|
# example of a custom attribute serializer, see
|
285
291
|
# `Person::TimeZoneSerializer` in the test suite.
|
@@ -291,7 +297,9 @@ module PaperTrail
|
|
291
297
|
#
|
292
298
|
# Note: `item` returns nil if `event` is "destroy".
|
293
299
|
unless item.nil?
|
294
|
-
|
300
|
+
AttributeSerializers::ObjectChangesAttribute.
|
301
|
+
new(item.class).
|
302
|
+
deserialize(changes)
|
295
303
|
end
|
296
304
|
|
297
305
|
# Finally, return a Hash mapping each attribute name to
|
data/paper_trail.gemspec
CHANGED
@@ -35,7 +35,7 @@ Gem::Specification.new do |s|
|
|
35
35
|
s.add_development_dependency "generator_spec", "~> 0.9.3"
|
36
36
|
s.add_development_dependency "database_cleaner", "~> 1.2"
|
37
37
|
s.add_development_dependency "pry-nav", "~> 0.2.4"
|
38
|
-
s.add_development_dependency "rubocop", "~> 0.
|
38
|
+
s.add_development_dependency "rubocop", "~> 0.40.0"
|
39
39
|
s.add_development_dependency "timecop", "~> 0.8.0"
|
40
40
|
|
41
41
|
if defined?(JRUBY_VERSION)
|
data/spec/models/widget_spec.rb
CHANGED
@@ -121,7 +121,8 @@ describe Widget, type: :model do
|
|
121
121
|
describe "sort order" do
|
122
122
|
it "should sort by the timestamp order from the `VersionConcern`" do
|
123
123
|
expect(widget.versions.to_sql).to eq(
|
124
|
-
widget.versions.reorder(PaperTrail::Version.timestamp_sort_order).to_sql
|
124
|
+
widget.versions.reorder(PaperTrail::Version.timestamp_sort_order).to_sql
|
125
|
+
)
|
125
126
|
end
|
126
127
|
end
|
127
128
|
end
|
@@ -28,7 +28,8 @@ describe PaperTrail::VERSION do
|
|
28
28
|
|
29
29
|
it "should join the numbers into a period separated string" do
|
30
30
|
expect(subject::STRING).to eq(
|
31
|
-
[subject::MAJOR, subject::MINOR, subject::TINY, subject::PRE].compact.join(".")
|
31
|
+
[subject::MAJOR, subject::MINOR, subject::TINY, subject::PRE].compact.join(".")
|
32
|
+
)
|
32
33
|
end
|
33
34
|
end
|
34
35
|
end
|
@@ -988,5 +988,29 @@ class AssociationsTest < ActiveSupport::TestCase
|
|
988
988
|
end
|
989
989
|
end
|
990
990
|
end
|
991
|
+
|
992
|
+
context "updated via nested attributes" do
|
993
|
+
setup do
|
994
|
+
@foo = FooHabtm.create(
|
995
|
+
name: "foo",
|
996
|
+
bar_habtms_attributes: [{ name: "bar" }]
|
997
|
+
)
|
998
|
+
Timecop.travel 1.second.since
|
999
|
+
@foo.update_attributes(
|
1000
|
+
name: "foo2",
|
1001
|
+
bar_habtms_attributes: [{ id: @foo.bar_habtms.first.id, name: "bar2" }]
|
1002
|
+
)
|
1003
|
+
|
1004
|
+
@reified = @foo.versions.last.reify(has_and_belongs_to_many: true)
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
should "see the associated object as it was at the time" do
|
1008
|
+
assert_equal "bar", @reified.bar_habtms.first.name
|
1009
|
+
end
|
1010
|
+
|
1011
|
+
should "not persist changes to the live object" do
|
1012
|
+
assert_not_equal @reified.bar_habtms.first.name, @foo.reload.bar_habtms.first.name
|
1013
|
+
end
|
1014
|
+
end
|
991
1015
|
end
|
992
1016
|
end
|
@@ -40,7 +40,9 @@ class JSONTest < ActiveSupport::TestCase
|
|
40
40
|
context "when value is a string" do
|
41
41
|
should "construct correct WHERE query" do
|
42
42
|
matches = PaperTrail::Serializers::JSON.where_object_condition(
|
43
|
-
PaperTrail::Version.arel_table[:object], :arg1,
|
43
|
+
PaperTrail::Version.arel_table[:object], :arg1,
|
44
|
+
"Val 1"
|
45
|
+
)
|
44
46
|
|
45
47
|
assert matches.instance_of?(Arel::Nodes::Matches)
|
46
48
|
if Arel::VERSION >= "6"
|
@@ -54,7 +56,10 @@ class JSONTest < ActiveSupport::TestCase
|
|
54
56
|
context "when value is `null`" do
|
55
57
|
should "construct correct WHERE query" do
|
56
58
|
matches = PaperTrail::Serializers::JSON.where_object_condition(
|
57
|
-
PaperTrail::Version.arel_table[:object],
|
59
|
+
PaperTrail::Version.arel_table[:object],
|
60
|
+
:arg1,
|
61
|
+
nil
|
62
|
+
)
|
58
63
|
|
59
64
|
assert matches.instance_of?(Arel::Nodes::Matches)
|
60
65
|
if Arel::VERSION >= "6"
|
@@ -68,7 +73,10 @@ class JSONTest < ActiveSupport::TestCase
|
|
68
73
|
context "when value is a number" do
|
69
74
|
should "construct correct WHERE query" do
|
70
75
|
grouping = PaperTrail::Serializers::JSON.where_object_condition(
|
71
|
-
PaperTrail::Version.arel_table[:object],
|
76
|
+
PaperTrail::Version.arel_table[:object],
|
77
|
+
:arg1,
|
78
|
+
-3.5
|
79
|
+
)
|
72
80
|
|
73
81
|
assert grouping.instance_of?(Arel::Nodes::Grouping)
|
74
82
|
matches = grouping.select { |v| v.instance_of?(Arel::Nodes::Matches) }
|
@@ -39,7 +39,10 @@ class YamlTest < ActiveSupport::TestCase
|
|
39
39
|
context "`where_object` class method" do
|
40
40
|
should "construct correct WHERE query" do
|
41
41
|
matches = PaperTrail::Serializers::YAML.where_object_condition(
|
42
|
-
PaperTrail::Version.arel_table[:object],
|
42
|
+
PaperTrail::Version.arel_table[:object],
|
43
|
+
:arg1,
|
44
|
+
"Val 1"
|
45
|
+
)
|
43
46
|
assert matches.instance_of?(Arel::Nodes::Matches)
|
44
47
|
if Arel::VERSION >= "6"
|
45
48
|
assert_equal matches.right.val, "%\narg1: Val 1\n%"
|
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: 5.0
|
4
|
+
version: 5.1.0
|
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: 2016-05-
|
12
|
+
date: 2016-05-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -231,14 +231,14 @@ dependencies:
|
|
231
231
|
requirements:
|
232
232
|
- - "~>"
|
233
233
|
- !ruby/object:Gem::Version
|
234
|
-
version: 0.
|
234
|
+
version: 0.40.0
|
235
235
|
type: :development
|
236
236
|
prerelease: false
|
237
237
|
version_requirements: !ruby/object:Gem::Requirement
|
238
238
|
requirements:
|
239
239
|
- - "~>"
|
240
240
|
- !ruby/object:Gem::Version
|
241
|
-
version: 0.
|
241
|
+
version: 0.40.0
|
242
242
|
- !ruby/object:Gem::Dependency
|
243
243
|
name: timecop
|
244
244
|
requirement: !ruby/object:Gem::Requirement
|
@@ -315,6 +315,7 @@ files:
|
|
315
315
|
- README.md
|
316
316
|
- Rakefile
|
317
317
|
- doc/bug_report_template.rb
|
318
|
+
- doc/warning_about_not_setting_whodunnit.md
|
318
319
|
- gemfiles/ar3.gemfile
|
319
320
|
- gemfiles/ar4.gemfile
|
320
321
|
- gemfiles/ar5.gemfile
|
@@ -325,7 +326,11 @@ files:
|
|
325
326
|
- lib/generators/paper_trail/templates/create_version_associations.rb
|
326
327
|
- lib/generators/paper_trail/templates/create_versions.rb
|
327
328
|
- lib/paper_trail.rb
|
328
|
-
- lib/paper_trail/
|
329
|
+
- lib/paper_trail/attribute_serializers/README.md
|
330
|
+
- lib/paper_trail/attribute_serializers/cast_attribute_serializer.rb
|
331
|
+
- lib/paper_trail/attribute_serializers/legacy_active_record_shim.rb
|
332
|
+
- lib/paper_trail/attribute_serializers/object_attribute.rb
|
333
|
+
- lib/paper_trail/attribute_serializers/object_changes_attribute.rb
|
329
334
|
- lib/paper_trail/cleaner.rb
|
330
335
|
- lib/paper_trail/config.rb
|
331
336
|
- lib/paper_trail/frameworks/active_record.rb
|
@@ -1,161 +0,0 @@
|
|
1
|
-
module PaperTrail
|
2
|
-
# "Serialization" here refers to the preparation of data for insertion into a
|
3
|
-
# database, particularly the `object` and `object_changes` columns in the
|
4
|
-
# `versions` table.
|
5
|
-
#
|
6
|
-
# Likewise, "deserialization" refers to any processing of data after they
|
7
|
-
# have been read from the database, for example preparing the result of
|
8
|
-
# `VersionConcern#changeset`.
|
9
|
-
module AttributesSerialization
|
10
|
-
# An attribute which needs no processing. It is part of our backport (shim)
|
11
|
-
# of rails 4.2's attribute API. See `type_for_attribute` below.
|
12
|
-
class NoOpAttribute
|
13
|
-
def type_cast_for_database(value)
|
14
|
-
value
|
15
|
-
end
|
16
|
-
|
17
|
-
def type_cast_from_database(data)
|
18
|
-
data
|
19
|
-
end
|
20
|
-
end
|
21
|
-
NO_OP_ATTRIBUTE = NoOpAttribute.new
|
22
|
-
|
23
|
-
# An attribute which requires manual (de)serialization to/from what we get
|
24
|
-
# from the database. It is part of our backport (shim) of rails 4.2's
|
25
|
-
# attribute API. See `type_for_attribute` below.
|
26
|
-
class SerializedAttribute
|
27
|
-
def initialize(coder)
|
28
|
-
@coder = coder.respond_to?(:dump) ? coder : PaperTrail.serializer
|
29
|
-
end
|
30
|
-
|
31
|
-
def type_cast_for_database(value)
|
32
|
-
@coder.dump(value)
|
33
|
-
end
|
34
|
-
|
35
|
-
def type_cast_from_database(data)
|
36
|
-
@coder.load(data)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
# The `AbstractSerializer` (de)serializes model attribute values. For
|
41
|
-
# example, the string "1.99" serializes into the integer `1` when assigned
|
42
|
-
# to an attribute of type `ActiveRecord::Type::Integer`.
|
43
|
-
#
|
44
|
-
# This implementation depends on the `type_for_attribute` method, which was
|
45
|
-
# introduced in rails 4.2. In older versions of rails, we shim this method
|
46
|
-
# below.
|
47
|
-
#
|
48
|
-
# At runtime, the `AbstractSerializer` has only one child class, the
|
49
|
-
# `CastedAttributeSerializer`, whose implementation varies depending on the
|
50
|
-
# version of ActiveRecord.
|
51
|
-
class AbstractSerializer
|
52
|
-
def initialize(klass)
|
53
|
-
@klass = klass
|
54
|
-
end
|
55
|
-
|
56
|
-
private
|
57
|
-
|
58
|
-
def apply_serialization(method, attr, val)
|
59
|
-
@klass.type_for_attribute(attr).send(method, val)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
if ::ActiveRecord::VERSION::MAJOR >= 5
|
64
|
-
# This implementation uses AR 5's `serialize` and `deserialize`.
|
65
|
-
class CastedAttributeSerializer < AbstractSerializer
|
66
|
-
def serialize(attr, val)
|
67
|
-
apply_serialization(:serialize, attr, val)
|
68
|
-
end
|
69
|
-
|
70
|
-
def deserialize(attr, val)
|
71
|
-
apply_serialization(:deserialize, attr, val)
|
72
|
-
end
|
73
|
-
end
|
74
|
-
else
|
75
|
-
# This implementation uses AR 4.2's `type_cast_for_database`. For
|
76
|
-
# versions of AR < 4.2 we provide an implementation of
|
77
|
-
# `type_cast_for_database` in our shim attribute type classes,
|
78
|
-
# `NoOpAttribute` and `SerializedAttribute`.
|
79
|
-
class CastedAttributeSerializer < AbstractSerializer
|
80
|
-
def serialize(attr, val)
|
81
|
-
val = defined_enums[attr][val] if defined_enums[attr]
|
82
|
-
apply_serialization(:type_cast_for_database, attr, val)
|
83
|
-
end
|
84
|
-
|
85
|
-
def deserialize(attr, val)
|
86
|
-
val = apply_serialization(:type_cast_from_database, attr, val)
|
87
|
-
|
88
|
-
if defined_enums[attr]
|
89
|
-
defined_enums[attr].key(val)
|
90
|
-
else
|
91
|
-
val
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
private
|
96
|
-
|
97
|
-
def defined_enums
|
98
|
-
@defined_enums ||= (@klass.respond_to?(:defined_enums) ? @klass.defined_enums : {})
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
# Backport Rails 4.2 and later's `type_for_attribute` so we can build
|
104
|
-
# on a common interface.
|
105
|
-
if ::ActiveRecord::VERSION::STRING < "4.2"
|
106
|
-
def type_for_attribute(attr_name)
|
107
|
-
serialized_attribute_types[attr_name.to_s] || NO_OP_ATTRIBUTE
|
108
|
-
end
|
109
|
-
|
110
|
-
def serialized_attribute_types
|
111
|
-
@attribute_types ||= Hash[serialized_attributes.map do |attr_name, coder|
|
112
|
-
[attr_name, SerializedAttribute.new(coder)]
|
113
|
-
end]
|
114
|
-
end
|
115
|
-
private :serialized_attribute_types
|
116
|
-
end
|
117
|
-
|
118
|
-
# Used for `Version#object` attribute.
|
119
|
-
def serialize_attributes_for_paper_trail!(attributes)
|
120
|
-
alter_attributes_for_paper_trail!(:serialize, attributes)
|
121
|
-
end
|
122
|
-
|
123
|
-
def unserialize_attributes_for_paper_trail!(attributes)
|
124
|
-
alter_attributes_for_paper_trail!(:deserialize, attributes)
|
125
|
-
end
|
126
|
-
|
127
|
-
def alter_attributes_for_paper_trail!(serialization_method, attributes)
|
128
|
-
# Don't serialize before values before inserting into columns of type
|
129
|
-
# `JSON` on `PostgreSQL` databases.
|
130
|
-
return attributes if paper_trail_version_class.object_col_is_json?
|
131
|
-
|
132
|
-
serializer = CastedAttributeSerializer.new(self)
|
133
|
-
attributes.each do |key, value|
|
134
|
-
attributes[key] = serializer.send(serialization_method, key, value)
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
# Used for Version#object_changes attribute.
|
139
|
-
def serialize_attribute_changes_for_paper_trail!(changes)
|
140
|
-
alter_attribute_changes_for_paper_trail!(:serialize, changes)
|
141
|
-
end
|
142
|
-
|
143
|
-
def unserialize_attribute_changes_for_paper_trail!(changes)
|
144
|
-
alter_attribute_changes_for_paper_trail!(:deserialize, changes)
|
145
|
-
end
|
146
|
-
|
147
|
-
def alter_attribute_changes_for_paper_trail!(serialization_method, changes)
|
148
|
-
# Don't serialize before values before inserting into columns of type
|
149
|
-
# `JSON` on `PostgreSQL` databases.
|
150
|
-
return changes if paper_trail_version_class.object_changes_col_is_json?
|
151
|
-
|
152
|
-
serializer = CastedAttributeSerializer.new(self)
|
153
|
-
changes.clone.each do |key, change|
|
154
|
-
# `change` is an Array with two elements, representing before and after.
|
155
|
-
changes[key] = Array(change).map do |value|
|
156
|
-
serializer.send(serialization_method, key, value)
|
157
|
-
end
|
158
|
-
end
|
159
|
-
end
|
160
|
-
end
|
161
|
-
end
|