Class: PaperTrail::RecordTrail
- Inherits:
-
Object
- Object
- PaperTrail::RecordTrail
- Defined in:
- lib/paper_trail/record_trail.rb
Overview
Represents the “paper trail” for a single record.
Constant Summary collapse
- RAILS_GTE_5_1 =
::ActiveRecord.gem_version >= ::Gem::Version.new("5.1.0.beta1")
Instance Method Summary collapse
-
#clear_rolled_back_versions ⇒ Object
Invoked after rollbacks to ensure versions records are not created for changes that never actually took place.
-
#clear_version_instance ⇒ Object
Invoked via`after_update` callback for when a previous version is reified and then saved.
-
#data_for_create ⇒ Object
private
PT-AT extends this method to add its transaction id.
-
#data_for_destroy ⇒ Object
private
PT-AT extends this method to add its transaction id.
-
#data_for_update ⇒ Object
private
PT-AT extends this method to add its transaction id.
-
#data_for_update_columns ⇒ Object
private
PT-AT extends this method to add its transaction id.
-
#enabled? ⇒ Boolean
private
Is PT enabled for this particular record?.
-
#initialize(record) ⇒ RecordTrail
constructor
A new instance of RecordTrail.
-
#live? ⇒ Boolean
Returns true if this instance is the current, live one; returns false if this instance came from a previous version.
-
#next_version ⇒ Object
Returns the object (not a Version) as it became next.
-
#originator ⇒ Object
Returns who put ‘@record` into its current state.
-
#previous_version ⇒ Object
Returns the object (not a Version) as it was most recently.
- #record_create ⇒ Object
-
#record_destroy(recording_order) ⇒ Object
private
‘recording_order` is “after” or “before”.
-
#record_update(force:, in_after_callback:, is_touch:) ⇒ Object
private
paper_trail-association_tracking.
-
#record_update_columns(changes) ⇒ Object
private
paper_trail-association_tracking.
-
#reset_timestamp_attrs_for_update_if_needed ⇒ Object
Invoked via callback when a user attempts to persist a reified ‘Version`.
-
#save_version? ⇒ Boolean
private
AR callback.
-
#save_with_version(*args) ⇒ Object
Save, and create a version record regardless of options such as ‘:on`, `:if`, or `:unless`.
- #source_version ⇒ Object
-
#update_column(name, value) ⇒ Object
Like the ‘update_column` method from `ActiveRecord::Persistence`, but also creates a version to record those changes.
-
#update_columns(attributes) ⇒ Object
Like the ‘update_columns` method from `ActiveRecord::Persistence`, but also creates a version to record those changes.
-
#version_at(timestamp, reify_options = {}) ⇒ Object
Returns the object (not a Version) as it was at the given timestamp.
-
#versions_between(start_time, end_time) ⇒ Object
Returns the objects (not Versions) as they were between the given times.
Constructor Details
#initialize(record) ⇒ RecordTrail
Returns a new instance of RecordTrail.
12 13 14 |
# File 'lib/paper_trail/record_trail.rb', line 12 def initialize(record) @record = record end |
Instance Method Details
#clear_rolled_back_versions ⇒ Object
Invoked after rollbacks to ensure versions records are not created for changes that never actually took place. Optimization: Use lazy ‘reset` instead of eager `reload` because, in many use cases, the association will not be used.
20 21 22 |
# File 'lib/paper_trail/record_trail.rb', line 20 def clear_rolled_back_versions versions.reset end |
#clear_version_instance ⇒ Object
Invoked via`after_update` callback for when a previous version is reified and then saved.
26 27 28 |
# File 'lib/paper_trail/record_trail.rb', line 26 def clear_version_instance @record.send("#{@record.class.version_association_name}=", nil) end |
#data_for_create ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
PT-AT extends this method to add its transaction id.
83 84 85 |
# File 'lib/paper_trail/record_trail.rb', line 83 def data_for_create {} end |
#data_for_destroy ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
PT-AT extends this method to add its transaction id.
113 114 115 |
# File 'lib/paper_trail/record_trail.rb', line 113 def data_for_destroy {} end |
#data_for_update ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
PT-AT extends this method to add its transaction id.
144 145 146 |
# File 'lib/paper_trail/record_trail.rb', line 144 def data_for_update {} end |
#data_for_update_columns ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
PT-AT extends this method to add its transaction id.
171 172 173 |
# File 'lib/paper_trail/record_trail.rb', line 171 def data_for_update_columns {} end |
#enabled? ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Is PT enabled for this particular record?
32 33 34 35 36 |
# File 'lib/paper_trail/record_trail.rb', line 32 def enabled? PaperTrail.enabled? && PaperTrail.request.enabled? && PaperTrail.request.enabled_for_model?(@record.class) end |
#live? ⇒ Boolean
Returns true if this instance is the current, live one; returns false if this instance came from a previous version.
40 41 42 |
# File 'lib/paper_trail/record_trail.rb', line 40 def live? source_version.nil? end |
#next_version ⇒ Object
Returns the object (not a Version) as it became next. NOTE: if self (the item) was not reified from a version, i.e. it is the
"live" item, we return nil. Perhaps we should return self instead?
47 48 49 50 51 52 |
# File 'lib/paper_trail/record_trail.rb', line 47 def next_version subsequent_version = source_version.next subsequent_version ? subsequent_version.reify : @record.class.find(@record.id) rescue StandardError # TODO: Rescue something more specific nil end |
#originator ⇒ Object
Returns who put ‘@record` into its current state.
57 58 59 |
# File 'lib/paper_trail/record_trail.rb', line 57 def originator (source_version || versions.last).try(:whodunnit) end |
#previous_version ⇒ Object
Returns the object (not a Version) as it was most recently.
64 65 66 |
# File 'lib/paper_trail/record_trail.rb', line 64 def previous_version (source_version ? source_version.previous : versions.last).try(:reify) end |
#record_create ⇒ Object
68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/paper_trail/record_trail.rb', line 68 def record_create return unless enabled? build_version_on_create(in_after_callback: true).tap do |version| version.save! # Because the version object was created using version_class.new instead # of versions_assoc.build?, the association cache is unaware. So, we # invalidate the `versions` association cache with `reset`. versions.reset end end |
#record_destroy(recording_order) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
‘recording_order` is “after” or “before”. See ModelConfig#on_destroy.
paper_trail-association_tracking
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/paper_trail/record_trail.rb', line 92 def record_destroy(recording_order) return unless enabled? && !@record.new_record? in_after_callback = recording_order == "after" event = Events::Destroy.new(@record, in_after_callback) # Merge data from `Event` with data from PT-AT. We no longer use # `data_for_destroy` but PT-AT still does. data = event.data.merge(data_for_destroy) version = @record.class.paper_trail.version_class.create(data) if version.errors.any? log_version_errors(version, :destroy) else assign_and_reset_version_association(version) version end end |
#record_update(force:, in_after_callback:, is_touch:) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
paper_trail-association_tracking
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/paper_trail/record_trail.rb', line 120 def record_update(force:, in_after_callback:, is_touch:) return unless enabled? version = build_version_on_update( force: force, in_after_callback: in_after_callback, is_touch: is_touch ) return unless version if version.save # Because the version object was created using version_class.new instead # of versions_assoc.build?, the association cache is unaware. So, we # invalidate the `versions` association cache with `reset`. versions.reset version else log_version_errors(version, :update) end end |
#record_update_columns(changes) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
paper_trail-association_tracking
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/paper_trail/record_trail.rb', line 151 def record_update_columns(changes) return unless enabled? event = Events::Update.new(@record, false, false, changes) # Merge data from `Event` with data from PT-AT. We no longer use # `data_for_update_columns` but PT-AT still does. data = event.data.merge(data_for_update_columns) versions_assoc = @record.send(@record.class.versions_association_name) version = versions_assoc.create(data) if version.errors.any? log_version_errors(version, :update) else version end end |
#reset_timestamp_attrs_for_update_if_needed ⇒ Object
Invoked via callback when a user attempts to persist a reified ‘Version`.
177 178 179 180 181 182 |
# File 'lib/paper_trail/record_trail.rb', line 177 def return if live? @record.send(:timestamp_attributes_for_update_in_model).each do |column| @record.send("restore_#{column}!") end end |
#save_version? ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
AR callback.
186 187 188 189 190 |
# File 'lib/paper_trail/record_trail.rb', line 186 def save_version? if_condition = @record.[:if] unless_condition = @record.[:unless] (if_condition.blank? || if_condition.call(@record)) && !unless_condition.try(:call, @record) end |
#save_with_version(*args) ⇒ Object
Save, and create a version record regardless of options such as ‘:on`, `:if`, or `:unless`.
Arguments are passed to ‘save`.
This is an “update” event. That is, we record the same data we would in the case of a normal AR ‘update`.
203 204 205 206 207 208 |
# File 'lib/paper_trail/record_trail.rb', line 203 def save_with_version(*args) ::PaperTrail.request(enabled: false) do @record.save(*args) end record_update(force: true, in_after_callback: false, is_touch: false) end |
#source_version ⇒ Object
192 193 194 |
# File 'lib/paper_trail/record_trail.rb', line 192 def source_version version end |
#update_column(name, value) ⇒ Object
Like the ‘update_column` method from `ActiveRecord::Persistence`, but also creates a version to record those changes.
213 214 215 |
# File 'lib/paper_trail/record_trail.rb', line 213 def update_column(name, value) update_columns(name => value) end |
#update_columns(attributes) ⇒ Object
Like the ‘update_columns` method from `ActiveRecord::Persistence`, but also creates a version to record those changes.
220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/paper_trail/record_trail.rb', line 220 def update_columns(attributes) # `@record.update_columns` skips dirty-tracking, so we can't just use # `@record.changes` or @record.saved_changes` from `ActiveModel::Dirty`. # We need to build our own hash with the changes that will be made # directly to the database. changes = {} attributes.each do |k, v| changes[k] = [@record[k], v] end @record.update_columns(attributes) record_update_columns(changes) end |
#version_at(timestamp, reify_options = {}) ⇒ Object
Returns the object (not a Version) as it was at the given timestamp.
234 235 236 237 238 239 240 |
# File 'lib/paper_trail/record_trail.rb', line 234 def version_at(, = {}) # Because a version stores how its object looked *before* the change, # we need to look for the first version created *after* the timestamp. v = versions.subsequent(, true).first return v.reify() if v @record unless @record.destroyed? end |
#versions_between(start_time, end_time) ⇒ Object
Returns the objects (not Versions) as they were between the given times.
243 244 245 246 |
# File 'lib/paper_trail/record_trail.rb', line 243 def versions_between(start_time, end_time) versions = send(@record.class.versions_association_name).between(start_time, end_time) versions.collect { |version| version_at(version.created_at) } end |