paper_trail 9.1.1 → 9.2.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,92 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module PaperTrail
4
- module Reifiers
5
- # Reify a single HMT association of `model`.
6
- # @api private
7
- module HasManyThrough
8
- class << self
9
- # @api private
10
- def reify(assoc, model, options, transaction_id)
11
- # Load the collection of through-models. For example, if `model` is a
12
- # Chapter, having many Paragraphs through Sections, then
13
- # `through_collection` will contain Sections.
14
- through_collection = model.send(assoc.options[:through])
15
-
16
- # Now, given the collection of "through" models (e.g. sections), load
17
- # the collection of "target" models (e.g. paragraphs)
18
- collection = collection(through_collection, assoc, options, transaction_id)
19
-
20
- # Finally, assign the `collection` of "target" models, e.g. to
21
- # `model.paragraphs`.
22
- model.send(assoc.name).proxy_association.target = collection
23
- end
24
-
25
- private
26
-
27
- # Examine the `source_reflection`, i.e. the "source" of `assoc` the
28
- # `ThroughReflection`. The source can be a `BelongsToReflection`
29
- # or a `HasManyReflection`.
30
- #
31
- # If the association is a has_many association again, then call
32
- # reify_has_manys for each record in `through_collection`.
33
- #
34
- # @api private
35
- def collection(through_collection, assoc, options, transaction_id)
36
- if !assoc.source_reflection.belongs_to? && through_collection.present?
37
- collection_through_has_many(through_collection, assoc, options, transaction_id)
38
- else
39
- collection_through_belongs_to(through_collection, assoc, options, transaction_id)
40
- end
41
- end
42
-
43
- # @api private
44
- def collection_through_has_many(through_collection, assoc, options, transaction_id)
45
- through_collection.each do |through_model|
46
- ::PaperTrail::Reifier.reify_has_manys(transaction_id, through_model, options)
47
- end
48
-
49
- # At this point, the "through" part of the association chain has
50
- # been reified, but not the final, "target" part. To continue our
51
- # example, `model.sections` (including `model.sections.paragraphs`)
52
- # has been loaded. However, the final "target" part of the
53
- # association, that is, `model.paragraphs`, has not been loaded. So,
54
- # we do that now.
55
- through_collection.flat_map { |through_model|
56
- through_model.public_send(assoc.name.to_sym).to_a
57
- }
58
- end
59
-
60
- # @api private
61
- def collection_through_belongs_to(through_collection, assoc, options, tx_id)
62
- ids = through_collection.map { |through_model|
63
- through_model.send(assoc.source_reflection.foreign_key)
64
- }
65
- versions = load_versions_for_hmt_association(assoc, ids, tx_id, options[:version_at])
66
- collection = Array.new assoc.klass.where(assoc.klass.primary_key => ids)
67
- Reifiers::HasMany.prepare_array(collection, options, versions)
68
- collection
69
- end
70
-
71
- # Given a `has_many(through:)` association and an array of `ids`, return
72
- # the version records from the point in time identified by `tx_id` or
73
- # `version_at`.
74
- # @api private
75
- def load_versions_for_hmt_association(assoc, ids, tx_id, version_at)
76
- version_id_subquery = assoc.klass.paper_trail.version_class.
77
- select("MIN(id)").
78
- where("item_type = ?", assoc.klass.base_class.name).
79
- where("item_id IN (?)", ids).
80
- where(
81
- "created_at >= ? OR transaction_id = ?",
82
- version_at,
83
- tx_id
84
- ).
85
- group("item_id").
86
- to_sql
87
- Reifiers::HasMany.versions_by_id(assoc.klass, version_id_subquery)
88
- end
89
- end
90
- end
91
- end
92
- end
@@ -1,135 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module PaperTrail
4
- module Reifiers
5
- # Reify a single `has_one` association of `model`.
6
- # @api private
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/paper-trail-gem/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
-
40
- class << self
41
- # @api private
42
- def reify(assoc, model, options, transaction_id)
43
- version = load_version(assoc, model, transaction_id, options[:version_at])
44
- return unless version
45
- if version.event == "create"
46
- create_event(assoc, model, options)
47
- else
48
- noncreate_event(assoc, model, options, version)
49
- end
50
- end
51
-
52
- private
53
-
54
- # @api private
55
- def create_event(assoc, model, options)
56
- if options[:mark_for_destruction]
57
- model.send(assoc.name).mark_for_destruction if model.send(assoc.name, true)
58
- else
59
- model.paper_trail.appear_as_new_record do
60
- model.send "#{assoc.name}=", nil
61
- end
62
- end
63
- end
64
-
65
- # Given a has-one association `assoc` on `model`, return the version
66
- # record from the point in time identified by `transaction_id` or `version_at`.
67
- # @api private
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
- case PaperTrail.config.association_reify_error_behaviour.to_s
78
- when "warn"
79
- version = versions.first
80
- version.logger&.warn(
81
- FoundMoreThanOne.new(base_class_name, versions.length).message
82
- )
83
- version
84
- when "ignore"
85
- versions.first
86
- else # "error"
87
- raise FoundMoreThanOne.new(base_class_name, versions.length)
88
- end
89
- end
90
- end
91
-
92
- # @api private
93
- def load_versions(assoc, model, transaction_id, version_at, base_class_name)
94
- version_table_name = model.class.paper_trail.version_class.table_name
95
- model.class.paper_trail.version_class.joins(:version_associations).
96
- where("version_associations.foreign_key_name = ?", assoc.foreign_key).
97
- where("version_associations.foreign_key_id = ?", model.id).
98
- where("#{version_table_name}.item_type = ?", base_class_name).
99
- where("created_at >= ? OR transaction_id = ?", version_at, transaction_id).
100
- order("#{version_table_name}.id ASC").
101
- load
102
- end
103
-
104
- # @api private
105
- def noncreate_event(assoc, model, options, version)
106
- child = version.reify(
107
- options.merge(
108
- has_many: false,
109
- has_one: false,
110
- belongs_to: false,
111
- has_and_belongs_to_many: false
112
- )
113
- )
114
- model.paper_trail.appear_as_new_record do
115
- without_persisting(child) do
116
- model.send "#{assoc.name}=", child
117
- end
118
- end
119
- end
120
-
121
- # Temporarily suppress #save so we can reassociate with the reified
122
- # master of a has_one relationship. Since ActiveRecord 5 the related
123
- # object is saved when it is assigned to the association. ActiveRecord
124
- # 5 also happens to be the first version that provides #suppress.
125
- def without_persisting(record)
126
- if record.class.respond_to? :suppress
127
- record.class.suppress { yield }
128
- else
129
- yield
130
- end
131
- end
132
- end
133
- end
134
- end
135
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module PaperTrail
4
- # Functionality for `PaperTrail::VersionAssociation`. Exists in a module
5
- # for the same reasons outlined in version_concern.rb.
6
- module VersionAssociationConcern
7
- extend ::ActiveSupport::Concern
8
-
9
- included do
10
- belongs_to :version
11
- end
12
- end
13
- end