paper_trail 6.0.1 → 6.0.2

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: 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)