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.
@@ -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 "un-serialization",
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
- item.class.unserialize_attribute_changes_for_paper_trail!(changes)
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
@@ -2,8 +2,8 @@ module PaperTrail
2
2
  # :nodoc:
3
3
  module VERSION
4
4
  MAJOR = 5
5
- MINOR = 0
6
- TINY = 1
5
+ MINOR = 1
6
+ TINY = 0
7
7
  PRE = nil
8
8
 
9
9
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".").freeze
@@ -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.37.2"
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)
@@ -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
@@ -1,4 +1,5 @@
1
1
  class FooHabtm < ActiveRecord::Base
2
2
  has_and_belongs_to_many :bar_habtms
3
+ accepts_nested_attributes_for :bar_habtms
3
4
  has_paper_trail
4
5
  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, "Val 1")
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], :arg1, nil)
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], :arg1, -3.5)
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], :arg1, "Val 1")
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.1
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-04 00:00:00.000000000 Z
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.37.2
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.37.2
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/attributes_serialization.rb
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