paper_trail 6.0.1 → 6.0.2

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: e2aaa66f683707d78b3d5c9a294e01c9e9b16813
4
- data.tar.gz: f9e5043fea515093cd21d151eda33e7b915de194
3
+ metadata.gz: c23b36829183d413cfe6fa50f86b50dc5d5dd99a
4
+ data.tar.gz: c1964749e5fb1f1d23d8d066558d8d61323a6b73
5
5
  SHA512:
6
- metadata.gz: ac92887f5f16f868f7f5936e9e9c6353533c3a9f233989ae075d3cb1550a3c4bc9b51b6523dc22899e695b2bbfded7f3a9ed4a7bbd814fe9e9c4ec15eabea30b
7
- data.tar.gz: 8b31ac54123aeba74eb156b5d6971c3a579e3a3cae0ee8e2b55c2c824c018e8e0267f616fb92aa09ebec1a2404b93e57e2ffe8ad4c9554a9509c4ea4d38e142f
6
+ metadata.gz: 31692c33ac64c0322c894038b1a75c50ba5231a42ba7ebfa08a08517b721d0a08eba2e2e821566586da4a7bca7c7da92b7f74aee257bc5c698dbe8e8284a42dd
7
+ data.tar.gz: 0fe50a103b744687e0e2acd02dbb24c5f32f82c0255a356e89f2a66a1c5bd4bd0b688ac7eb8394cb34fb08b856780cc8e55b53be6afbaea7f6f99c24cfc5d408
@@ -18,7 +18,9 @@ Please use our [bug report template][1].
18
18
 
19
19
  ## Development
20
20
 
21
- Install gems with `bundle exec appraisal install`.
21
+ Install gems with `bundle exec appraisal install`. This requires ruby >= 2.0.
22
+ (It is still possible to run the `ar-4.2` gemfile locally on ruby 1.9.3, but
23
+ not the `ar-5.0` gemfile.)
22
24
 
23
25
  Testing is a little awkward because the test suite:
24
26
 
@@ -26,43 +28,73 @@ Testing is a little awkward because the test suite:
26
28
  1. Contains a "dummy" rails app with three databases (test, foo, and bar)
27
29
  1. Supports three different RDBMS': sqlite, mysql, and postgres
28
30
 
29
- ### Run tests with sqlite
31
+ ### Test sqlite, AR 4.2
30
32
 
31
33
  ```
32
34
  # Create the appropriate database config. file
33
35
  rm test/dummy/config/database.yml
34
36
  DB=sqlite bundle exec rake prepare
35
37
 
36
- # If this is the first test run ever, create databases
38
+ # If this is the first test run ever, create databases.
39
+ # We can't use `appraisal` inside the test dummy, so we must set `BUNDLE_GEMFILE`.
40
+ # See test/dummy/config/boot.rb for a complete explanation.
37
41
  cd test/dummy
42
+ export BUNDLE_GEMFILE=../../gemfiles/ar_4.2.gemfile
38
43
  RAILS_ENV=test bundle exec rake db:setup
39
44
  RAILS_ENV=foo bundle exec rake db:setup
40
45
  RAILS_ENV=bar bundle exec rake db:setup
46
+ unset BUNDLE_GEMFILE
47
+ cd ../..
48
+
49
+ # Run tests
50
+ DB=sqlite bundle exec appraisal ar-4.2 rake
51
+ ```
52
+
53
+ ### Test sqlite, AR 5
54
+
55
+ ```
56
+ # Create the appropriate database config. file
57
+ rm test/dummy/config/database.yml
58
+ DB=sqlite bundle exec rake prepare
59
+
60
+ # If this is the first test run ever, create databases.
61
+ # We can't use `appraisal` inside the test dummy, so we must set `BUNDLE_GEMFILE`.
62
+ # See test/dummy/config/boot.rb for a complete explanation.
63
+ cd test/dummy
64
+ export BUNDLE_GEMFILE=../../gemfiles/ar_5.0.gemfile
65
+ RAILS_ENV=test bundle exec rake db:environment:set db:setup
66
+ RAILS_ENV=foo bundle exec rake db:environment:set db:setup
67
+ RAILS_ENV=bar bundle exec rake db:environment:set db:setup
68
+ unset BUNDLE_GEMFILE
41
69
  cd ../..
42
70
 
43
71
  # Run tests
44
72
  DB=sqlite bundle exec appraisal ar-5.0 rake
45
73
  ```
46
74
 
47
- ### Run tests with mysql
75
+ ### Test mysql, AR 5
48
76
 
49
77
  ```
50
78
  # Create the appropriate database config. file
51
79
  rm test/dummy/config/database.yml
52
80
  DB=mysql bundle exec rake prepare
53
81
 
54
- # If this is the first test run ever, create databases
82
+ # If this is the first test run ever, create databases.
83
+ # We can't use `appraisal` inside the test dummy, so we must set `BUNDLE_GEMFILE`.
84
+ # See test/dummy/config/boot.rb for a complete explanation.
55
85
  cd test/dummy
56
- RAILS_ENV=test bundle exec rake db:setup
57
- RAILS_ENV=foo bundle exec rake db:setup
58
- RAILS_ENV=bar bundle exec rake db:setup
86
+ export BUNDLE_GEMFILE=../../gemfiles/ar_5.0.gemfile
87
+ RAILS_ENV=test bundle exec rake db:environment:set db:setup
88
+ RAILS_ENV=foo bundle exec rake db:environment:set db:setup
89
+ RAILS_ENV=bar bundle exec rake db:environment:set db:setup
90
+ unset BUNDLE_GEMFILE
59
91
  cd ../..
60
92
 
61
93
  # Run tests
62
94
  DB=mysql bundle exec appraisal ar-5.0 rake
63
95
  ```
64
96
 
65
- ### Run tests with postgres
97
+ ### Test postgres, AR 5
66
98
 
67
99
  ```
68
100
  # Create the appropriate database config. file
@@ -71,10 +103,14 @@ DB=postgres bundle exec rake prepare
71
103
 
72
104
  # If this is the first test run ever, create databases.
73
105
  # Unlike mysql, use create/migrate instead of setup.
106
+ # We can't use `appraisal` inside the test dummy, so we must set `BUNDLE_GEMFILE`.
107
+ # See test/dummy/config/boot.rb for a complete explanation.
74
108
  cd test/dummy
109
+ export BUNDLE_GEMFILE=../../gemfiles/ar_5.0.gemfile
75
110
  DB=postgres RAILS_ENV=test bundle exec rake db:drop db:create db:migrate
76
111
  DB=postgres RAILS_ENV=foo bundle exec rake db:drop db:create db:migrate
77
112
  DB=postgres RAILS_ENV=bar bundle exec rake db:drop db:create db:migrate
113
+ unset BUNDLE_GEMFILE
78
114
  cd ../..
79
115
 
80
116
  # Run tests
@@ -2,13 +2,13 @@
2
2
  # one by one as the offenses are removed from the code base.
3
3
 
4
4
  Metrics/AbcSize:
5
- Max: 30 # Goal: 15
5
+ Max: 22 # Goal: 15
6
6
 
7
7
  Metrics/CyclomaticComplexity:
8
8
  Max: 8 # Goal: 6
9
9
 
10
10
  Metrics/PerceivedComplexity:
11
- Max: 10 # Goal: 7
11
+ Max: 9 # Goal: 7
12
12
 
13
13
  Style/FrozenStringLiteralComment:
14
14
  Enabled: false
@@ -17,6 +17,28 @@ recommendations of [keepachangelog.com](http://keepachangelog.com/).
17
17
 
18
18
  - None
19
19
 
20
+ ## 6.0.2 (2016-12-13)
21
+
22
+ ### Breaking Changes
23
+
24
+ - None
25
+
26
+ ### Added
27
+
28
+ - None
29
+
30
+ ### Fixed
31
+
32
+ - `88e513f` - Surprise argument modification bug in `where_object_changes`
33
+ - `c7efd62` - Column type-detection bug in `where_object_changes`
34
+ - [#905](https://github.com/airblade/paper_trail/pull/905) - Only invoke
35
+ `logger.warn` if `logger` instance exists
36
+
37
+ ### Code Quality
38
+
39
+ - Improve Metrics/AbcSize from 30 to 22
40
+ - Improve Metrics/PerceivedComplexity from 10 to 9
41
+
20
42
  ## 6.0.1 (2016-12-04)
21
43
 
22
44
  ### Breaking Changes
data/README.md CHANGED
@@ -11,7 +11,7 @@ has been destroyed.
11
11
  | Version | Documentation |
12
12
  | -------------- | ------------- |
13
13
  | Unreleased | https://github.com/airblade/paper_trail/blob/master/README.md |
14
- | 6.0.1 | https://github.com/airblade/paper_trail/blob/v6.0.1/README.md |
14
+ | 6.0.2 | https://github.com/airblade/paper_trail/blob/v6.0.2/README.md |
15
15
  | 5.2.3 | https://github.com/airblade/paper_trail/blob/v5.2.3/README.md |
16
16
  | 4.2.0 | https://github.com/airblade/paper_trail/blob/v4.2.0/README.md |
17
17
  | 3.0.9 | https://github.com/airblade/paper_trail/blob/v3.0.9/README.md |
@@ -124,22 +124,12 @@ module PaperTrail
124
124
 
125
125
  @model_class.send :attr_accessor, :paper_trail_event
126
126
 
127
- # In rails 4, the `has_many` syntax for specifying order uses a lambda.
128
- if ::ActiveRecord::VERSION::MAJOR >= 4
129
- @model_class.has_many(
130
- @model_class.versions_association_name,
131
- -> { order(model.timestamp_sort_order) },
132
- class_name: @model_class.version_class_name,
133
- as: :item
134
- )
135
- else
136
- @model_class.has_many(
137
- @model_class.versions_association_name,
138
- class_name: @model_class.version_class_name,
139
- as: :item,
140
- order: @model_class.paper_trail.version_class.timestamp_sort_order
141
- )
142
- end
127
+ @model_class.has_many(
128
+ @model_class.versions_association_name,
129
+ -> { order(model.timestamp_sort_order) },
130
+ class_name: @model_class.version_class_name,
131
+ as: :item
132
+ )
143
133
  end
144
134
 
145
135
  # Adds callbacks to record changes to habtm associations such that on save
@@ -0,0 +1,60 @@
1
+ module PaperTrail
2
+ module Queries
3
+ module Versions
4
+ # For public API documentation, see `where_object` in
5
+ # `paper_trail/version_concern.rb`.
6
+ # @api private
7
+ class WhereObject
8
+ # - version_model_class - The class that VersionConcern was mixed into.
9
+ # - attributes - A `Hash` of attributes and values. See the public API
10
+ # documentation for details.
11
+ # @api private
12
+ def initialize(version_model_class, attributes)
13
+ @version_model_class = version_model_class
14
+ @attributes = attributes
15
+ end
16
+
17
+ # @api private
18
+ def execute
19
+ case @version_model_class.columns_hash["object"].type
20
+ when :jsonb
21
+ jsonb
22
+ when :json
23
+ json
24
+ else
25
+ text
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ # @api private
32
+ def json
33
+ predicates = []
34
+ values = []
35
+ @attributes.each do |field, value|
36
+ predicates.push "object->>? = ?"
37
+ values.concat([field, value.to_s])
38
+ end
39
+ sql = predicates.join(" and ")
40
+ @version_model_class.where(sql, *values)
41
+ end
42
+
43
+ # @api private
44
+ def jsonb
45
+ @version_model_class.where("object @> ?", @attributes.to_json)
46
+ end
47
+
48
+ # @api private
49
+ def text
50
+ arel_field = @version_model_class.arel_table[:object]
51
+ where_conditions = @attributes.map { |field, value|
52
+ ::PaperTrail.serializer.where_object_condition(arel_field, field, value)
53
+ }
54
+ where_conditions = where_conditions.reduce { |a, e| a.and(e) }
55
+ @version_model_class.where(where_conditions)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,68 @@
1
+ module PaperTrail
2
+ module Queries
3
+ module Versions
4
+ # For public API documentation, see `where_object` in
5
+ # `paper_trail/version_concern.rb`.
6
+ # @api private
7
+ class WhereObjectChanges
8
+ # - version_model_class - The class that VersionConcern was mixed into.
9
+ # - attributes - A `Hash` of attributes and values. See the public API
10
+ # documentation for details.
11
+ # @api private
12
+ def initialize(version_model_class, attributes)
13
+ @version_model_class = version_model_class
14
+
15
+ # Currently, this `deep_dup` is necessary because the `jsonb` branch
16
+ # modifies `@attributes`, and that would be a nasty suprise for
17
+ # consumers of this class.
18
+ # TODO: Stop modifying `@attributes`, then remove `deep_dup`.
19
+ @attributes = attributes.deep_dup
20
+ end
21
+
22
+ # @api private
23
+ def execute
24
+ case @version_model_class.columns_hash["object_changes"].type
25
+ when :jsonb
26
+ jsonb
27
+ when :json
28
+ json
29
+ else
30
+ text
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ # @api private
37
+ def json
38
+ predicates = []
39
+ values = []
40
+ @attributes.each do |field, value|
41
+ predicates.push(
42
+ "((object_changes->>? ILIKE ?) OR (object_changes->>? ILIKE ?))"
43
+ )
44
+ values.concat([field, "[#{value.to_json},%", field, "[%,#{value.to_json}]%"])
45
+ end
46
+ sql = predicates.join(" and ")
47
+ @version_model_class.where(sql, *values)
48
+ end
49
+
50
+ # @api private
51
+ def jsonb
52
+ @attributes.each { |field, value| @attributes[field] = [value] }
53
+ @version_model_class.where("object_changes @> ?", @attributes.to_json)
54
+ end
55
+
56
+ # @api private
57
+ def text
58
+ arel_field = @version_model_class.arel_table[:object_changes]
59
+ where_conditions = @attributes.map { |field, value|
60
+ ::PaperTrail.serializer.where_object_changes_condition(arel_field, field, value)
61
+ }
62
+ where_conditions = where_conditions.reduce { |a, e| a.and(e) }
63
+ @version_model_class.where(where_conditions)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -118,30 +118,47 @@ module PaperTrail
118
118
  source_version.nil?
119
119
  end
120
120
 
121
+ # Updates `data` from the model's `meta` option and from `controller_info`.
121
122
  # @api private
122
- def merge_metadata(data)
123
- # First we merge the model-level metadata in `meta`.
123
+ def merge_metadata_into(data)
124
+ merge_metadata_from_model_into(data)
125
+ merge_metadata_from_controller_into(data)
126
+ end
127
+
128
+ # Updates `data` from `controller_info`.
129
+ # @api private
130
+ def merge_metadata_from_controller_into(data)
131
+ data.merge(PaperTrail.controller_info || {})
132
+ end
133
+
134
+ # Updates `data` from the model's `meta` option.
135
+ # @api private
136
+ def merge_metadata_from_model_into(data)
124
137
  @record.paper_trail_options[:meta].each do |k, v|
125
- data[k] =
126
- if v.respond_to?(:call)
127
- v.call(@record)
128
- elsif v.is_a?(Symbol) && @record.respond_to?(v, true)
129
- # If it is an attribute that is changing in an existing object,
130
- # be sure to grab the current version.
131
- if @record.has_attribute?(v) &&
132
- attribute_changed_in_latest_version?(v) &&
133
- data[:event] != "create"
134
- attribute_in_previous_version(v)
135
- else
136
- @record.send(v)
137
- end
138
- else
139
- v
140
- end
138
+ data[k] = model_metadatum(v, data[:event])
141
139
  end
140
+ end
142
141
 
143
- # Second we merge any extra data from the controller (if available).
144
- data.merge(PaperTrail.controller_info || {})
142
+ # Given a `value` from the model's `meta` option, returns an object to be
143
+ # persisted. The `value` can be a simple scalar value, but it can also
144
+ # be a symbol that names a model method, or even a Proc.
145
+ # @api private
146
+ def model_metadatum(value, event)
147
+ if value.respond_to?(:call)
148
+ value.call(@record)
149
+ elsif value.is_a?(Symbol) && @record.respond_to?(value, true)
150
+ # If it is an attribute that is changing in an existing object,
151
+ # be sure to grab the current version.
152
+ if event != "create" &&
153
+ @record.has_attribute?(value) &&
154
+ attribute_changed_in_latest_version?(value)
155
+ attribute_in_previous_version(value)
156
+ else
157
+ @record.send(value)
158
+ end
159
+ else
160
+ value
161
+ end
145
162
  end
146
163
 
147
164
  # Returns the object (not a Version) as it became next.
@@ -210,7 +227,7 @@ module PaperTrail
210
227
  data[:object_changes] = recordable_object_changes
211
228
  end
212
229
  add_transaction_id_to(data)
213
- merge_metadata(data)
230
+ merge_metadata_into(data)
214
231
  end
215
232
 
216
233
  def record_destroy
@@ -238,7 +255,7 @@ module PaperTrail
238
255
  whodunnit: PaperTrail.whodunnit
239
256
  }
240
257
  add_transaction_id_to(data)
241
- merge_metadata(data)
258
+ merge_metadata_into(data)
242
259
  end
243
260
 
244
261
  # Returns a boolean indicating whether to store serialized version diffs
@@ -280,7 +297,7 @@ module PaperTrail
280
297
  data[:object_changes] = recordable_object_changes
281
298
  end
282
299
  add_transaction_id_to(data)
283
- merge_metadata(data)
300
+ merge_metadata_into(data)
284
301
  end
285
302
 
286
303
  # Returns an object which can be assigned to the `object` attribute of a
@@ -329,29 +346,15 @@ module PaperTrail
329
346
  # Saves associations if the join table for `VersionAssociation` exists.
330
347
  def save_associations(version)
331
348
  return unless PaperTrail.config.track_associations?
332
- save_associations_belongs_to(version)
333
- save_associations_habtm(version)
349
+ save_bt_associations(version)
350
+ save_habtm_associations(version)
334
351
  end
335
352
 
336
- def save_associations_belongs_to(version)
353
+ # Save all `belongs_to` associations.
354
+ # @api private
355
+ def save_bt_associations(version)
337
356
  @record.class.reflect_on_all_associations(:belongs_to).each do |assoc|
338
- assoc_version_args = {
339
- version_id: version.id,
340
- foreign_key_name: assoc.foreign_key
341
- }
342
-
343
- if assoc.options[:polymorphic]
344
- associated_record = @record.send(assoc.name) if @record.send(assoc.foreign_type)
345
- if associated_record && associated_record.class.paper_trail.enabled?
346
- assoc_version_args[:foreign_key_id] = associated_record.id
347
- end
348
- elsif assoc.klass.paper_trail.enabled?
349
- assoc_version_args[:foreign_key_id] = @record.send(assoc.foreign_key)
350
- end
351
-
352
- if assoc_version_args.key?(:foreign_key_id)
353
- PaperTrail::VersionAssociation.create(assoc_version_args)
354
- end
357
+ save_bt_association(assoc, version)
355
358
  end
356
359
  end
357
360
 
@@ -359,7 +362,8 @@ module PaperTrail
359
362
  # HABTM associations looked like before any changes were made, by using
360
363
  # the `paper_trail_habtm` data structure. Then, we create
361
364
  # `VersionAssociation` records for each of the associated records.
362
- def save_associations_habtm(version)
365
+ # @api private
366
+ def save_habtm_associations(version)
363
367
  @record.class.reflect_on_all_associations(:has_and_belongs_to_many).each do |a|
364
368
  next unless save_habtm_association?(a)
365
369
  habtm_assoc_ids(a).each do |id|
@@ -502,12 +506,34 @@ module PaperTrail
502
506
  end
503
507
 
504
508
  def log_version_errors(version, action)
505
- version.logger.warn(
509
+ version.logger && version.logger.warn(
506
510
  "Unable to create version for #{action} of #{@record.class.name}" +
507
511
  "##{@record.id}: " + version.errors.full_messages.join(", ")
508
512
  )
509
513
  end
510
514
 
515
+ # Save a single `belongs_to` association.
516
+ # @api private
517
+ def save_bt_association(assoc, version)
518
+ assoc_version_args = {
519
+ version_id: version.id,
520
+ foreign_key_name: assoc.foreign_key
521
+ }
522
+
523
+ if assoc.options[:polymorphic]
524
+ associated_record = @record.send(assoc.name) if @record.send(assoc.foreign_type)
525
+ if associated_record && associated_record.class.paper_trail.enabled?
526
+ assoc_version_args[:foreign_key_id] = associated_record.id
527
+ end
528
+ elsif assoc.klass.paper_trail.enabled?
529
+ assoc_version_args[:foreign_key_id] = @record.send(assoc.foreign_key)
530
+ end
531
+
532
+ if assoc_version_args.key?(:foreign_key_id)
533
+ PaperTrail::VersionAssociation.create(assoc_version_args)
534
+ end
535
+ end
536
+
511
537
  # Returns true if the given HABTM association should be saved.
512
538
  # @api private
513
539
  def save_habtm_association?(assoc)